HBASE-13211 Procedure V2 - master Enable/Disable table (Stephen Yuan Jiang)
This commit is contained in:
parent
4cc464dae1
commit
2cbb1afe52
File diff suppressed because it is too large
Load Diff
@ -135,3 +135,33 @@ message DeleteColumnFamilyStateData {
|
|||||||
required bytes columnfamily_name = 3;
|
required bytes columnfamily_name = 3;
|
||||||
optional TableSchema unmodified_table_schema = 4;
|
optional TableSchema unmodified_table_schema = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum EnableTableState {
|
||||||
|
ENABLE_TABLE_PREPARE = 1;
|
||||||
|
ENABLE_TABLE_PRE_OPERATION = 2;
|
||||||
|
ENABLE_TABLE_SET_ENABLING_TABLE_STATE = 3;
|
||||||
|
ENABLE_TABLE_MARK_REGIONS_ONLINE = 4;
|
||||||
|
ENABLE_TABLE_SET_ENABLED_TABLE_STATE = 5;
|
||||||
|
ENABLE_TABLE_POST_OPERATION = 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
message EnableTableStateData {
|
||||||
|
required UserInformation user_info = 1;
|
||||||
|
required TableName table_name = 2;
|
||||||
|
required bool skip_table_state_check = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum DisableTableState {
|
||||||
|
DISABLE_TABLE_PREPARE = 1;
|
||||||
|
DISABLE_TABLE_PRE_OPERATION = 2;
|
||||||
|
DISABLE_TABLE_SET_DISABLING_TABLE_STATE = 3;
|
||||||
|
DISABLE_TABLE_MARK_REGIONS_OFFLINE = 4;
|
||||||
|
DISABLE_TABLE_SET_DISABLED_TABLE_STATE = 5;
|
||||||
|
DISABLE_TABLE_POST_OPERATION = 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
message DisableTableStateData {
|
||||||
|
required UserInformation user_info = 1;
|
||||||
|
required TableName table_name = 2;
|
||||||
|
required bool skip_table_state_check = 3;
|
||||||
|
}
|
||||||
|
@ -90,14 +90,14 @@ import org.apache.hadoop.hbase.master.balancer.ClusterStatusChore;
|
|||||||
import org.apache.hadoop.hbase.master.balancer.LoadBalancerFactory;
|
import org.apache.hadoop.hbase.master.balancer.LoadBalancerFactory;
|
||||||
import org.apache.hadoop.hbase.master.cleaner.HFileCleaner;
|
import org.apache.hadoop.hbase.master.cleaner.HFileCleaner;
|
||||||
import org.apache.hadoop.hbase.master.cleaner.LogCleaner;
|
import org.apache.hadoop.hbase.master.cleaner.LogCleaner;
|
||||||
import org.apache.hadoop.hbase.master.handler.DisableTableHandler;
|
|
||||||
import org.apache.hadoop.hbase.master.handler.DispatchMergingRegionHandler;
|
import org.apache.hadoop.hbase.master.handler.DispatchMergingRegionHandler;
|
||||||
import org.apache.hadoop.hbase.master.handler.EnableTableHandler;
|
|
||||||
import org.apache.hadoop.hbase.master.handler.TruncateTableHandler;
|
import org.apache.hadoop.hbase.master.handler.TruncateTableHandler;
|
||||||
import org.apache.hadoop.hbase.master.procedure.AddColumnFamilyProcedure;
|
import org.apache.hadoop.hbase.master.procedure.AddColumnFamilyProcedure;
|
||||||
import org.apache.hadoop.hbase.master.procedure.CreateTableProcedure;
|
import org.apache.hadoop.hbase.master.procedure.CreateTableProcedure;
|
||||||
import org.apache.hadoop.hbase.master.procedure.DeleteColumnFamilyProcedure;
|
import org.apache.hadoop.hbase.master.procedure.DeleteColumnFamilyProcedure;
|
||||||
import org.apache.hadoop.hbase.master.procedure.DeleteTableProcedure;
|
import org.apache.hadoop.hbase.master.procedure.DeleteTableProcedure;
|
||||||
|
import org.apache.hadoop.hbase.master.procedure.DisableTableProcedure;
|
||||||
|
import org.apache.hadoop.hbase.master.procedure.EnableTableProcedure;
|
||||||
import org.apache.hadoop.hbase.master.procedure.MasterProcedureConstants;
|
import org.apache.hadoop.hbase.master.procedure.MasterProcedureConstants;
|
||||||
import org.apache.hadoop.hbase.master.procedure.MasterProcedureEnv;
|
import org.apache.hadoop.hbase.master.procedure.MasterProcedureEnv;
|
||||||
import org.apache.hadoop.hbase.master.procedure.ModifyColumnFamilyProcedure;
|
import org.apache.hadoop.hbase.master.procedure.ModifyColumnFamilyProcedure;
|
||||||
@ -1712,11 +1712,24 @@ public class HMaster extends HRegionServer implements MasterServices, Server {
|
|||||||
cpHost.preEnableTable(tableName);
|
cpHost.preEnableTable(tableName);
|
||||||
}
|
}
|
||||||
LOG.info(getClientIdAuditPrefix() + " enable " + tableName);
|
LOG.info(getClientIdAuditPrefix() + " enable " + tableName);
|
||||||
this.service.submit(new EnableTableHandler(this, tableName,
|
|
||||||
assignmentManager, tableLockManager, false).prepare());
|
// Execute the operation asynchronously - client will check the progress of the operation
|
||||||
|
final ProcedurePrepareLatch prepareLatch = ProcedurePrepareLatch.createLatch();
|
||||||
|
long procId =
|
||||||
|
this.procedureExecutor.submitProcedure(new EnableTableProcedure(procedureExecutor
|
||||||
|
.getEnvironment(), tableName, false, prepareLatch));
|
||||||
|
// Before returning to client, we want to make sure that the table is prepared to be
|
||||||
|
// enabled (the table is locked and the table state is set).
|
||||||
|
//
|
||||||
|
// Note: if the procedure throws exception, we will catch it and rethrow.
|
||||||
|
prepareLatch.await();
|
||||||
|
|
||||||
if (cpHost != null) {
|
if (cpHost != null) {
|
||||||
cpHost.postEnableTable(tableName);
|
cpHost.postEnableTable(tableName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: return procId as part of client-side change
|
||||||
|
// return procId;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -1726,11 +1739,25 @@ public class HMaster extends HRegionServer implements MasterServices, Server {
|
|||||||
cpHost.preDisableTable(tableName);
|
cpHost.preDisableTable(tableName);
|
||||||
}
|
}
|
||||||
LOG.info(getClientIdAuditPrefix() + " disable " + tableName);
|
LOG.info(getClientIdAuditPrefix() + " disable " + tableName);
|
||||||
this.service.submit(new DisableTableHandler(this, tableName,
|
|
||||||
assignmentManager, tableLockManager, false).prepare());
|
// Execute the operation asynchronously - client will check the progress of the operation
|
||||||
|
final ProcedurePrepareLatch prepareLatch = ProcedurePrepareLatch.createLatch();
|
||||||
|
// Execute the operation asynchronously - client will check the progress of the operation
|
||||||
|
long procId =
|
||||||
|
this.procedureExecutor.submitProcedure(new DisableTableProcedure(procedureExecutor
|
||||||
|
.getEnvironment(), tableName, false, prepareLatch));
|
||||||
|
// Before returning to client, we want to make sure that the table is prepared to be
|
||||||
|
// enabled (the table is locked and the table state is set).
|
||||||
|
//
|
||||||
|
// Note: if the procedure throws exception, we will catch it and rethrow.
|
||||||
|
prepareLatch.await();
|
||||||
|
|
||||||
if (cpHost != null) {
|
if (cpHost != null) {
|
||||||
cpHost.postDisableTable(tableName);
|
cpHost.postDisableTable(tableName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: return procId as part of client-side change
|
||||||
|
// return procId;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -0,0 +1,562 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.hadoop.hbase.master.procedure;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.security.PrivilegedExceptionAction;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
import org.apache.hadoop.hbase.HRegionInfo;
|
||||||
|
import org.apache.hadoop.hbase.MetaTableAccessor;
|
||||||
|
import org.apache.hadoop.hbase.TableName;
|
||||||
|
import org.apache.hadoop.hbase.TableNotEnabledException;
|
||||||
|
import org.apache.hadoop.hbase.TableNotFoundException;
|
||||||
|
import org.apache.hadoop.hbase.TableStateManager;
|
||||||
|
import org.apache.hadoop.hbase.classification.InterfaceAudience;
|
||||||
|
import org.apache.hadoop.hbase.constraint.ConstraintException;
|
||||||
|
import org.apache.hadoop.hbase.executor.EventType;
|
||||||
|
import org.apache.hadoop.hbase.exceptions.HBaseException;
|
||||||
|
import org.apache.hadoop.hbase.master.AssignmentManager;
|
||||||
|
import org.apache.hadoop.hbase.master.BulkAssigner;
|
||||||
|
import org.apache.hadoop.hbase.master.MasterCoprocessorHost;
|
||||||
|
import org.apache.hadoop.hbase.master.RegionState;
|
||||||
|
import org.apache.hadoop.hbase.master.RegionStates;
|
||||||
|
import org.apache.hadoop.hbase.procedure2.StateMachineProcedure;
|
||||||
|
import org.apache.hadoop.hbase.protobuf.ProtobufUtil;
|
||||||
|
import org.apache.hadoop.hbase.protobuf.generated.MasterProcedureProtos;
|
||||||
|
import org.apache.hadoop.hbase.protobuf.generated.MasterProcedureProtos.DisableTableState;
|
||||||
|
import org.apache.hadoop.hbase.protobuf.generated.ZooKeeperProtos;
|
||||||
|
import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
|
||||||
|
import org.apache.hadoop.security.UserGroupInformation;
|
||||||
|
import org.apache.htrace.Trace;
|
||||||
|
|
||||||
|
@InterfaceAudience.Private
|
||||||
|
public class DisableTableProcedure
|
||||||
|
extends StateMachineProcedure<MasterProcedureEnv, DisableTableState>
|
||||||
|
implements TableProcedureInterface {
|
||||||
|
private static final Log LOG = LogFactory.getLog(DisableTableProcedure.class);
|
||||||
|
|
||||||
|
private final AtomicBoolean aborted = new AtomicBoolean(false);
|
||||||
|
|
||||||
|
// This is for back compatible with 1.0 asynchronized operations.
|
||||||
|
private final ProcedurePrepareLatch syncLatch;
|
||||||
|
|
||||||
|
private TableName tableName;
|
||||||
|
private boolean skipTableStateCheck;
|
||||||
|
private UserGroupInformation user;
|
||||||
|
|
||||||
|
private Boolean traceEnabled = null;
|
||||||
|
|
||||||
|
enum MarkRegionOfflineOpResult {
|
||||||
|
MARK_ALL_REGIONS_OFFLINE_SUCCESSFUL,
|
||||||
|
BULK_ASSIGN_REGIONS_FAILED,
|
||||||
|
MARK_ALL_REGIONS_OFFLINE_INTERRUPTED,
|
||||||
|
}
|
||||||
|
|
||||||
|
public DisableTableProcedure() {
|
||||||
|
syncLatch = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
* @param env MasterProcedureEnv
|
||||||
|
* @param tableName the table to operate on
|
||||||
|
* @param skipTableStateCheck whether to check table state
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
public DisableTableProcedure(
|
||||||
|
final MasterProcedureEnv env,
|
||||||
|
final TableName tableName,
|
||||||
|
final boolean skipTableStateCheck) throws IOException {
|
||||||
|
this(env, tableName, skipTableStateCheck, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
* @param env MasterProcedureEnv
|
||||||
|
* @param tableName the table to operate on
|
||||||
|
* @param skipTableStateCheck whether to check table state
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
public DisableTableProcedure(
|
||||||
|
final MasterProcedureEnv env,
|
||||||
|
final TableName tableName,
|
||||||
|
final boolean skipTableStateCheck,
|
||||||
|
final ProcedurePrepareLatch syncLatch) throws IOException {
|
||||||
|
this.tableName = tableName;
|
||||||
|
this.skipTableStateCheck = skipTableStateCheck;
|
||||||
|
this.user = env.getRequestUser().getUGI();
|
||||||
|
|
||||||
|
// Compatible with 1.0: We use latch to make sure that this procedure implementation is
|
||||||
|
// compatible with 1.0 asynchronized operations. We need to lock the table and check
|
||||||
|
// whether the Disable operation could be performed (table exists and online; table state
|
||||||
|
// is ENABLED). Once it is done, we are good to release the latch and the client can
|
||||||
|
// start asynchronously wait for the operation.
|
||||||
|
//
|
||||||
|
// Note: the member syncLatch could be null if we are in failover or recovery scenario.
|
||||||
|
// This is ok for backward compatible, as 1.0 client would not able to peek at procedure.
|
||||||
|
this.syncLatch = syncLatch;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Flow executeFromState(final MasterProcedureEnv env, final DisableTableState state) {
|
||||||
|
if (isTraceEnabled()) {
|
||||||
|
LOG.trace(this + " execute state=" + state);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
switch (state) {
|
||||||
|
case DISABLE_TABLE_PREPARE:
|
||||||
|
if (prepareDisable(env)) {
|
||||||
|
setNextState(DisableTableState.DISABLE_TABLE_PRE_OPERATION);
|
||||||
|
} else {
|
||||||
|
assert isFailed() : "disable should have an exception here";
|
||||||
|
return Flow.NO_MORE_STATE;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case DISABLE_TABLE_PRE_OPERATION:
|
||||||
|
preDisable(env, state);
|
||||||
|
setNextState(DisableTableState.DISABLE_TABLE_SET_DISABLING_TABLE_STATE);
|
||||||
|
break;
|
||||||
|
case DISABLE_TABLE_SET_DISABLING_TABLE_STATE:
|
||||||
|
setTableStateToDisabling(env, tableName);
|
||||||
|
setNextState(DisableTableState.DISABLE_TABLE_MARK_REGIONS_OFFLINE);
|
||||||
|
break;
|
||||||
|
case DISABLE_TABLE_MARK_REGIONS_OFFLINE:
|
||||||
|
if (markRegionsOffline(env, tableName, true) ==
|
||||||
|
MarkRegionOfflineOpResult.MARK_ALL_REGIONS_OFFLINE_SUCCESSFUL) {
|
||||||
|
setNextState(DisableTableState.DISABLE_TABLE_SET_DISABLED_TABLE_STATE);
|
||||||
|
} else {
|
||||||
|
LOG.trace("Retrying later to disable the missing regions");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case DISABLE_TABLE_SET_DISABLED_TABLE_STATE:
|
||||||
|
setTableStateToDisabled(env, tableName);
|
||||||
|
setNextState(DisableTableState.DISABLE_TABLE_POST_OPERATION);
|
||||||
|
break;
|
||||||
|
case DISABLE_TABLE_POST_OPERATION:
|
||||||
|
postDisable(env, state);
|
||||||
|
return Flow.NO_MORE_STATE;
|
||||||
|
default:
|
||||||
|
throw new UnsupportedOperationException("unhandled state=" + state);
|
||||||
|
}
|
||||||
|
} catch (InterruptedException|HBaseException|IOException e) {
|
||||||
|
LOG.warn("Retriable error trying to disable table=" + tableName + " state=" + state, e);
|
||||||
|
}
|
||||||
|
return Flow.HAS_MORE_STATE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void rollbackState(final MasterProcedureEnv env, final DisableTableState state)
|
||||||
|
throws IOException {
|
||||||
|
if (state == DisableTableState.DISABLE_TABLE_PREPARE) {
|
||||||
|
undoTableStateChange(env);
|
||||||
|
ProcedurePrepareLatch.releaseLatch(syncLatch, this);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The delete doesn't have a rollback. The execution will succeed, at some point.
|
||||||
|
throw new UnsupportedOperationException("unhandled state=" + state);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected DisableTableState getState(final int stateId) {
|
||||||
|
return DisableTableState.valueOf(stateId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int getStateId(final DisableTableState state) {
|
||||||
|
return state.getNumber();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected DisableTableState getInitialState() {
|
||||||
|
return DisableTableState.DISABLE_TABLE_PREPARE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void setNextState(final DisableTableState state) {
|
||||||
|
if (aborted.get()) {
|
||||||
|
setAbortFailure("disable-table", "abort requested");
|
||||||
|
} else {
|
||||||
|
super.setNextState(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean abort(final MasterProcedureEnv env) {
|
||||||
|
aborted.set(true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean acquireLock(final MasterProcedureEnv env) {
|
||||||
|
if (!env.isInitialized()) return false;
|
||||||
|
return env.getProcedureQueue().tryAcquireTableWrite(
|
||||||
|
tableName,
|
||||||
|
EventType.C_M_DISABLE_TABLE.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void releaseLock(final MasterProcedureEnv env) {
|
||||||
|
env.getProcedureQueue().releaseTableWrite(tableName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void serializeStateData(final OutputStream stream) throws IOException {
|
||||||
|
super.serializeStateData(stream);
|
||||||
|
|
||||||
|
MasterProcedureProtos.DisableTableStateData.Builder disableTableMsg =
|
||||||
|
MasterProcedureProtos.DisableTableStateData.newBuilder()
|
||||||
|
.setUserInfo(MasterProcedureUtil.toProtoUserInfo(user))
|
||||||
|
.setTableName(ProtobufUtil.toProtoTableName(tableName))
|
||||||
|
.setSkipTableStateCheck(skipTableStateCheck);
|
||||||
|
|
||||||
|
disableTableMsg.build().writeDelimitedTo(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void deserializeStateData(final InputStream stream) throws IOException {
|
||||||
|
super.deserializeStateData(stream);
|
||||||
|
|
||||||
|
MasterProcedureProtos.DisableTableStateData disableTableMsg =
|
||||||
|
MasterProcedureProtos.DisableTableStateData.parseDelimitedFrom(stream);
|
||||||
|
user = MasterProcedureUtil.toUserInfo(disableTableMsg.getUserInfo());
|
||||||
|
tableName = ProtobufUtil.toTableName(disableTableMsg.getTableName());
|
||||||
|
skipTableStateCheck = disableTableMsg.getSkipTableStateCheck();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void toStringClassDetails(StringBuilder sb) {
|
||||||
|
sb.append(getClass().getSimpleName());
|
||||||
|
sb.append(" (table=");
|
||||||
|
sb.append(tableName);
|
||||||
|
sb.append(") user=");
|
||||||
|
sb.append(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TableName getTableName() {
|
||||||
|
return tableName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TableOperationType getTableOperationType() {
|
||||||
|
return TableOperationType.DISABLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Action before any real action of disabling table. Set the exception in the procedure instead
|
||||||
|
* of throwing it. This approach is to deal with backward compatible with 1.0.
|
||||||
|
* @param env MasterProcedureEnv
|
||||||
|
* @throws HBaseException
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
private boolean prepareDisable(final MasterProcedureEnv env) throws HBaseException, IOException {
|
||||||
|
boolean canTableBeDisabled = true;
|
||||||
|
if (tableName.equals(TableName.META_TABLE_NAME)) {
|
||||||
|
setFailure("master-disable-table", new ConstraintException("Cannot disable catalog table"));
|
||||||
|
canTableBeDisabled = false;
|
||||||
|
} else if (!MetaTableAccessor.tableExists(env.getMasterServices().getConnection(), tableName)) {
|
||||||
|
setFailure("master-disable-table", new TableNotFoundException(tableName));
|
||||||
|
canTableBeDisabled = false;
|
||||||
|
} else if (!skipTableStateCheck) {
|
||||||
|
// There could be multiple client requests trying to disable or enable
|
||||||
|
// the table at the same time. Ensure only the first request is honored
|
||||||
|
// After that, no other requests can be accepted until the table reaches
|
||||||
|
// DISABLED or ENABLED.
|
||||||
|
//
|
||||||
|
// Note: A quick state check should be enough for us to move forward. However, instead of
|
||||||
|
// calling TableStateManager.isTableState() to just check the state, we called
|
||||||
|
// TableStateManager.setTableStateIfInStates() to set the state to DISABLING from ENABLED.
|
||||||
|
// This is because we treat empty state as enabled from 0.92-clusters. See
|
||||||
|
// ZKTableStateManager.setTableStateIfInStates() that has a hack solution to work around
|
||||||
|
// this issue.
|
||||||
|
TableStateManager tsm =
|
||||||
|
env.getMasterServices().getAssignmentManager().getTableStateManager();
|
||||||
|
if (!tsm.setTableStateIfInStates(tableName, ZooKeeperProtos.Table.State.DISABLING,
|
||||||
|
ZooKeeperProtos.Table.State.DISABLING, ZooKeeperProtos.Table.State.ENABLED)) {
|
||||||
|
LOG.info("Table " + tableName + " isn't enabled; skipping disable");
|
||||||
|
setFailure("master-disable-table", new TableNotEnabledException(tableName));
|
||||||
|
canTableBeDisabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We are done the check. Future actions in this procedure could be done asynchronously.
|
||||||
|
ProcedurePrepareLatch.releaseLatch(syncLatch, this);
|
||||||
|
|
||||||
|
return canTableBeDisabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rollback of table state change in prepareDisable()
|
||||||
|
* @param env MasterProcedureEnv
|
||||||
|
*/
|
||||||
|
private void undoTableStateChange(final MasterProcedureEnv env) {
|
||||||
|
if (!skipTableStateCheck) {
|
||||||
|
try {
|
||||||
|
// If the state was changed, undo it.
|
||||||
|
if (env.getMasterServices().getAssignmentManager().getTableStateManager().isTableState(
|
||||||
|
tableName, ZooKeeperProtos.Table.State.DISABLING)) {
|
||||||
|
EnableTableProcedure.setTableStateToEnabled(env, tableName);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
// Ignore exception.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Action before disabling table.
|
||||||
|
* @param env MasterProcedureEnv
|
||||||
|
* @param state the procedure state
|
||||||
|
* @throws IOException
|
||||||
|
* @throws InterruptedException
|
||||||
|
*/
|
||||||
|
protected void preDisable(final MasterProcedureEnv env, final DisableTableState state)
|
||||||
|
throws IOException, InterruptedException {
|
||||||
|
runCoprocessorAction(env, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark table state to Disabling
|
||||||
|
* @param env MasterProcedureEnv
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
protected static void setTableStateToDisabling(
|
||||||
|
final MasterProcedureEnv env,
|
||||||
|
final TableName tableName) throws HBaseException, IOException {
|
||||||
|
// Set table disabling flag up in zk.
|
||||||
|
env.getMasterServices().getAssignmentManager().getTableStateManager().setTableState(
|
||||||
|
tableName,
|
||||||
|
ZooKeeperProtos.Table.State.DISABLING);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark regions of the table offline with retries
|
||||||
|
* @param env MasterProcedureEnv
|
||||||
|
* @param tableName the target table
|
||||||
|
* @param retryRequired whether to retry if the first run failed
|
||||||
|
* @return whether the operation is fully completed or being interrupted.
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
protected static MarkRegionOfflineOpResult markRegionsOffline(
|
||||||
|
final MasterProcedureEnv env,
|
||||||
|
final TableName tableName,
|
||||||
|
final Boolean retryRequired) throws IOException {
|
||||||
|
// Dev consideration: add a config to control max number of retry. For now, it is hard coded.
|
||||||
|
int maxTry = (retryRequired ? 10 : 1);
|
||||||
|
MarkRegionOfflineOpResult operationResult =
|
||||||
|
MarkRegionOfflineOpResult.BULK_ASSIGN_REGIONS_FAILED;
|
||||||
|
do {
|
||||||
|
try {
|
||||||
|
operationResult = markRegionsOffline(env, tableName);
|
||||||
|
if (operationResult == MarkRegionOfflineOpResult.MARK_ALL_REGIONS_OFFLINE_SUCCESSFUL) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
maxTry--;
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOG.warn("Received exception while marking regions online. tries left: " + maxTry, e);
|
||||||
|
maxTry--;
|
||||||
|
if (maxTry > 0) {
|
||||||
|
continue; // we still have some retry left, try again.
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
} while (maxTry > 0);
|
||||||
|
|
||||||
|
if (operationResult != MarkRegionOfflineOpResult.MARK_ALL_REGIONS_OFFLINE_SUCCESSFUL) {
|
||||||
|
LOG.warn("Some or all regions of the Table '" + tableName + "' were still online");
|
||||||
|
}
|
||||||
|
|
||||||
|
return operationResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark regions of the table offline
|
||||||
|
* @param env MasterProcedureEnv
|
||||||
|
* @param tableName the target table
|
||||||
|
* @return whether the operation is fully completed or being interrupted.
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
private static MarkRegionOfflineOpResult markRegionsOffline(
|
||||||
|
final MasterProcedureEnv env,
|
||||||
|
final TableName tableName) throws IOException {
|
||||||
|
// Get list of online regions that are of this table. Regions that are
|
||||||
|
// already closed will not be included in this list; i.e. the returned
|
||||||
|
// list is not ALL regions in a table, its all online regions according
|
||||||
|
// to the in-memory state on this master.
|
||||||
|
MarkRegionOfflineOpResult operationResult =
|
||||||
|
MarkRegionOfflineOpResult.MARK_ALL_REGIONS_OFFLINE_SUCCESSFUL;
|
||||||
|
final List<HRegionInfo> regions =
|
||||||
|
env.getMasterServices().getAssignmentManager().getRegionStates()
|
||||||
|
.getRegionsOfTable(tableName);
|
||||||
|
if (regions.size() > 0) {
|
||||||
|
LOG.info("Offlining " + regions.size() + " regions.");
|
||||||
|
|
||||||
|
BulkDisabler bd = new BulkDisabler(env, tableName, regions);
|
||||||
|
try {
|
||||||
|
if (!bd.bulkAssign()) {
|
||||||
|
operationResult = MarkRegionOfflineOpResult.BULK_ASSIGN_REGIONS_FAILED;
|
||||||
|
}
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
LOG.warn("Disable was interrupted");
|
||||||
|
// Preserve the interrupt.
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
operationResult = MarkRegionOfflineOpResult.MARK_ALL_REGIONS_OFFLINE_INTERRUPTED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return operationResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark table state to Disabled
|
||||||
|
* @param env MasterProcedureEnv
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
protected static void setTableStateToDisabled(
|
||||||
|
final MasterProcedureEnv env,
|
||||||
|
final TableName tableName) throws HBaseException, IOException {
|
||||||
|
// Flip the table to disabled
|
||||||
|
env.getMasterServices().getAssignmentManager().getTableStateManager().setTableState(
|
||||||
|
tableName,
|
||||||
|
ZooKeeperProtos.Table.State.DISABLED);
|
||||||
|
LOG.info("Disabled table, " + tableName + ", is completed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Action after disabling table.
|
||||||
|
* @param env MasterProcedureEnv
|
||||||
|
* @param state the procedure state
|
||||||
|
* @throws IOException
|
||||||
|
* @throws InterruptedException
|
||||||
|
*/
|
||||||
|
protected void postDisable(final MasterProcedureEnv env, final DisableTableState state)
|
||||||
|
throws IOException, InterruptedException {
|
||||||
|
runCoprocessorAction(env, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The procedure could be restarted from a different machine. If the variable is null, we need to
|
||||||
|
* retrieve it.
|
||||||
|
* @return traceEnabled
|
||||||
|
*/
|
||||||
|
private Boolean isTraceEnabled() {
|
||||||
|
if (traceEnabled == null) {
|
||||||
|
traceEnabled = LOG.isTraceEnabled();
|
||||||
|
}
|
||||||
|
return traceEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Coprocessor Action.
|
||||||
|
* @param env MasterProcedureEnv
|
||||||
|
* @param state the procedure state
|
||||||
|
* @throws IOException
|
||||||
|
* @throws InterruptedException
|
||||||
|
*/
|
||||||
|
private void runCoprocessorAction(final MasterProcedureEnv env, final DisableTableState state)
|
||||||
|
throws IOException, InterruptedException {
|
||||||
|
final MasterCoprocessorHost cpHost = env.getMasterCoprocessorHost();
|
||||||
|
if (cpHost != null) {
|
||||||
|
user.doAs(new PrivilegedExceptionAction<Void>() {
|
||||||
|
@Override
|
||||||
|
public Void run() throws Exception {
|
||||||
|
switch (state) {
|
||||||
|
case DISABLE_TABLE_PRE_OPERATION:
|
||||||
|
cpHost.preDisableTableHandler(tableName);
|
||||||
|
break;
|
||||||
|
case DISABLE_TABLE_POST_OPERATION:
|
||||||
|
cpHost.postDisableTableHandler(tableName);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new UnsupportedOperationException(this + " unhandled state=" + state);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run bulk disable.
|
||||||
|
*/
|
||||||
|
private static class BulkDisabler extends BulkAssigner {
|
||||||
|
private final AssignmentManager assignmentManager;
|
||||||
|
private final List<HRegionInfo> regions;
|
||||||
|
private final TableName tableName;
|
||||||
|
private final int waitingTimeForEvents;
|
||||||
|
|
||||||
|
public BulkDisabler(final MasterProcedureEnv env, final TableName tableName,
|
||||||
|
final List<HRegionInfo> regions) {
|
||||||
|
super(env.getMasterServices());
|
||||||
|
this.assignmentManager = env.getMasterServices().getAssignmentManager();
|
||||||
|
this.tableName = tableName;
|
||||||
|
this.regions = regions;
|
||||||
|
this.waitingTimeForEvents =
|
||||||
|
env.getMasterServices().getConfiguration()
|
||||||
|
.getInt("hbase.master.event.waiting.time", 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void populatePool(ExecutorService pool) {
|
||||||
|
RegionStates regionStates = assignmentManager.getRegionStates();
|
||||||
|
for (final HRegionInfo region : regions) {
|
||||||
|
if (regionStates.isRegionInTransition(region)
|
||||||
|
&& !regionStates.isRegionInState(region, RegionState.State.FAILED_CLOSE)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
pool.execute(Trace.wrap("DisableTableHandler.BulkDisabler", new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
assignmentManager.unassign(region);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean waitUntilDone(long timeout) throws InterruptedException {
|
||||||
|
long startTime = EnvironmentEdgeManager.currentTime();
|
||||||
|
long remaining = timeout;
|
||||||
|
List<HRegionInfo> regions = null;
|
||||||
|
long lastLogTime = startTime;
|
||||||
|
while (!server.isStopped() && remaining > 0) {
|
||||||
|
Thread.sleep(waitingTimeForEvents);
|
||||||
|
regions = assignmentManager.getRegionStates().getRegionsOfTable(tableName);
|
||||||
|
long now = EnvironmentEdgeManager.currentTime();
|
||||||
|
// Don't log more than once every ten seconds. Its obnoxious. And only log table regions
|
||||||
|
// if we are waiting a while for them to go down...
|
||||||
|
if (LOG.isDebugEnabled() && ((now - lastLogTime) > 10000)) {
|
||||||
|
lastLogTime = now;
|
||||||
|
LOG.debug("Disable waiting until done; " + remaining + " ms remaining; " + regions);
|
||||||
|
}
|
||||||
|
if (regions.isEmpty()) break;
|
||||||
|
remaining = timeout - (now - startTime);
|
||||||
|
}
|
||||||
|
return regions != null && regions.isEmpty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,587 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.hadoop.hbase.master.procedure;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.security.PrivilegedExceptionAction;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
import org.apache.hadoop.hbase.HRegionInfo;
|
||||||
|
import org.apache.hadoop.hbase.MetaTableAccessor;
|
||||||
|
import org.apache.hadoop.hbase.ServerName;
|
||||||
|
import org.apache.hadoop.hbase.TableName;
|
||||||
|
import org.apache.hadoop.hbase.TableNotDisabledException;
|
||||||
|
import org.apache.hadoop.hbase.TableNotFoundException;
|
||||||
|
import org.apache.hadoop.hbase.TableStateManager;
|
||||||
|
import org.apache.hadoop.hbase.classification.InterfaceAudience;
|
||||||
|
import org.apache.hadoop.hbase.executor.EventType;
|
||||||
|
import org.apache.hadoop.hbase.exceptions.HBaseException;
|
||||||
|
import org.apache.hadoop.hbase.master.AssignmentManager;
|
||||||
|
import org.apache.hadoop.hbase.master.BulkAssigner;
|
||||||
|
import org.apache.hadoop.hbase.master.GeneralBulkAssigner;
|
||||||
|
import org.apache.hadoop.hbase.master.MasterCoprocessorHost;
|
||||||
|
import org.apache.hadoop.hbase.master.MasterServices;
|
||||||
|
import org.apache.hadoop.hbase.master.RegionStates;
|
||||||
|
import org.apache.hadoop.hbase.master.ServerManager;
|
||||||
|
import org.apache.hadoop.hbase.procedure2.StateMachineProcedure;
|
||||||
|
import org.apache.hadoop.hbase.protobuf.ProtobufUtil;
|
||||||
|
import org.apache.hadoop.hbase.protobuf.generated.MasterProcedureProtos;
|
||||||
|
import org.apache.hadoop.hbase.protobuf.generated.MasterProcedureProtos.EnableTableState;
|
||||||
|
import org.apache.hadoop.hbase.protobuf.generated.ZooKeeperProtos;
|
||||||
|
import org.apache.hadoop.hbase.util.Pair;
|
||||||
|
import org.apache.hadoop.hbase.zookeeper.MetaTableLocator;
|
||||||
|
import org.apache.hadoop.security.UserGroupInformation;
|
||||||
|
|
||||||
|
@InterfaceAudience.Private
|
||||||
|
public class EnableTableProcedure
|
||||||
|
extends StateMachineProcedure<MasterProcedureEnv, EnableTableState>
|
||||||
|
implements TableProcedureInterface {
|
||||||
|
private static final Log LOG = LogFactory.getLog(EnableTableProcedure.class);
|
||||||
|
|
||||||
|
private final AtomicBoolean aborted = new AtomicBoolean(false);
|
||||||
|
|
||||||
|
// This is for back compatible with 1.0 asynchronized operations.
|
||||||
|
private final ProcedurePrepareLatch syncLatch;
|
||||||
|
|
||||||
|
private TableName tableName;
|
||||||
|
private boolean skipTableStateCheck;
|
||||||
|
private UserGroupInformation user;
|
||||||
|
|
||||||
|
private Boolean traceEnabled = null;
|
||||||
|
|
||||||
|
public EnableTableProcedure() {
|
||||||
|
syncLatch = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
* @param env MasterProcedureEnv
|
||||||
|
* @param tableName the table to operate on
|
||||||
|
* @param skipTableStateCheck whether to check table state
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
public EnableTableProcedure(
|
||||||
|
final MasterProcedureEnv env,
|
||||||
|
final TableName tableName,
|
||||||
|
final boolean skipTableStateCheck) throws IOException {
|
||||||
|
this(env, tableName, skipTableStateCheck, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
* @param env MasterProcedureEnv
|
||||||
|
* @throws IOException
|
||||||
|
* @param tableName the table to operate on
|
||||||
|
* @param skipTableStateCheck whether to check table state
|
||||||
|
*/
|
||||||
|
public EnableTableProcedure(
|
||||||
|
final MasterProcedureEnv env,
|
||||||
|
final TableName tableName,
|
||||||
|
final boolean skipTableStateCheck,
|
||||||
|
final ProcedurePrepareLatch syncLatch) throws IOException {
|
||||||
|
this.tableName = tableName;
|
||||||
|
this.skipTableStateCheck = skipTableStateCheck;
|
||||||
|
this.user = env.getRequestUser().getUGI();
|
||||||
|
|
||||||
|
// Compatible with 1.0: We use latch to make sure that this procedure implementation is
|
||||||
|
// compatible with 1.0 asynchronized operations. We need to lock the table and check
|
||||||
|
// whether the Enable operation could be performed (table exists and offline; table state
|
||||||
|
// is DISABLED). Once it is done, we are good to release the latch and the client can
|
||||||
|
// start asynchronously wait for the operation.
|
||||||
|
//
|
||||||
|
// Note: the member syncLatch could be null if we are in failover or recovery scenario.
|
||||||
|
// This is ok for backward compatible, as 1.0 client would not able to peek at procedure.
|
||||||
|
this.syncLatch = syncLatch;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Flow executeFromState(final MasterProcedureEnv env, final EnableTableState state) {
|
||||||
|
if (isTraceEnabled()) {
|
||||||
|
LOG.trace(this + " execute state=" + state);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
switch (state) {
|
||||||
|
case ENABLE_TABLE_PREPARE:
|
||||||
|
if (prepareEnable(env)) {
|
||||||
|
setNextState(EnableTableState.ENABLE_TABLE_PRE_OPERATION);
|
||||||
|
} else {
|
||||||
|
assert isFailed() : "enable should have an exception here";
|
||||||
|
return Flow.NO_MORE_STATE;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ENABLE_TABLE_PRE_OPERATION:
|
||||||
|
preEnable(env, state);
|
||||||
|
setNextState(EnableTableState.ENABLE_TABLE_SET_ENABLING_TABLE_STATE);
|
||||||
|
break;
|
||||||
|
case ENABLE_TABLE_SET_ENABLING_TABLE_STATE:
|
||||||
|
setTableStateToEnabling(env, tableName);
|
||||||
|
setNextState(EnableTableState.ENABLE_TABLE_MARK_REGIONS_ONLINE);
|
||||||
|
break;
|
||||||
|
case ENABLE_TABLE_MARK_REGIONS_ONLINE:
|
||||||
|
markRegionsOnline(env, tableName, true);
|
||||||
|
setNextState(EnableTableState.ENABLE_TABLE_SET_ENABLED_TABLE_STATE);
|
||||||
|
break;
|
||||||
|
case ENABLE_TABLE_SET_ENABLED_TABLE_STATE:
|
||||||
|
setTableStateToEnabled(env, tableName);
|
||||||
|
setNextState(EnableTableState.ENABLE_TABLE_POST_OPERATION);
|
||||||
|
break;
|
||||||
|
case ENABLE_TABLE_POST_OPERATION:
|
||||||
|
postEnable(env, state);
|
||||||
|
return Flow.NO_MORE_STATE;
|
||||||
|
default:
|
||||||
|
throw new UnsupportedOperationException("unhandled state=" + state);
|
||||||
|
}
|
||||||
|
} catch (InterruptedException|HBaseException|IOException e) {
|
||||||
|
LOG.error("Error trying to enable table=" + tableName + " state=" + state, e);
|
||||||
|
setFailure("master-enable-table", e);
|
||||||
|
}
|
||||||
|
return Flow.HAS_MORE_STATE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void rollbackState(final MasterProcedureEnv env, final EnableTableState state)
|
||||||
|
throws IOException {
|
||||||
|
if (isTraceEnabled()) {
|
||||||
|
LOG.trace(this + " rollback state=" + state);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
switch (state) {
|
||||||
|
case ENABLE_TABLE_POST_OPERATION:
|
||||||
|
// TODO-MAYBE: call the coprocessor event to undo (eg. DisableTableProcedure.preDisable())?
|
||||||
|
break;
|
||||||
|
case ENABLE_TABLE_SET_ENABLED_TABLE_STATE:
|
||||||
|
DisableTableProcedure.setTableStateToDisabling(env, tableName);
|
||||||
|
break;
|
||||||
|
case ENABLE_TABLE_MARK_REGIONS_ONLINE:
|
||||||
|
markRegionsOfflineDuringRecovery(env);
|
||||||
|
break;
|
||||||
|
case ENABLE_TABLE_SET_ENABLING_TABLE_STATE:
|
||||||
|
DisableTableProcedure.setTableStateToDisabled(env, tableName);
|
||||||
|
break;
|
||||||
|
case ENABLE_TABLE_PRE_OPERATION:
|
||||||
|
// TODO-MAYBE: call the coprocessor event to undo (eg. DisableTableProcedure.postDisable())?
|
||||||
|
break;
|
||||||
|
case ENABLE_TABLE_PREPARE:
|
||||||
|
// Nothing to undo for this state.
|
||||||
|
// We do need to count down the latch count so that we don't stuck.
|
||||||
|
ProcedurePrepareLatch.releaseLatch(syncLatch, this);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new UnsupportedOperationException("unhandled state=" + state);
|
||||||
|
}
|
||||||
|
} catch (HBaseException e) {
|
||||||
|
LOG.warn("Failed enable table rollback attempt step=" + state + " table=" + tableName, e);
|
||||||
|
throw new IOException(e);
|
||||||
|
} catch (IOException e) {
|
||||||
|
// This will be retried. Unless there is a bug in the code,
|
||||||
|
// this should be just a "temporary error" (e.g. network down)
|
||||||
|
LOG.warn("Failed enable table rollback attempt step=" + state + " table=" + tableName, e);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected EnableTableState getState(final int stateId) {
|
||||||
|
return EnableTableState.valueOf(stateId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int getStateId(final EnableTableState state) {
|
||||||
|
return state.getNumber();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected EnableTableState getInitialState() {
|
||||||
|
return EnableTableState.ENABLE_TABLE_PREPARE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void setNextState(final EnableTableState state) {
|
||||||
|
if (aborted.get()) {
|
||||||
|
setAbortFailure("Enable-table", "abort requested");
|
||||||
|
} else {
|
||||||
|
super.setNextState(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean abort(final MasterProcedureEnv env) {
|
||||||
|
aborted.set(true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean acquireLock(final MasterProcedureEnv env) {
|
||||||
|
if (!env.isInitialized()) return false;
|
||||||
|
return env.getProcedureQueue().tryAcquireTableWrite(
|
||||||
|
tableName,
|
||||||
|
EventType.C_M_ENABLE_TABLE.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void releaseLock(final MasterProcedureEnv env) {
|
||||||
|
env.getProcedureQueue().releaseTableWrite(tableName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void serializeStateData(final OutputStream stream) throws IOException {
|
||||||
|
super.serializeStateData(stream);
|
||||||
|
|
||||||
|
MasterProcedureProtos.EnableTableStateData.Builder enableTableMsg =
|
||||||
|
MasterProcedureProtos.EnableTableStateData.newBuilder()
|
||||||
|
.setUserInfo(MasterProcedureUtil.toProtoUserInfo(user))
|
||||||
|
.setTableName(ProtobufUtil.toProtoTableName(tableName))
|
||||||
|
.setSkipTableStateCheck(skipTableStateCheck);
|
||||||
|
|
||||||
|
enableTableMsg.build().writeDelimitedTo(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void deserializeStateData(final InputStream stream) throws IOException {
|
||||||
|
super.deserializeStateData(stream);
|
||||||
|
|
||||||
|
MasterProcedureProtos.EnableTableStateData enableTableMsg =
|
||||||
|
MasterProcedureProtos.EnableTableStateData.parseDelimitedFrom(stream);
|
||||||
|
user = MasterProcedureUtil.toUserInfo(enableTableMsg.getUserInfo());
|
||||||
|
tableName = ProtobufUtil.toTableName(enableTableMsg.getTableName());
|
||||||
|
skipTableStateCheck = enableTableMsg.getSkipTableStateCheck();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void toStringClassDetails(StringBuilder sb) {
|
||||||
|
sb.append(getClass().getSimpleName());
|
||||||
|
sb.append(" (table=");
|
||||||
|
sb.append(tableName);
|
||||||
|
sb.append(") user=");
|
||||||
|
sb.append(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TableName getTableName() {
|
||||||
|
return tableName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TableOperationType getTableOperationType() {
|
||||||
|
return TableOperationType.ENABLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Action before any real action of enabling table. Set the exception in the procedure instead
|
||||||
|
* of throwing it. This approach is to deal with backward compatible with 1.0.
|
||||||
|
* @param env MasterProcedureEnv
|
||||||
|
* @return whether the table passes the necessary checks
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
private boolean prepareEnable(final MasterProcedureEnv env) throws IOException {
|
||||||
|
boolean canTableBeEnabled = true;
|
||||||
|
|
||||||
|
// Check whether table exists
|
||||||
|
if (!MetaTableAccessor.tableExists(env.getMasterServices().getConnection(), tableName)) {
|
||||||
|
setFailure("master-enable-table", new TableNotFoundException(tableName));
|
||||||
|
canTableBeEnabled = false;
|
||||||
|
} else if (!skipTableStateCheck) {
|
||||||
|
// There could be multiple client requests trying to disable or enable
|
||||||
|
// the table at the same time. Ensure only the first request is honored
|
||||||
|
// After that, no other requests can be accepted until the table reaches
|
||||||
|
// DISABLED or ENABLED.
|
||||||
|
//
|
||||||
|
// Note: in 1.0 release, we called TableStateManager.setTableStateIfInStates() to set
|
||||||
|
// the state to ENABLING from DISABLED. The implementation was done before table lock
|
||||||
|
// was implemented. With table lock, there is no need to set the state here (it will
|
||||||
|
// set the state later on). A quick state check should be enough for us to move forward.
|
||||||
|
TableStateManager tsm = env.getMasterServices().getAssignmentManager().getTableStateManager();
|
||||||
|
if (!tsm.isTableState(tableName, ZooKeeperProtos.Table.State.DISABLED)) {
|
||||||
|
LOG.info("Table " + tableName + " isn't disabled; skipping enable");
|
||||||
|
setFailure("master-enable-table", new TableNotDisabledException(this.tableName));
|
||||||
|
canTableBeEnabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We are done the check. Future actions in this procedure could be done asynchronously.
|
||||||
|
ProcedurePrepareLatch.releaseLatch(syncLatch, this);
|
||||||
|
|
||||||
|
return canTableBeEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Action before enabling table.
|
||||||
|
* @param env MasterProcedureEnv
|
||||||
|
* @param state the procedure state
|
||||||
|
* @throws IOException
|
||||||
|
* @throws InterruptedException
|
||||||
|
*/
|
||||||
|
private void preEnable(final MasterProcedureEnv env, final EnableTableState state)
|
||||||
|
throws IOException, InterruptedException {
|
||||||
|
runCoprocessorAction(env, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark table state to Enabling
|
||||||
|
* @param env MasterProcedureEnv
|
||||||
|
* @param tableName the target table
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
protected static void setTableStateToEnabling(
|
||||||
|
final MasterProcedureEnv env,
|
||||||
|
final TableName tableName) throws HBaseException, IOException {
|
||||||
|
// Set table disabling flag up in zk.
|
||||||
|
LOG.info("Attempting to enable the table " + tableName);
|
||||||
|
env.getMasterServices().getAssignmentManager().getTableStateManager().setTableState(
|
||||||
|
tableName,
|
||||||
|
ZooKeeperProtos.Table.State.ENABLING);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark offline regions of the table online with retry
|
||||||
|
* @param env MasterProcedureEnv
|
||||||
|
* @param tableName the target table
|
||||||
|
* @param retryRequired whether to retry if the first run failed
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
protected static void markRegionsOnline(
|
||||||
|
final MasterProcedureEnv env,
|
||||||
|
final TableName tableName,
|
||||||
|
final Boolean retryRequired) throws IOException {
|
||||||
|
// This is best effort approach to make all regions of a table online. If we fail to do
|
||||||
|
// that, it is ok that the table has some offline regions; user can fix it manually.
|
||||||
|
|
||||||
|
// Dev consideration: add a config to control max number of retry. For now, it is hard coded.
|
||||||
|
int maxTry = (retryRequired ? 10 : 1);
|
||||||
|
boolean done = false;
|
||||||
|
|
||||||
|
do {
|
||||||
|
try {
|
||||||
|
done = markRegionsOnline(env, tableName);
|
||||||
|
if (done) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
maxTry--;
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOG.warn("Received exception while marking regions online. tries left: " + maxTry, e);
|
||||||
|
maxTry--;
|
||||||
|
if (maxTry > 0) {
|
||||||
|
continue; // we still have some retry left, try again.
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
} while (maxTry > 0);
|
||||||
|
|
||||||
|
if (!done) {
|
||||||
|
LOG.warn("Some or all regions of the Table '" + tableName + "' were offline");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark offline regions of the table online
|
||||||
|
* @param env MasterProcedureEnv
|
||||||
|
* @param tableName the target table
|
||||||
|
* @return whether the operation is fully completed or being interrupted.
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
private static boolean markRegionsOnline(final MasterProcedureEnv env, final TableName tableName)
|
||||||
|
throws IOException {
|
||||||
|
final AssignmentManager assignmentManager = env.getMasterServices().getAssignmentManager();
|
||||||
|
final MasterServices masterServices = env.getMasterServices();
|
||||||
|
final ServerManager serverManager = masterServices.getServerManager();
|
||||||
|
boolean done = false;
|
||||||
|
// Get the regions of this table. We're done when all listed
|
||||||
|
// tables are onlined.
|
||||||
|
List<Pair<HRegionInfo, ServerName>> tableRegionsAndLocations;
|
||||||
|
|
||||||
|
if (TableName.META_TABLE_NAME.equals(tableName)) {
|
||||||
|
tableRegionsAndLocations =
|
||||||
|
new MetaTableLocator().getMetaRegionsAndLocations(masterServices.getZooKeeper());
|
||||||
|
} else {
|
||||||
|
tableRegionsAndLocations =
|
||||||
|
MetaTableAccessor.getTableRegionsAndLocations(
|
||||||
|
masterServices.getZooKeeper(), masterServices.getConnection(), tableName, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
int countOfRegionsInTable = tableRegionsAndLocations.size();
|
||||||
|
Map<HRegionInfo, ServerName> regionsToAssign =
|
||||||
|
regionsToAssignWithServerName(env, tableRegionsAndLocations);
|
||||||
|
|
||||||
|
// need to potentially create some regions for the replicas
|
||||||
|
List<HRegionInfo> unrecordedReplicas =
|
||||||
|
AssignmentManager.replicaRegionsNotRecordedInMeta(new HashSet<HRegionInfo>(
|
||||||
|
regionsToAssign.keySet()), masterServices);
|
||||||
|
Map<ServerName, List<HRegionInfo>> srvToUnassignedRegs =
|
||||||
|
assignmentManager.getBalancer().roundRobinAssignment(unrecordedReplicas,
|
||||||
|
serverManager.getOnlineServersList());
|
||||||
|
if (srvToUnassignedRegs != null) {
|
||||||
|
for (Map.Entry<ServerName, List<HRegionInfo>> entry : srvToUnassignedRegs.entrySet()) {
|
||||||
|
for (HRegionInfo h : entry.getValue()) {
|
||||||
|
regionsToAssign.put(h, entry.getKey());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int offlineRegionsCount = regionsToAssign.size();
|
||||||
|
|
||||||
|
LOG.info("Table '" + tableName + "' has " + countOfRegionsInTable + " regions, of which "
|
||||||
|
+ offlineRegionsCount + " are offline.");
|
||||||
|
if (offlineRegionsCount == 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<ServerName> onlineServers = serverManager.createDestinationServersList();
|
||||||
|
Map<ServerName, List<HRegionInfo>> bulkPlan =
|
||||||
|
env.getMasterServices().getAssignmentManager().getBalancer()
|
||||||
|
.retainAssignment(regionsToAssign, onlineServers);
|
||||||
|
if (bulkPlan != null) {
|
||||||
|
LOG.info("Bulk assigning " + offlineRegionsCount + " region(s) across " + bulkPlan.size()
|
||||||
|
+ " server(s), retainAssignment=true");
|
||||||
|
|
||||||
|
BulkAssigner ba = new GeneralBulkAssigner(masterServices, bulkPlan, assignmentManager, true);
|
||||||
|
try {
|
||||||
|
if (ba.bulkAssign()) {
|
||||||
|
done = true;
|
||||||
|
}
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
LOG.warn("Enable operation was interrupted when enabling table '" + tableName + "'");
|
||||||
|
// Preserve the interrupt.
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LOG.info("Balancer was unable to find suitable servers for table " + tableName
|
||||||
|
+ ", leaving unassigned");
|
||||||
|
}
|
||||||
|
return done;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark regions of the table offline during recovery
|
||||||
|
* @param env MasterProcedureEnv
|
||||||
|
*/
|
||||||
|
private void markRegionsOfflineDuringRecovery(final MasterProcedureEnv env) {
|
||||||
|
try {
|
||||||
|
// This is a best effort attempt. We will move on even it does not succeed. We will retry
|
||||||
|
// several times until we giving up.
|
||||||
|
DisableTableProcedure.markRegionsOffline(env, tableName, true);
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOG.debug("Failed to offline all regions of table " + tableName + ". Ignoring", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark table state to Enabled
|
||||||
|
* @param env MasterProcedureEnv
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
protected static void setTableStateToEnabled(
|
||||||
|
final MasterProcedureEnv env,
|
||||||
|
final TableName tableName) throws HBaseException, IOException {
|
||||||
|
// Flip the table to Enabled
|
||||||
|
env.getMasterServices().getAssignmentManager().getTableStateManager().setTableState(
|
||||||
|
tableName,
|
||||||
|
ZooKeeperProtos.Table.State.ENABLED);
|
||||||
|
LOG.info("Table '" + tableName + "' was successfully enabled.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Action after enabling table.
|
||||||
|
* @param env MasterProcedureEnv
|
||||||
|
* @param state the procedure state
|
||||||
|
* @throws IOException
|
||||||
|
* @throws InterruptedException
|
||||||
|
*/
|
||||||
|
private void postEnable(final MasterProcedureEnv env, final EnableTableState state)
|
||||||
|
throws IOException, InterruptedException {
|
||||||
|
runCoprocessorAction(env, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The procedure could be restarted from a different machine. If the variable is null, we need to
|
||||||
|
* retrieve it.
|
||||||
|
* @return traceEnabled
|
||||||
|
*/
|
||||||
|
private Boolean isTraceEnabled() {
|
||||||
|
if (traceEnabled == null) {
|
||||||
|
traceEnabled = LOG.isTraceEnabled();
|
||||||
|
}
|
||||||
|
return traceEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param regionsInMeta
|
||||||
|
* @return List of regions neither in transition nor assigned.
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
private static Map<HRegionInfo, ServerName> regionsToAssignWithServerName(
|
||||||
|
final MasterProcedureEnv env,
|
||||||
|
final List<Pair<HRegionInfo, ServerName>> regionsInMeta) throws IOException {
|
||||||
|
Map<HRegionInfo, ServerName> regionsToAssign =
|
||||||
|
new HashMap<HRegionInfo, ServerName>(regionsInMeta.size());
|
||||||
|
RegionStates regionStates = env.getMasterServices().getAssignmentManager().getRegionStates();
|
||||||
|
for (Pair<HRegionInfo, ServerName> regionLocation : regionsInMeta) {
|
||||||
|
HRegionInfo hri = regionLocation.getFirst();
|
||||||
|
ServerName sn = regionLocation.getSecond();
|
||||||
|
if (regionStates.isRegionOffline(hri)) {
|
||||||
|
regionsToAssign.put(hri, sn);
|
||||||
|
} else {
|
||||||
|
if (LOG.isDebugEnabled()) {
|
||||||
|
LOG.debug("Skipping assign for the region " + hri + " during enable table "
|
||||||
|
+ hri.getTable() + " because its already in tranition or assigned.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return regionsToAssign;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Coprocessor Action.
|
||||||
|
* @param env MasterProcedureEnv
|
||||||
|
* @param state the procedure state
|
||||||
|
* @throws IOException
|
||||||
|
* @throws InterruptedException
|
||||||
|
*/
|
||||||
|
private void runCoprocessorAction(final MasterProcedureEnv env, final EnableTableState state)
|
||||||
|
throws IOException, InterruptedException {
|
||||||
|
final MasterCoprocessorHost cpHost = env.getMasterCoprocessorHost();
|
||||||
|
if (cpHost != null) {
|
||||||
|
user.doAs(new PrivilegedExceptionAction<Void>() {
|
||||||
|
@Override
|
||||||
|
public Void run() throws Exception {
|
||||||
|
switch (state) {
|
||||||
|
case ENABLE_TABLE_PRE_OPERATION:
|
||||||
|
cpHost.preEnableTableHandler(getTableName());
|
||||||
|
break;
|
||||||
|
case ENABLE_TABLE_POST_OPERATION:
|
||||||
|
cpHost.postEnableTableHandler(getTableName());
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new UnsupportedOperationException(this + " unhandled state=" + state);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -18,9 +18,9 @@
|
|||||||
|
|
||||||
package org.apache.hadoop.hbase.master.procedure;
|
package org.apache.hadoop.hbase.master.procedure;
|
||||||
|
|
||||||
|
import org.apache.hadoop.hbase.TableName;
|
||||||
import org.apache.hadoop.hbase.classification.InterfaceAudience;
|
import org.apache.hadoop.hbase.classification.InterfaceAudience;
|
||||||
import org.apache.hadoop.hbase.classification.InterfaceStability;
|
import org.apache.hadoop.hbase.classification.InterfaceStability;
|
||||||
import org.apache.hadoop.hbase.TableName;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Procedures that operates on a specific Table (e.g. create, delete, snapshot, ...)
|
* Procedures that operates on a specific Table (e.g. create, delete, snapshot, ...)
|
||||||
@ -29,7 +29,9 @@ import org.apache.hadoop.hbase.TableName;
|
|||||||
@InterfaceAudience.Private
|
@InterfaceAudience.Private
|
||||||
@InterfaceStability.Evolving
|
@InterfaceStability.Evolving
|
||||||
public interface TableProcedureInterface {
|
public interface TableProcedureInterface {
|
||||||
public enum TableOperationType { CREATE, DELETE, EDIT, READ };
|
public enum TableOperationType {
|
||||||
|
CREATE, DELETE, DISABLE, EDIT, ENABLE, READ,
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the name of the table the procedure is operating on
|
* @return the name of the table the procedure is operating on
|
||||||
|
@ -34,6 +34,7 @@ import org.apache.hadoop.hbase.MetaTableAccessor;
|
|||||||
import org.apache.hadoop.hbase.RegionLocations;
|
import org.apache.hadoop.hbase.RegionLocations;
|
||||||
import org.apache.hadoop.hbase.ServerName;
|
import org.apache.hadoop.hbase.ServerName;
|
||||||
import org.apache.hadoop.hbase.TableName;
|
import org.apache.hadoop.hbase.TableName;
|
||||||
|
import org.apache.hadoop.hbase.TableStateManager;
|
||||||
import org.apache.hadoop.hbase.client.MetaScanner;
|
import org.apache.hadoop.hbase.client.MetaScanner;
|
||||||
import org.apache.hadoop.hbase.client.MetaScanner.MetaScannerVisitor;
|
import org.apache.hadoop.hbase.client.MetaScanner.MetaScannerVisitor;
|
||||||
import org.apache.hadoop.hbase.client.MetaScanner.MetaScannerVisitorBase;
|
import org.apache.hadoop.hbase.client.MetaScanner.MetaScannerVisitorBase;
|
||||||
@ -41,6 +42,7 @@ import org.apache.hadoop.hbase.client.Result;
|
|||||||
import org.apache.hadoop.hbase.master.HMaster;
|
import org.apache.hadoop.hbase.master.HMaster;
|
||||||
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.protobuf.generated.ZooKeeperProtos;
|
||||||
import org.apache.hadoop.hbase.util.ModifyRegionUtils;
|
import org.apache.hadoop.hbase.util.ModifyRegionUtils;
|
||||||
import org.apache.hadoop.hbase.util.Bytes;
|
import org.apache.hadoop.hbase.util.Bytes;
|
||||||
import org.apache.hadoop.hbase.util.FSUtils;
|
import org.apache.hadoop.hbase.util.FSUtils;
|
||||||
@ -172,6 +174,18 @@ public class MasterProcedureTestingUtility {
|
|||||||
return actualRegCount.get();
|
return actualRegCount.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void validateTableIsEnabled(final HMaster master, final TableName tableName)
|
||||||
|
throws IOException {
|
||||||
|
TableStateManager tsm = master.getAssignmentManager().getTableStateManager();
|
||||||
|
assertTrue(tsm.isTableState(tableName, ZooKeeperProtos.Table.State.ENABLED));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void validateTableIsDisabled(final HMaster master, final TableName tableName)
|
||||||
|
throws IOException {
|
||||||
|
TableStateManager tsm = master.getAssignmentManager().getTableStateManager();
|
||||||
|
assertTrue(tsm.isTableState(tableName, ZooKeeperProtos.Table.State.DISABLED));
|
||||||
|
}
|
||||||
|
|
||||||
public static <TState> void testRecoveryAndDoubleExecution(
|
public static <TState> void testRecoveryAndDoubleExecution(
|
||||||
final ProcedureExecutor<MasterProcedureEnv> procExec, final long procId,
|
final ProcedureExecutor<MasterProcedureEnv> procExec, final long procId,
|
||||||
final int numSteps, final TState[] states) throws Exception {
|
final int numSteps, final TState[] states) throws Exception {
|
||||||
|
@ -0,0 +1,181 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.hadoop.hbase.master.procedure;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
import org.apache.hadoop.conf.Configuration;
|
||||||
|
import org.apache.hadoop.hbase.HBaseTestingUtility;
|
||||||
|
import org.apache.hadoop.hbase.HTableDescriptor;
|
||||||
|
import org.apache.hadoop.hbase.TableName;
|
||||||
|
import org.apache.hadoop.hbase.TableNotEnabledException;
|
||||||
|
import org.apache.hadoop.hbase.procedure2.ProcedureExecutor;
|
||||||
|
import org.apache.hadoop.hbase.procedure2.ProcedureResult;
|
||||||
|
import org.apache.hadoop.hbase.procedure2.ProcedureTestingUtility;
|
||||||
|
import org.apache.hadoop.hbase.protobuf.generated.MasterProcedureProtos.DisableTableState;
|
||||||
|
import org.apache.hadoop.hbase.testclassification.MediumTests;
|
||||||
|
import org.apache.hadoop.hbase.util.Bytes;
|
||||||
|
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.AfterClass;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.BeforeClass;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.experimental.categories.Category;
|
||||||
|
|
||||||
|
@Category(MediumTests.class)
|
||||||
|
public class TestDisableTableProcedure {
|
||||||
|
private static final Log LOG = LogFactory.getLog(TestDisableTableProcedure.class);
|
||||||
|
|
||||||
|
protected static final HBaseTestingUtility UTIL = new HBaseTestingUtility();
|
||||||
|
|
||||||
|
private static void setupConf(Configuration conf) {
|
||||||
|
conf.setInt(MasterProcedureConstants.MASTER_PROCEDURE_THREADS, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@BeforeClass
|
||||||
|
public static void setupCluster() throws Exception {
|
||||||
|
setupConf(UTIL.getConfiguration());
|
||||||
|
UTIL.startMiniCluster(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterClass
|
||||||
|
public static void cleanupTest() throws Exception {
|
||||||
|
try {
|
||||||
|
UTIL.shutdownMiniCluster();
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOG.warn("failure shutting down cluster", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setup() throws Exception {
|
||||||
|
ProcedureTestingUtility.setKillAndToggleBeforeStoreUpdate(getMasterProcedureExecutor(), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void tearDown() throws Exception {
|
||||||
|
ProcedureTestingUtility.setKillAndToggleBeforeStoreUpdate(getMasterProcedureExecutor(), false);
|
||||||
|
for (HTableDescriptor htd: UTIL.getHBaseAdmin().listTables()) {
|
||||||
|
LOG.info("Tear down, remove table=" + htd.getTableName());
|
||||||
|
UTIL.deleteTable(htd.getTableName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(timeout = 60000)
|
||||||
|
public void testDisableTable() throws Exception {
|
||||||
|
final TableName tableName = TableName.valueOf("testDisableTable");
|
||||||
|
final ProcedureExecutor<MasterProcedureEnv> procExec = getMasterProcedureExecutor();
|
||||||
|
|
||||||
|
MasterProcedureTestingUtility.createTable(procExec, tableName, null, "f1", "f2");
|
||||||
|
|
||||||
|
// Disable the table
|
||||||
|
long procId = procExec.submitProcedure(
|
||||||
|
new DisableTableProcedure(procExec.getEnvironment(), tableName, false));
|
||||||
|
// Wait the completion
|
||||||
|
ProcedureTestingUtility.waitProcedure(procExec, procId);
|
||||||
|
ProcedureTestingUtility.assertProcNotFailed(procExec, procId);
|
||||||
|
MasterProcedureTestingUtility.validateTableIsDisabled(UTIL.getHBaseCluster().getMaster(),
|
||||||
|
tableName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(timeout = 60000)
|
||||||
|
public void testDisableTableMultipleTimes() throws Exception {
|
||||||
|
final TableName tableName = TableName.valueOf("testDisableTableMultipleTimes");
|
||||||
|
final ProcedureExecutor<MasterProcedureEnv> procExec = getMasterProcedureExecutor();
|
||||||
|
|
||||||
|
MasterProcedureTestingUtility.createTable(procExec, tableName, null, "f1", "f2");
|
||||||
|
|
||||||
|
// Disable the table
|
||||||
|
long procId1 = procExec.submitProcedure(new DisableTableProcedure(
|
||||||
|
procExec.getEnvironment(), tableName, false));
|
||||||
|
// Wait the completion
|
||||||
|
ProcedureTestingUtility.waitProcedure(procExec, procId1);
|
||||||
|
ProcedureTestingUtility.assertProcNotFailed(procExec, procId1);
|
||||||
|
MasterProcedureTestingUtility.validateTableIsDisabled(UTIL.getHBaseCluster().getMaster(),
|
||||||
|
tableName);
|
||||||
|
|
||||||
|
// Disable the table again - expect failure
|
||||||
|
long procId2 = procExec.submitProcedure(new DisableTableProcedure(
|
||||||
|
procExec.getEnvironment(), tableName, false));
|
||||||
|
// Wait the completion
|
||||||
|
ProcedureTestingUtility.waitProcedure(procExec, procId2);
|
||||||
|
ProcedureResult result = procExec.getResult(procId2);
|
||||||
|
assertTrue(result.isFailed());
|
||||||
|
LOG.debug("Disable failed with exception: " + result.getException());
|
||||||
|
assertTrue(result.getException().getCause() instanceof TableNotEnabledException);
|
||||||
|
|
||||||
|
// Disable the table - expect failure from ProcedurePrepareLatch
|
||||||
|
try {
|
||||||
|
final ProcedurePrepareLatch prepareLatch = new ProcedurePrepareLatch.CompatibilityLatch();
|
||||||
|
|
||||||
|
long procId3 = procExec.submitProcedure(new DisableTableProcedure(
|
||||||
|
procExec.getEnvironment(), tableName, false, prepareLatch));
|
||||||
|
prepareLatch.await();
|
||||||
|
Assert.fail("Disable should throw exception through latch.");
|
||||||
|
} catch (TableNotEnabledException tnee) {
|
||||||
|
// Expected
|
||||||
|
LOG.debug("Disable failed with expected exception.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disable the table again with skipping table state check flag (simulate recovery scenario)
|
||||||
|
long procId4 = procExec.submitProcedure(new DisableTableProcedure(
|
||||||
|
procExec.getEnvironment(), tableName, true));
|
||||||
|
// Wait the completion
|
||||||
|
ProcedureTestingUtility.waitProcedure(procExec, procId4);
|
||||||
|
ProcedureTestingUtility.assertProcNotFailed(procExec, procId4);
|
||||||
|
MasterProcedureTestingUtility.validateTableIsDisabled(UTIL.getHBaseCluster().getMaster(),
|
||||||
|
tableName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(timeout=60000)
|
||||||
|
public void testRecoveryAndDoubleExecution() throws Exception {
|
||||||
|
final TableName tableName = TableName.valueOf("testRecoveryAndDoubleExecution");
|
||||||
|
final ProcedureExecutor<MasterProcedureEnv> procExec = getMasterProcedureExecutor();
|
||||||
|
|
||||||
|
final byte[][] splitKeys = new byte[][] {
|
||||||
|
Bytes.toBytes("a"), Bytes.toBytes("b"), Bytes.toBytes("c")
|
||||||
|
};
|
||||||
|
MasterProcedureTestingUtility.createTable(procExec, tableName, splitKeys, "f1", "f2");
|
||||||
|
|
||||||
|
ProcedureTestingUtility.setKillAndToggleBeforeStoreUpdate(procExec, true);
|
||||||
|
|
||||||
|
// Start the Disable procedure && kill the executor
|
||||||
|
long procId =
|
||||||
|
procExec.submitProcedure(new DisableTableProcedure(procExec.getEnvironment(), tableName,
|
||||||
|
false));
|
||||||
|
|
||||||
|
// Restart the executor and execute the step twice
|
||||||
|
int numberOfSteps = DisableTableState.values().length;
|
||||||
|
MasterProcedureTestingUtility.testRecoveryAndDoubleExecution(
|
||||||
|
procExec,
|
||||||
|
procId,
|
||||||
|
numberOfSteps,
|
||||||
|
DisableTableState.values());
|
||||||
|
MasterProcedureTestingUtility.validateTableIsDisabled(UTIL.getHBaseCluster().getMaster(),
|
||||||
|
tableName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ProcedureExecutor<MasterProcedureEnv> getMasterProcedureExecutor() {
|
||||||
|
return UTIL.getHBaseCluster().getMaster().getMasterProcedureExecutor();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,192 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.apache.hadoop.hbase.master.procedure;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
import org.apache.hadoop.conf.Configuration;
|
||||||
|
import org.apache.hadoop.hbase.HBaseTestingUtility;
|
||||||
|
import org.apache.hadoop.hbase.HTableDescriptor;
|
||||||
|
import org.apache.hadoop.hbase.TableName;
|
||||||
|
import org.apache.hadoop.hbase.TableNotDisabledException;
|
||||||
|
import org.apache.hadoop.hbase.procedure2.ProcedureExecutor;
|
||||||
|
import org.apache.hadoop.hbase.procedure2.ProcedureResult;
|
||||||
|
import org.apache.hadoop.hbase.procedure2.ProcedureTestingUtility;
|
||||||
|
import org.apache.hadoop.hbase.protobuf.generated.MasterProcedureProtos.EnableTableState;
|
||||||
|
import org.apache.hadoop.hbase.testclassification.MediumTests;
|
||||||
|
import org.apache.hadoop.hbase.util.Bytes;
|
||||||
|
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.AfterClass;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.BeforeClass;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.experimental.categories.Category;
|
||||||
|
|
||||||
|
@Category(MediumTests.class)
|
||||||
|
public class TestEnableTableProcedure {
|
||||||
|
private static final Log LOG = LogFactory.getLog(TestEnableTableProcedure.class);
|
||||||
|
|
||||||
|
protected static final HBaseTestingUtility UTIL = new HBaseTestingUtility();
|
||||||
|
|
||||||
|
private static void setupConf(Configuration conf) {
|
||||||
|
conf.setInt(MasterProcedureConstants.MASTER_PROCEDURE_THREADS, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@BeforeClass
|
||||||
|
public static void setupCluster() throws Exception {
|
||||||
|
setupConf(UTIL.getConfiguration());
|
||||||
|
UTIL.startMiniCluster(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterClass
|
||||||
|
public static void cleanupTest() throws Exception {
|
||||||
|
try {
|
||||||
|
UTIL.shutdownMiniCluster();
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOG.warn("failure shutting down cluster", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setup() throws Exception {
|
||||||
|
ProcedureTestingUtility.setKillAndToggleBeforeStoreUpdate(getMasterProcedureExecutor(), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void tearDown() throws Exception {
|
||||||
|
ProcedureTestingUtility.setKillAndToggleBeforeStoreUpdate(getMasterProcedureExecutor(), false);
|
||||||
|
for (HTableDescriptor htd: UTIL.getHBaseAdmin().listTables()) {
|
||||||
|
LOG.info("Tear down, remove table=" + htd.getTableName());
|
||||||
|
UTIL.deleteTable(htd.getTableName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(timeout = 60000)
|
||||||
|
public void testEnableTable() throws Exception {
|
||||||
|
final TableName tableName = TableName.valueOf("testEnableTable");
|
||||||
|
final ProcedureExecutor<MasterProcedureEnv> procExec = getMasterProcedureExecutor();
|
||||||
|
|
||||||
|
MasterProcedureTestingUtility.createTable(procExec, tableName, null, "f1", "f2");
|
||||||
|
UTIL.getHBaseAdmin().disableTable(tableName);
|
||||||
|
|
||||||
|
// Enable the table
|
||||||
|
long procId = procExec.submitProcedure(
|
||||||
|
new EnableTableProcedure(procExec.getEnvironment(), tableName, false));
|
||||||
|
// Wait the completion
|
||||||
|
ProcedureTestingUtility.waitProcedure(procExec, procId);
|
||||||
|
ProcedureTestingUtility.assertProcNotFailed(procExec, procId);
|
||||||
|
MasterProcedureTestingUtility.validateTableIsEnabled(UTIL.getHBaseCluster().getMaster(),
|
||||||
|
tableName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(timeout=60000, expected=TableNotDisabledException.class)
|
||||||
|
public void testEnableNonDisabledTable() throws Exception {
|
||||||
|
final TableName tableName = TableName.valueOf("testEnableNonExistingTable");
|
||||||
|
final ProcedureExecutor<MasterProcedureEnv> procExec = getMasterProcedureExecutor();
|
||||||
|
|
||||||
|
MasterProcedureTestingUtility.createTable(procExec, tableName, null, "f1", "f2");
|
||||||
|
|
||||||
|
// Enable the table - expect failure
|
||||||
|
long procId1 = procExec.submitProcedure(
|
||||||
|
new EnableTableProcedure(procExec.getEnvironment(), tableName, false));
|
||||||
|
ProcedureTestingUtility.waitProcedure(procExec, procId1);
|
||||||
|
|
||||||
|
ProcedureResult result = procExec.getResult(procId1);
|
||||||
|
assertTrue(result.isFailed());
|
||||||
|
LOG.debug("Enable failed with exception: " + result.getException());
|
||||||
|
assertTrue(result.getException().getCause() instanceof TableNotDisabledException);
|
||||||
|
|
||||||
|
// Enable the table with skipping table state check flag (simulate recovery scenario)
|
||||||
|
long procId2 = procExec.submitProcedure(
|
||||||
|
new EnableTableProcedure(procExec.getEnvironment(), tableName, true));
|
||||||
|
// Wait the completion
|
||||||
|
ProcedureTestingUtility.waitProcedure(procExec, procId2);
|
||||||
|
ProcedureTestingUtility.assertProcNotFailed(procExec, procId2);
|
||||||
|
|
||||||
|
// Enable the table - expect failure from ProcedurePrepareLatch
|
||||||
|
final ProcedurePrepareLatch prepareLatch = new ProcedurePrepareLatch.CompatibilityLatch();
|
||||||
|
long procId3 = procExec.submitProcedure(
|
||||||
|
new EnableTableProcedure(procExec.getEnvironment(), tableName, false, prepareLatch));
|
||||||
|
prepareLatch.await();
|
||||||
|
Assert.fail("Enable should throw exception through latch.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(timeout = 60000)
|
||||||
|
public void testRecoveryAndDoubleExecution() throws Exception {
|
||||||
|
final TableName tableName = TableName.valueOf("testRecoveryAndDoubleExecution");
|
||||||
|
final ProcedureExecutor<MasterProcedureEnv> procExec = getMasterProcedureExecutor();
|
||||||
|
|
||||||
|
final byte[][] splitKeys = new byte[][] {
|
||||||
|
Bytes.toBytes("a"), Bytes.toBytes("b"), Bytes.toBytes("c")
|
||||||
|
};
|
||||||
|
MasterProcedureTestingUtility.createTable(procExec, tableName, splitKeys, "f1", "f2");
|
||||||
|
UTIL.getHBaseAdmin().disableTable(tableName);
|
||||||
|
ProcedureTestingUtility.waitNoProcedureRunning(procExec);
|
||||||
|
ProcedureTestingUtility.setKillAndToggleBeforeStoreUpdate(procExec, true);
|
||||||
|
|
||||||
|
// Start the Enable procedure && kill the executor
|
||||||
|
long procId = procExec.submitProcedure(
|
||||||
|
new EnableTableProcedure(procExec.getEnvironment(), tableName, false));
|
||||||
|
|
||||||
|
// Restart the executor and execute the step twice
|
||||||
|
int numberOfSteps = EnableTableState.values().length;
|
||||||
|
MasterProcedureTestingUtility.testRecoveryAndDoubleExecution(
|
||||||
|
procExec,
|
||||||
|
procId,
|
||||||
|
numberOfSteps,
|
||||||
|
EnableTableState.values());
|
||||||
|
MasterProcedureTestingUtility.validateTableIsEnabled(UTIL.getHBaseCluster().getMaster(),
|
||||||
|
tableName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(timeout = 60000)
|
||||||
|
public void testRollbackAndDoubleExecution() throws Exception {
|
||||||
|
final TableName tableName = TableName.valueOf("testRollbackAndDoubleExecution");
|
||||||
|
final ProcedureExecutor<MasterProcedureEnv> procExec = getMasterProcedureExecutor();
|
||||||
|
|
||||||
|
final byte[][] splitKeys = new byte[][] {
|
||||||
|
Bytes.toBytes("a"), Bytes.toBytes("b"), Bytes.toBytes("c")
|
||||||
|
};
|
||||||
|
MasterProcedureTestingUtility.createTable(procExec, tableName, splitKeys, "f1", "f2");
|
||||||
|
UTIL.getHBaseAdmin().disableTable(tableName);
|
||||||
|
ProcedureTestingUtility.waitNoProcedureRunning(procExec);
|
||||||
|
ProcedureTestingUtility.setKillAndToggleBeforeStoreUpdate(procExec, true);
|
||||||
|
|
||||||
|
// Start the Enable procedure && kill the executor
|
||||||
|
long procId = procExec.submitProcedure(
|
||||||
|
new EnableTableProcedure(procExec.getEnvironment(), tableName, false));
|
||||||
|
|
||||||
|
int numberOfSteps = EnableTableState.values().length - 2; // failing in the middle of proc
|
||||||
|
MasterProcedureTestingUtility.testRollbackAndDoubleExecution(
|
||||||
|
procExec,
|
||||||
|
procId,
|
||||||
|
numberOfSteps,
|
||||||
|
EnableTableState.values());
|
||||||
|
MasterProcedureTestingUtility.validateTableIsDisabled(UTIL.getHBaseCluster().getMaster(),
|
||||||
|
tableName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ProcedureExecutor<MasterProcedureEnv> getMasterProcedureExecutor() {
|
||||||
|
return UTIL.getHBaseCluster().getMaster().getMasterProcedureExecutor();
|
||||||
|
}
|
||||||
|
}
|
@ -37,7 +37,10 @@ import org.apache.hadoop.hbase.procedure2.store.ProcedureStore;
|
|||||||
import org.apache.hadoop.hbase.procedure2.store.wal.WALProcedureStore;
|
import org.apache.hadoop.hbase.procedure2.store.wal.WALProcedureStore;
|
||||||
import org.apache.hadoop.hbase.protobuf.generated.MasterProcedureProtos.CreateTableState;
|
import org.apache.hadoop.hbase.protobuf.generated.MasterProcedureProtos.CreateTableState;
|
||||||
import org.apache.hadoop.hbase.protobuf.generated.MasterProcedureProtos.DeleteTableState;
|
import org.apache.hadoop.hbase.protobuf.generated.MasterProcedureProtos.DeleteTableState;
|
||||||
|
import org.apache.hadoop.hbase.protobuf.generated.MasterProcedureProtos.DisableTableState;
|
||||||
|
import org.apache.hadoop.hbase.protobuf.generated.MasterProcedureProtos.EnableTableState;
|
||||||
import org.apache.hadoop.hbase.testclassification.LargeTests;
|
import org.apache.hadoop.hbase.testclassification.LargeTests;
|
||||||
|
import org.apache.hadoop.hbase.util.Bytes;
|
||||||
import org.apache.hadoop.hbase.util.FSUtils;
|
import org.apache.hadoop.hbase.util.FSUtils;
|
||||||
import org.apache.hadoop.hbase.util.ModifyRegionUtils;
|
import org.apache.hadoop.hbase.util.ModifyRegionUtils;
|
||||||
|
|
||||||
@ -210,6 +213,79 @@ public class TestMasterFailoverWithProcedures {
|
|||||||
UTIL.getHBaseCluster().getMaster(), tableName, regions, "f1", "f2");
|
UTIL.getHBaseCluster().getMaster(), tableName, regions, "f1", "f2");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==========================================================================
|
||||||
|
// Test Disable Table
|
||||||
|
// ==========================================================================
|
||||||
|
@Test(timeout=60000)
|
||||||
|
public void testDisableTableWithFailover() throws Exception {
|
||||||
|
// TODO: Should we try every step? (master failover takes long time)
|
||||||
|
// It is already covered by TestDisableTableProcedure
|
||||||
|
// but without the master restart, only the executor/store is restarted.
|
||||||
|
// Without Master restart we may not find bug in the procedure code
|
||||||
|
// like missing "wait" for resources to be available (e.g. RS)
|
||||||
|
testDisableTableWithFailoverAtStep(
|
||||||
|
DisableTableState.DISABLE_TABLE_MARK_REGIONS_OFFLINE.ordinal());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void testDisableTableWithFailoverAtStep(final int step) throws Exception {
|
||||||
|
final TableName tableName = TableName.valueOf("testDisableTableWithFailoverAtStep" + step);
|
||||||
|
|
||||||
|
// create the table
|
||||||
|
final byte[][] splitKeys = new byte[][] {
|
||||||
|
Bytes.toBytes("a"), Bytes.toBytes("b"), Bytes.toBytes("c")
|
||||||
|
};
|
||||||
|
MasterProcedureTestingUtility.createTable(
|
||||||
|
getMasterProcedureExecutor(), tableName, splitKeys, "f1", "f2");
|
||||||
|
|
||||||
|
ProcedureExecutor<MasterProcedureEnv> procExec = getMasterProcedureExecutor();
|
||||||
|
ProcedureTestingUtility.setKillAndToggleBeforeStoreUpdate(procExec, true);
|
||||||
|
|
||||||
|
// Start the Delete procedure && kill the executor
|
||||||
|
long procId = procExec.submitProcedure(
|
||||||
|
new DisableTableProcedure(procExec.getEnvironment(), tableName, false));
|
||||||
|
testRecoveryAndDoubleExecution(UTIL, procId, step, DisableTableState.values());
|
||||||
|
|
||||||
|
MasterProcedureTestingUtility.validateTableIsDisabled(
|
||||||
|
UTIL.getHBaseCluster().getMaster(), tableName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==========================================================================
|
||||||
|
// Test Enable Table
|
||||||
|
// ==========================================================================
|
||||||
|
@Test(timeout=60000)
|
||||||
|
public void testEnableTableWithFailover() throws Exception {
|
||||||
|
// TODO: Should we try every step? (master failover takes long time)
|
||||||
|
// It is already covered by TestEnableTableProcedure
|
||||||
|
// but without the master restart, only the executor/store is restarted.
|
||||||
|
// Without Master restart we may not find bug in the procedure code
|
||||||
|
// like missing "wait" for resources to be available (e.g. RS)
|
||||||
|
testEnableTableWithFailoverAtStep(
|
||||||
|
EnableTableState.ENABLE_TABLE_MARK_REGIONS_ONLINE.ordinal());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void testEnableTableWithFailoverAtStep(final int step) throws Exception {
|
||||||
|
final TableName tableName = TableName.valueOf("testEnableTableWithFailoverAtStep" + step);
|
||||||
|
|
||||||
|
// create the table
|
||||||
|
final byte[][] splitKeys = new byte[][] {
|
||||||
|
Bytes.toBytes("a"), Bytes.toBytes("b"), Bytes.toBytes("c")
|
||||||
|
};
|
||||||
|
MasterProcedureTestingUtility.createTable(
|
||||||
|
getMasterProcedureExecutor(), tableName, splitKeys, "f1", "f2");
|
||||||
|
UTIL.getHBaseAdmin().disableTable(tableName);
|
||||||
|
|
||||||
|
ProcedureExecutor<MasterProcedureEnv> procExec = getMasterProcedureExecutor();
|
||||||
|
ProcedureTestingUtility.setKillAndToggleBeforeStoreUpdate(procExec, true);
|
||||||
|
|
||||||
|
// Start the Delete procedure && kill the executor
|
||||||
|
long procId = procExec.submitProcedure(
|
||||||
|
new EnableTableProcedure(procExec.getEnvironment(), tableName, false));
|
||||||
|
testRecoveryAndDoubleExecution(UTIL, procId, step, EnableTableState.values());
|
||||||
|
|
||||||
|
MasterProcedureTestingUtility.validateTableIsEnabled(
|
||||||
|
UTIL.getHBaseCluster().getMaster(), tableName);
|
||||||
|
}
|
||||||
|
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
// Test Helpers
|
// Test Helpers
|
||||||
// ==========================================================================
|
// ==========================================================================
|
||||||
|
Loading…
x
Reference in New Issue
Block a user