HBASE-20642 Clients should re-use the same nonce across DDL operations

Also changes modify table operations to help the case where a MTP spans
two master, avoiding the sanity-checks propagating back to the client
unnecessarily.

Signed-off-by: Josh Elser <elserj@apache.org>
Signed-off-by: Michael Stack <stack@apache.org>
This commit is contained in:
Ankit Singhal 2018-06-20 14:52:26 -07:00 committed by Josh Elser
parent e989a9927e
commit 72784c2d83
4 changed files with 224 additions and 92 deletions

View File

@ -348,11 +348,13 @@ public class HBaseAdmin implements Admin {
public Future<Void> modifyTableAsync(TableDescriptor td) throws IOException { public Future<Void> modifyTableAsync(TableDescriptor td) throws IOException {
ModifyTableResponse response = executeCallable( ModifyTableResponse response = executeCallable(
new MasterCallable<ModifyTableResponse>(getConnection(), getRpcControllerFactory()) { new MasterCallable<ModifyTableResponse>(getConnection(), getRpcControllerFactory()) {
Long nonceGroup = ng.getNonceGroup();
Long nonce = ng.newNonce();
@Override @Override
protected ModifyTableResponse rpcCall() throws Exception { protected ModifyTableResponse rpcCall() throws Exception {
setPriority(td.getTableName()); setPriority(td.getTableName());
ModifyTableRequest request = RequestConverter.buildModifyTableRequest( ModifyTableRequest request = RequestConverter.buildModifyTableRequest(
td.getTableName(), td, ng.getNonceGroup(), ng.newNonce()); td.getTableName(), td, nonceGroup, nonce);
return master.modifyTable(getRpcController(), request); return master.modifyTable(getRpcController(), request);
} }
}); });
@ -644,11 +646,13 @@ public class HBaseAdmin implements Admin {
CreateTableResponse response = executeCallable( CreateTableResponse response = executeCallable(
new MasterCallable<CreateTableResponse>(getConnection(), getRpcControllerFactory()) { new MasterCallable<CreateTableResponse>(getConnection(), getRpcControllerFactory()) {
Long nonceGroup = ng.getNonceGroup();
Long nonce = ng.newNonce();
@Override @Override
protected CreateTableResponse rpcCall() throws Exception { protected CreateTableResponse rpcCall() throws Exception {
setPriority(desc.getTableName()); setPriority(desc.getTableName());
CreateTableRequest request = RequestConverter.buildCreateTableRequest( CreateTableRequest request = RequestConverter.buildCreateTableRequest(
desc, splitKeys, ng.getNonceGroup(), ng.newNonce()); desc, splitKeys, nonceGroup, nonce);
return master.createTable(getRpcController(), request); return master.createTable(getRpcController(), request);
} }
}); });
@ -694,11 +698,13 @@ public class HBaseAdmin implements Admin {
public Future<Void> deleteTableAsync(final TableName tableName) throws IOException { public Future<Void> deleteTableAsync(final TableName tableName) throws IOException {
DeleteTableResponse response = executeCallable( DeleteTableResponse response = executeCallable(
new MasterCallable<DeleteTableResponse>(getConnection(), getRpcControllerFactory()) { new MasterCallable<DeleteTableResponse>(getConnection(), getRpcControllerFactory()) {
Long nonceGroup = ng.getNonceGroup();
Long nonce = ng.newNonce();
@Override @Override
protected DeleteTableResponse rpcCall() throws Exception { protected DeleteTableResponse rpcCall() throws Exception {
setPriority(tableName); setPriority(tableName);
DeleteTableRequest req = DeleteTableRequest req =
RequestConverter.buildDeleteTableRequest(tableName, ng.getNonceGroup(),ng.newNonce()); RequestConverter.buildDeleteTableRequest(tableName, nonceGroup,nonce);
return master.deleteTable(getRpcController(), req); return master.deleteTable(getRpcController(), req);
} }
}); });
@ -775,12 +781,14 @@ public class HBaseAdmin implements Admin {
TruncateTableResponse response = TruncateTableResponse response =
executeCallable(new MasterCallable<TruncateTableResponse>(getConnection(), executeCallable(new MasterCallable<TruncateTableResponse>(getConnection(),
getRpcControllerFactory()) { getRpcControllerFactory()) {
Long nonceGroup = ng.getNonceGroup();
Long nonce = ng.newNonce();
@Override @Override
protected TruncateTableResponse rpcCall() throws Exception { protected TruncateTableResponse rpcCall() throws Exception {
setPriority(tableName); setPriority(tableName);
LOG.info("Started truncating " + tableName); LOG.info("Started truncating " + tableName);
TruncateTableRequest req = RequestConverter.buildTruncateTableRequest( TruncateTableRequest req = RequestConverter.buildTruncateTableRequest(
tableName, preserveSplits, ng.getNonceGroup(), ng.newNonce()); tableName, preserveSplits, nonceGroup, nonce);
return master.truncateTable(getRpcController(), req); return master.truncateTable(getRpcController(), req);
} }
}); });
@ -838,12 +846,14 @@ public class HBaseAdmin implements Admin {
TableName.isLegalFullyQualifiedTableName(tableName.getName()); TableName.isLegalFullyQualifiedTableName(tableName.getName());
EnableTableResponse response = executeCallable( EnableTableResponse response = executeCallable(
new MasterCallable<EnableTableResponse>(getConnection(), getRpcControllerFactory()) { new MasterCallable<EnableTableResponse>(getConnection(), getRpcControllerFactory()) {
Long nonceGroup = ng.getNonceGroup();
Long nonce = ng.newNonce();
@Override @Override
protected EnableTableResponse rpcCall() throws Exception { protected EnableTableResponse rpcCall() throws Exception {
setPriority(tableName); setPriority(tableName);
LOG.info("Started enable of " + tableName); LOG.info("Started enable of " + tableName);
EnableTableRequest req = EnableTableRequest req =
RequestConverter.buildEnableTableRequest(tableName, ng.getNonceGroup(),ng.newNonce()); RequestConverter.buildEnableTableRequest(tableName, nonceGroup, nonce);
return master.enableTable(getRpcController(),req); return master.enableTable(getRpcController(),req);
} }
}); });
@ -901,13 +911,15 @@ public class HBaseAdmin implements Admin {
TableName.isLegalFullyQualifiedTableName(tableName.getName()); TableName.isLegalFullyQualifiedTableName(tableName.getName());
DisableTableResponse response = executeCallable( DisableTableResponse response = executeCallable(
new MasterCallable<DisableTableResponse>(getConnection(), getRpcControllerFactory()) { new MasterCallable<DisableTableResponse>(getConnection(), getRpcControllerFactory()) {
Long nonceGroup = ng.getNonceGroup();
Long nonce = ng.newNonce();
@Override @Override
protected DisableTableResponse rpcCall() throws Exception { protected DisableTableResponse rpcCall() throws Exception {
setPriority(tableName); setPriority(tableName);
LOG.info("Started disable of " + tableName); LOG.info("Started disable of " + tableName);
DisableTableRequest req = DisableTableRequest req =
RequestConverter.buildDisableTableRequest( RequestConverter.buildDisableTableRequest(
tableName, ng.getNonceGroup(), ng.newNonce()); tableName, nonceGroup, nonce);
return master.disableTable(getRpcController(), req); return master.disableTable(getRpcController(), req);
} }
}); });
@ -1019,12 +1031,13 @@ public class HBaseAdmin implements Admin {
AddColumnResponse response = AddColumnResponse response =
executeCallable(new MasterCallable<AddColumnResponse>(getConnection(), executeCallable(new MasterCallable<AddColumnResponse>(getConnection(),
getRpcControllerFactory()) { getRpcControllerFactory()) {
Long nonceGroup = ng.getNonceGroup();
Long nonce = ng.newNonce();
@Override @Override
protected AddColumnResponse rpcCall() throws Exception { protected AddColumnResponse rpcCall() throws Exception {
setPriority(tableName); setPriority(tableName);
AddColumnRequest req = AddColumnRequest req =
RequestConverter.buildAddColumnRequest(tableName, columnFamily, ng.getNonceGroup(), RequestConverter.buildAddColumnRequest(tableName, columnFamily, nonceGroup, nonce);
ng.newNonce());
return master.addColumn(getRpcController(), req); return master.addColumn(getRpcController(), req);
} }
}); });
@ -1068,12 +1081,14 @@ public class HBaseAdmin implements Admin {
DeleteColumnResponse response = DeleteColumnResponse response =
executeCallable(new MasterCallable<DeleteColumnResponse>(getConnection(), executeCallable(new MasterCallable<DeleteColumnResponse>(getConnection(),
getRpcControllerFactory()) { getRpcControllerFactory()) {
Long nonceGroup = ng.getNonceGroup();
Long nonce = ng.newNonce();
@Override @Override
protected DeleteColumnResponse rpcCall() throws Exception { protected DeleteColumnResponse rpcCall() throws Exception {
setPriority(tableName); setPriority(tableName);
DeleteColumnRequest req = DeleteColumnRequest req =
RequestConverter.buildDeleteColumnRequest(tableName, columnFamily, RequestConverter.buildDeleteColumnRequest(tableName, columnFamily,
ng.getNonceGroup(), ng.newNonce()); nonceGroup, nonce);
return master.deleteColumn(getRpcController(), req); return master.deleteColumn(getRpcController(), req);
} }
}); });
@ -1105,12 +1120,14 @@ public class HBaseAdmin implements Admin {
ModifyColumnResponse response = ModifyColumnResponse response =
executeCallable(new MasterCallable<ModifyColumnResponse>(getConnection(), executeCallable(new MasterCallable<ModifyColumnResponse>(getConnection(),
getRpcControllerFactory()) { getRpcControllerFactory()) {
Long nonceGroup = ng.getNonceGroup();
Long nonce = ng.newNonce();
@Override @Override
protected ModifyColumnResponse rpcCall() throws Exception { protected ModifyColumnResponse rpcCall() throws Exception {
setPriority(tableName); setPriority(tableName);
ModifyColumnRequest req = ModifyColumnRequest req =
RequestConverter.buildModifyColumnRequest(tableName, columnFamily, RequestConverter.buildModifyColumnRequest(tableName, columnFamily,
ng.getNonceGroup(), ng.newNonce()); nonceGroup, nonce);
return master.modifyColumn(getRpcController(), req); return master.modifyColumn(getRpcController(), req);
} }
}); });
@ -1719,14 +1736,16 @@ public class HBaseAdmin implements Admin {
MergeTableRegionsResponse response = MergeTableRegionsResponse response =
executeCallable(new MasterCallable<MergeTableRegionsResponse>(getConnection(), executeCallable(new MasterCallable<MergeTableRegionsResponse>(getConnection(),
getRpcControllerFactory()) { getRpcControllerFactory()) {
Long nonceGroup = ng.getNonceGroup();
Long nonce = ng.newNonce();
@Override @Override
protected MergeTableRegionsResponse rpcCall() throws Exception { protected MergeTableRegionsResponse rpcCall() throws Exception {
MergeTableRegionsRequest request = RequestConverter MergeTableRegionsRequest request = RequestConverter
.buildMergeTableRegionsRequest( .buildMergeTableRegionsRequest(
encodedNameofRegionsToMerge, encodedNameofRegionsToMerge,
forcible, forcible,
ng.getNonceGroup(), nonceGroup,
ng.newNonce()); nonce);
return master.mergeTableRegions(getRpcController(), request); return master.mergeTableRegions(getRpcController(), request);
} }
}); });
@ -1813,11 +1832,13 @@ public class HBaseAdmin implements Admin {
SplitTableRegionResponse response = executeCallable( SplitTableRegionResponse response = executeCallable(
new MasterCallable<SplitTableRegionResponse>(getConnection(), getRpcControllerFactory()) { new MasterCallable<SplitTableRegionResponse>(getConnection(), getRpcControllerFactory()) {
Long nonceGroup = ng.getNonceGroup();
Long nonce = ng.newNonce();
@Override @Override
protected SplitTableRegionResponse rpcCall() throws Exception { protected SplitTableRegionResponse rpcCall() throws Exception {
setPriority(tableName); setPriority(tableName);
SplitTableRegionRequest request = RequestConverter SplitTableRegionRequest request = RequestConverter
.buildSplitTableRegionRequest(hri, splitPoint, ng.getNonceGroup(), ng.newNonce()); .buildSplitTableRegionRequest(hri, splitPoint, nonceGroup, nonce);
return master.splitRegion(getRpcController(), request); return master.splitRegion(getRpcController(), request);
} }
}); });
@ -2847,12 +2868,14 @@ public class HBaseAdmin implements Admin {
RestoreSnapshotResponse response = executeCallable( RestoreSnapshotResponse response = executeCallable(
new MasterCallable<RestoreSnapshotResponse>(getConnection(), getRpcControllerFactory()) { new MasterCallable<RestoreSnapshotResponse>(getConnection(), getRpcControllerFactory()) {
Long nonceGroup = ng.getNonceGroup();
Long nonce = ng.newNonce();
@Override @Override
protected RestoreSnapshotResponse rpcCall() throws Exception { protected RestoreSnapshotResponse rpcCall() throws Exception {
final RestoreSnapshotRequest request = RestoreSnapshotRequest.newBuilder() final RestoreSnapshotRequest request = RestoreSnapshotRequest.newBuilder()
.setSnapshot(snapshot) .setSnapshot(snapshot)
.setNonceGroup(ng.getNonceGroup()) .setNonceGroup(nonceGroup)
.setNonce(ng.newNonce()) .setNonce(nonce)
.setRestoreACL(restoreAcl) .setRestoreACL(restoreAcl)
.build(); .build();
return master.restoreSnapshot(getRpcController(), request); return master.restoreSnapshot(getRpcController(), request);

View File

@ -2241,74 +2241,76 @@ public class HMaster extends HRegionServer implements MasterServices {
} }
@Override @Override
public long addColumn( public long addColumn(final TableName tableName, final ColumnFamilyDescriptor column,
final TableName tableName, final long nonceGroup, final long nonce) throws IOException {
final ColumnFamilyDescriptor column,
final long nonceGroup,
final long nonce)
throws IOException {
checkInitialized(); checkInitialized();
checkTableExists(tableName); checkTableExists(tableName);
TableDescriptor old = getTableDescriptors().get(tableName); return modifyTable(tableName, new TableDescriptorGetter() {
if (old.hasColumnFamily(column.getName())) {
throw new InvalidFamilyOperationException("Column family '" + column.getNameAsString()
+ "' in table '" + tableName + "' already exists so cannot be added");
}
TableDescriptor newDesc = TableDescriptorBuilder @Override
.newBuilder(old).setColumnFamily(column).build(); public TableDescriptor get() throws IOException {
return modifyTable(tableName, newDesc, nonceGroup, nonce); TableDescriptor old = getTableDescriptors().get(tableName);
if (old.hasColumnFamily(column.getName())) {
throw new InvalidFamilyOperationException("Column family '" + column.getNameAsString()
+ "' in table '" + tableName + "' already exists so cannot be added");
}
return TableDescriptorBuilder.newBuilder(old).setColumnFamily(column).build();
}
}, nonceGroup, nonce);
}
/**
* Implement to return TableDescriptor after pre-checks
*/
protected interface TableDescriptorGetter {
TableDescriptor get() throws IOException;
} }
@Override @Override
public long modifyColumn( public long modifyColumn(final TableName tableName, final ColumnFamilyDescriptor descriptor,
final TableName tableName, final long nonceGroup, final long nonce) throws IOException {
final ColumnFamilyDescriptor descriptor,
final long nonceGroup,
final long nonce)
throws IOException {
checkInitialized(); checkInitialized();
checkTableExists(tableName); checkTableExists(tableName);
return modifyTable(tableName, new TableDescriptorGetter() {
TableDescriptor old = getTableDescriptors().get(tableName); @Override
if (! old.hasColumnFamily(descriptor.getName())) { public TableDescriptor get() throws IOException {
throw new InvalidFamilyOperationException("Family '" + descriptor.getNameAsString() TableDescriptor old = getTableDescriptors().get(tableName);
+ "' does not exist, so it cannot be modified"); if (!old.hasColumnFamily(descriptor.getName())) {
} throw new InvalidFamilyOperationException("Family '" + descriptor.getNameAsString()
+ "' does not exist, so it cannot be modified");
}
TableDescriptor td = TableDescriptorBuilder return TableDescriptorBuilder.newBuilder(old).modifyColumnFamily(descriptor).build();
.newBuilder(old) }
.modifyColumnFamily(descriptor) }, nonceGroup, nonce);
.build();
return modifyTable(tableName, td, nonceGroup, nonce);
} }
@Override @Override
public long deleteColumn( public long deleteColumn(final TableName tableName, final byte[] columnName,
final TableName tableName, final long nonceGroup, final long nonce) throws IOException {
final byte[] columnName,
final long nonceGroup,
final long nonce)
throws IOException {
checkInitialized(); checkInitialized();
checkTableExists(tableName); checkTableExists(tableName);
TableDescriptor old = getTableDescriptors().get(tableName); return modifyTable(tableName, new TableDescriptorGetter() {
if (! old.hasColumnFamily(columnName)) { @Override
throw new InvalidFamilyOperationException("Family '" + Bytes.toString(columnName) public TableDescriptor get() throws IOException {
+ "' does not exist, so it cannot be deleted"); TableDescriptor old = getTableDescriptors().get(tableName);
}
if (old.getColumnFamilyCount() == 1) {
throw new InvalidFamilyOperationException("Family '" + Bytes.toString(columnName)
+ "' is the only column family in the table, so it cannot be deleted");
}
TableDescriptor td = TableDescriptorBuilder if (!old.hasColumnFamily(columnName)) {
.newBuilder(old).removeColumnFamily(columnName).build(); throw new InvalidFamilyOperationException("Family '" + Bytes.toString(columnName)
return modifyTable(tableName, td, nonceGroup, nonce); + "' does not exist, so it cannot be deleted");
}
if (old.getColumnFamilyCount() == 1) {
throw new InvalidFamilyOperationException("Family '" + Bytes.toString(columnName)
+ "' is the only column family in the table, so it cannot be deleted");
}
return TableDescriptorBuilder.newBuilder(old).removeColumnFamily(columnName).build();
}
}, nonceGroup, nonce);
} }
@Override @Override
@ -2433,40 +2435,54 @@ public class HMaster extends HRegionServer implements MasterServices {
return result.get(); return result.get();
} }
private long modifyTable(final TableName tableName,
final TableDescriptorGetter newDescriptorGetter, final long nonceGroup, final long nonce)
throws IOException {
return MasterProcedureUtil
.submitProcedure(new MasterProcedureUtil.NonceProcedureRunnable(this, nonceGroup, nonce) {
@Override
protected void run() throws IOException {
TableDescriptor newDescriptor = newDescriptorGetter.get();
sanityCheckTableDescriptor(newDescriptor);
TableDescriptor oldDescriptor = getMaster().getTableDescriptors().get(tableName);
getMaster().getMasterCoprocessorHost().preModifyTable(tableName, oldDescriptor,
newDescriptor);
LOG.info(getClientIdAuditPrefix() + " modify " + tableName);
// Execute the operation synchronously - wait for the operation completes before
// continuing.
//
// We need to wait for the procedure to potentially fail due to "prepare" sanity
// checks. This will block only the beginning of the procedure. See HBASE-19953.
ProcedurePrepareLatch latch = ProcedurePrepareLatch.createBlockingLatch();
submitProcedure(
new ModifyTableProcedure(procedureExecutor.getEnvironment(), newDescriptor, latch));
latch.await();
getMaster().getMasterCoprocessorHost().postModifyTable(tableName, oldDescriptor,
newDescriptor);
}
@Override
protected String getDescription() {
return "ModifyTableProcedure";
}
});
}
@Override @Override
public long modifyTable(final TableName tableName, final TableDescriptor newDescriptor, public long modifyTable(final TableName tableName, final TableDescriptor newDescriptor,
final long nonceGroup, final long nonce) throws IOException { final long nonceGroup, final long nonce) throws IOException {
checkInitialized(); checkInitialized();
sanityCheckTableDescriptor(newDescriptor); return modifyTable(tableName, new TableDescriptorGetter() {
return MasterProcedureUtil.submitProcedure(
new MasterProcedureUtil.NonceProcedureRunnable(this, nonceGroup, nonce) {
@Override @Override
protected void run() throws IOException { public TableDescriptor get() throws IOException {
TableDescriptor oldDescriptor = getMaster().getTableDescriptors().get(tableName); return newDescriptor;
getMaster().getMasterCoprocessorHost()
.preModifyTable(tableName, oldDescriptor, newDescriptor);
LOG.info(getClientIdAuditPrefix() + " modify " + tableName);
// Execute the operation synchronously - wait for the operation completes before continuing.
//
// We need to wait for the procedure to potentially fail due to "prepare" sanity
// checks. This will block only the beginning of the procedure. See HBASE-19953.
ProcedurePrepareLatch latch = ProcedurePrepareLatch.createBlockingLatch();
submitProcedure(
new ModifyTableProcedure(procedureExecutor.getEnvironment(), newDescriptor, latch));
latch.await();
getMaster().getMasterCoprocessorHost()
.postModifyTable(tableName, oldDescriptor, newDescriptor);
} }
}, nonceGroup, nonce);
@Override
protected String getDescription() {
return "ModifyTableProcedure";
}
});
} }
public long restoreSnapshot(final SnapshotDescription snapshotDesc, public long restoreSnapshot(final SnapshotDescription snapshotDesc,

View File

@ -405,6 +405,7 @@ public class MasterProcedureTestingUtility {
*<p>It does *<p>It does
* <ol><li>Execute step N - kill the executor before store update * <ol><li>Execute step N - kill the executor before store update
* <li>Restart executor/store * <li>Restart executor/store
* <li>Executes hook for each step twice
* <li>Execute step N - and then save to store * <li>Execute step N - and then save to store
* </ol> * </ol>
* *
@ -415,11 +416,15 @@ public class MasterProcedureTestingUtility {
* @see #testRecoveryAndDoubleExecution(ProcedureExecutor, long, int, boolean) * @see #testRecoveryAndDoubleExecution(ProcedureExecutor, long, int, boolean)
*/ */
public static void testRecoveryAndDoubleExecution( public static void testRecoveryAndDoubleExecution(
final ProcedureExecutor<MasterProcedureEnv> procExec, final long procId) throws Exception { final ProcedureExecutor<MasterProcedureEnv> procExec, final long procId, final StepHook hook)
throws Exception {
ProcedureTestingUtility.waitProcedure(procExec, procId); ProcedureTestingUtility.waitProcedure(procExec, procId);
assertEquals(false, procExec.isRunning()); assertEquals(false, procExec.isRunning());
for (int i = 0; !procExec.isFinished(procId); ++i) { for (int i = 0; !procExec.isFinished(procId); ++i) {
LOG.info("Restart " + i + " exec state=" + procExec.getProcedure(procId)); LOG.info("Restart " + i + " exec state=" + procExec.getProcedure(procId));
if (hook != null) {
assertTrue(hook.execute(i));
}
restartMasterProcedureExecutor(procExec); restartMasterProcedureExecutor(procExec);
ProcedureTestingUtility.waitProcedure(procExec, procId); ProcedureTestingUtility.waitProcedure(procExec, procId);
} }
@ -427,6 +432,23 @@ public class MasterProcedureTestingUtility {
ProcedureTestingUtility.assertProcNotFailed(procExec, procId); ProcedureTestingUtility.assertProcNotFailed(procExec, procId);
} }
public static void testRecoveryAndDoubleExecution(
final ProcedureExecutor<MasterProcedureEnv> procExec, final long procId) throws Exception {
testRecoveryAndDoubleExecution(procExec, procId, null);
}
/**
* Hook which will be executed on each step
*/
public interface StepHook{
/**
* @param step Step no. at which this will be executed
* @return false if test should fail otherwise true
* @throws IOException
*/
boolean execute(int step) throws IOException;
}
/** /**
* Execute the procedure up to "lastStep" and then the ProcedureExecutor * Execute the procedure up to "lastStep" and then the ProcedureExecutor
* is restarted and an abort() is injected. * is restarted and an abort() is injected.

View File

@ -21,21 +21,28 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import java.io.IOException;
import org.apache.hadoop.hbase.DoNotRetryIOException; import org.apache.hadoop.hbase.DoNotRetryIOException;
import org.apache.hadoop.hbase.HBaseClassTestRule; import org.apache.hadoop.hbase.HBaseClassTestRule;
import org.apache.hadoop.hbase.HColumnDescriptor; import org.apache.hadoop.hbase.HColumnDescriptor;
import org.apache.hadoop.hbase.HTableDescriptor; import org.apache.hadoop.hbase.HTableDescriptor;
import org.apache.hadoop.hbase.InvalidFamilyOperationException;
import org.apache.hadoop.hbase.TableName; import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder; import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder;
import org.apache.hadoop.hbase.client.PerClientRandomNonceGenerator;
import org.apache.hadoop.hbase.client.RegionInfo; import org.apache.hadoop.hbase.client.RegionInfo;
import org.apache.hadoop.hbase.client.TableDescriptor; import org.apache.hadoop.hbase.client.TableDescriptor;
import org.apache.hadoop.hbase.client.TableDescriptorBuilder; import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
import org.apache.hadoop.hbase.master.procedure.MasterProcedureTestingUtility.StepHook;
import org.apache.hadoop.hbase.procedure2.Procedure; import org.apache.hadoop.hbase.procedure2.Procedure;
import org.apache.hadoop.hbase.procedure2.ProcedureExecutor; import org.apache.hadoop.hbase.procedure2.ProcedureExecutor;
import org.apache.hadoop.hbase.procedure2.ProcedureTestingUtility; import org.apache.hadoop.hbase.procedure2.ProcedureTestingUtility;
import org.apache.hadoop.hbase.testclassification.MasterTests; import org.apache.hadoop.hbase.testclassification.MasterTests;
import org.apache.hadoop.hbase.testclassification.MediumTests; import org.apache.hadoop.hbase.testclassification.MediumTests;
import org.apache.hadoop.hbase.util.Bytes; import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.hbase.util.NonceKey;
import org.junit.Assert;
import org.junit.ClassRule; import org.junit.ClassRule;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
@ -268,6 +275,70 @@ public class TestModifyTableProcedure extends TestTableDDLProcedureBase {
tableName, regions, "cf1", cf2); tableName, regions, "cf1", cf2);
} }
@Test
public void testColumnFamilyAdditionTwiceWithNonce() throws Exception {
final TableName tableName = TableName.valueOf(name.getMethodName());
final String cf2 = "cf2";
final String cf3 = "cf3";
final ProcedureExecutor<MasterProcedureEnv> procExec = getMasterProcedureExecutor();
// create the table
RegionInfo[] regions =
MasterProcedureTestingUtility.createTable(procExec, tableName, null, "cf1", cf3);
ProcedureTestingUtility.setKillAndToggleBeforeStoreUpdate(procExec, true);
// Modify multiple properties of the table.
final HTableDescriptor htd =
new HTableDescriptor(UTIL.getAdmin().getTableDescriptor(tableName));
boolean newCompactionEnableOption = htd.isCompactionEnabled() ? false : true;
htd.setCompactionEnabled(newCompactionEnableOption);
htd.addFamily(new HColumnDescriptor(cf2));
PerClientRandomNonceGenerator nonceGenerator = PerClientRandomNonceGenerator.get();
long nonceGroup = nonceGenerator.getNonceGroup();
long newNonce = nonceGenerator.newNonce();
NonceKey nonceKey = new NonceKey(nonceGroup, newNonce);
procExec.registerNonce(nonceKey);
// Start the Modify procedure && kill the executor
final long procId = procExec
.submitProcedure(new ModifyTableProcedure(procExec.getEnvironment(), htd), nonceKey);
// Restart the executor after MODIFY_TABLE_UPDATE_TABLE_DESCRIPTOR and try to add column family
// as nonce are there , we should not fail
MasterProcedureTestingUtility.testRecoveryAndDoubleExecution(procExec, procId, new StepHook() {
@Override
public boolean execute(int step) throws IOException {
if (step == 3) {
return procId == UTIL.getHBaseCluster().getMaster().addColumn(tableName,
ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes(cf2)).build(), nonceGroup,
newNonce);
}
return true;
}
});
//Try with different nonce, now it should fail the checks
try {
UTIL.getHBaseCluster().getMaster().addColumn(tableName,
ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes(cf2)).build(), nonceGroup,
nonceGenerator.newNonce());
Assert.fail();
} catch (InvalidFamilyOperationException e) {
}
// Validate descriptor
HTableDescriptor currentHtd = UTIL.getAdmin().getTableDescriptor(tableName);
assertEquals(newCompactionEnableOption, currentHtd.isCompactionEnabled());
assertEquals(3, currentHtd.getFamiliesKeys().size());
assertTrue(currentHtd.hasFamily(Bytes.toBytes(cf2)));
assertTrue(currentHtd.hasFamily(Bytes.toBytes(cf3)));
// cf2 should be added
MasterProcedureTestingUtility.validateTableCreation(UTIL.getHBaseCluster().getMaster(),
tableName, regions, "cf1", cf2, cf3);
}
@Test @Test
public void testRollbackAndDoubleExecutionOnline() throws Exception { public void testRollbackAndDoubleExecutionOnline() throws Exception {
final TableName tableName = TableName.valueOf(name.getMethodName()); final TableName tableName = TableName.valueOf(name.getMethodName());