org.apache.hadoop.hbase.master.procedure.DeleteColumnFamilyProcedure Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of hbase-server Show documentation
Show all versions of hbase-server Show documentation
Server functionality for HBase
/**
* 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.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.HTableDescriptor;
import org.apache.hadoop.hbase.InvalidFamilyOperationException;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.classification.InterfaceAudience;
import org.apache.hadoop.hbase.executor.EventType;
import org.apache.hadoop.hbase.master.MasterCoprocessorHost;
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.DeleteColumnFamilyState;
import org.apache.hadoop.hbase.protobuf.generated.ZooKeeperProtos;
import org.apache.hadoop.hbase.util.ByteStringer;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.security.UserGroupInformation;
/**
* The procedure to delete a column family from an existing table.
*/
@InterfaceAudience.Private
public class DeleteColumnFamilyProcedure
extends StateMachineProcedure
implements TableProcedureInterface {
private static final Log LOG = LogFactory.getLog(DeleteColumnFamilyProcedure.class);
private final AtomicBoolean aborted = new AtomicBoolean(false);
private HTableDescriptor unmodifiedHTableDescriptor;
private TableName tableName;
private byte [] familyName;
private UserGroupInformation user;
private List regionInfoList;
private Boolean traceEnabled;
public DeleteColumnFamilyProcedure() {
this.unmodifiedHTableDescriptor = null;
this.regionInfoList = null;
this.traceEnabled = null;
}
public DeleteColumnFamilyProcedure(
final MasterProcedureEnv env,
final TableName tableName,
final byte[] familyName) throws IOException {
this.tableName = tableName;
this.familyName = familyName;
this.user = env.getRequestUser().getUGI();
this.unmodifiedHTableDescriptor = null;
this.regionInfoList = null;
this.traceEnabled = null;
}
@Override
protected Flow executeFromState(final MasterProcedureEnv env, DeleteColumnFamilyState state) {
if (isTraceEnabled()) {
LOG.trace(this + " execute state=" + state);
}
try {
switch (state) {
case DELETE_COLUMN_FAMILY_PREPARE:
prepareDelete(env);
setNextState(DeleteColumnFamilyState.DELETE_COLUMN_FAMILY_PRE_OPERATION);
break;
case DELETE_COLUMN_FAMILY_PRE_OPERATION:
preDelete(env, state);
setNextState(DeleteColumnFamilyState.DELETE_COLUMN_FAMILY_UPDATE_TABLE_DESCRIPTOR);
break;
case DELETE_COLUMN_FAMILY_UPDATE_TABLE_DESCRIPTOR:
updateTableDescriptor(env);
setNextState(DeleteColumnFamilyState.DELETE_COLUMN_FAMILY_DELETE_FS_LAYOUT);
break;
case DELETE_COLUMN_FAMILY_DELETE_FS_LAYOUT:
deleteFromFs(env);
setNextState(DeleteColumnFamilyState.DELETE_COLUMN_FAMILY_POST_OPERATION);
break;
case DELETE_COLUMN_FAMILY_POST_OPERATION:
postDelete(env, state);
setNextState(DeleteColumnFamilyState.DELETE_COLUMN_FAMILY_REOPEN_ALL_REGIONS);
break;
case DELETE_COLUMN_FAMILY_REOPEN_ALL_REGIONS:
reOpenAllRegionsIfTableIsOnline(env);
return Flow.NO_MORE_STATE;
default:
throw new UnsupportedOperationException(this + " unhandled state=" + state);
}
} catch (InterruptedException|IOException e) {
if (!isRollbackSupported(state)) {
// We reach a state that cannot be rolled back. We just need to keep retry.
LOG.warn("Error trying to delete the column family " + getColumnFamilyName()
+ " from table " + tableName + "(in state=" + state + ")", e);
} else {
LOG.error("Error trying to delete the column family " + getColumnFamilyName()
+ " from table " + tableName + "(in state=" + state + ")", e);
setFailure("master-delete-column-family", e);
}
}
return Flow.HAS_MORE_STATE;
}
@Override
protected void rollbackState(final MasterProcedureEnv env, final DeleteColumnFamilyState state)
throws IOException {
if (isTraceEnabled()) {
LOG.trace(this + " rollback state=" + state);
}
try {
switch (state) {
case DELETE_COLUMN_FAMILY_REOPEN_ALL_REGIONS:
break; // Nothing to undo.
case DELETE_COLUMN_FAMILY_POST_OPERATION:
// TODO-MAYBE: call the coprocessor event to undo?
break;
case DELETE_COLUMN_FAMILY_DELETE_FS_LAYOUT:
// Once we reach to this state - we could NOT rollback - as it is tricky to undelete
// the deleted files. We are not suppose to reach here, throw exception so that we know
// there is a code bug to investigate.
throw new UnsupportedOperationException(this + " rollback of state=" + state
+ " is unsupported.");
case DELETE_COLUMN_FAMILY_UPDATE_TABLE_DESCRIPTOR:
restoreTableDescriptor(env);
break;
case DELETE_COLUMN_FAMILY_PRE_OPERATION:
// TODO-MAYBE: call the coprocessor event to undo?
break;
case DELETE_COLUMN_FAMILY_PREPARE:
break; // nothing to do
default:
throw new UnsupportedOperationException(this + " unhandled state=" + state);
}
} 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 rollback attempt step " + state + " for deleting the column family"
+ getColumnFamilyName() + " to the table " + tableName, e);
throw e;
}
}
@Override
protected DeleteColumnFamilyState getState(final int stateId) {
return DeleteColumnFamilyState.valueOf(stateId);
}
@Override
protected int getStateId(final DeleteColumnFamilyState state) {
return state.getNumber();
}
@Override
protected DeleteColumnFamilyState getInitialState() {
return DeleteColumnFamilyState.DELETE_COLUMN_FAMILY_PREPARE;
}
@Override
protected void setNextState(DeleteColumnFamilyState state) {
if (aborted.get() && isRollbackSupported(state)) {
setAbortFailure("delete-columnfamily", "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_DELETE_FAMILY.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.DeleteColumnFamilyStateData.Builder deleteCFMsg =
MasterProcedureProtos.DeleteColumnFamilyStateData.newBuilder()
.setUserInfo(MasterProcedureUtil.toProtoUserInfo(user))
.setTableName(ProtobufUtil.toProtoTableName(tableName))
.setColumnfamilyName(ByteStringer.wrap(familyName));
if (unmodifiedHTableDescriptor != null) {
deleteCFMsg.setUnmodifiedTableSchema(unmodifiedHTableDescriptor.convert());
}
deleteCFMsg.build().writeDelimitedTo(stream);
}
@Override
public void deserializeStateData(final InputStream stream) throws IOException {
super.deserializeStateData(stream);
MasterProcedureProtos.DeleteColumnFamilyStateData deleteCFMsg =
MasterProcedureProtos.DeleteColumnFamilyStateData.parseDelimitedFrom(stream);
user = MasterProcedureUtil.toUserInfo(deleteCFMsg.getUserInfo());
tableName = ProtobufUtil.toTableName(deleteCFMsg.getTableName());
familyName = deleteCFMsg.getColumnfamilyName().toByteArray();
if (deleteCFMsg.hasUnmodifiedTableSchema()) {
unmodifiedHTableDescriptor = HTableDescriptor.convert(deleteCFMsg.getUnmodifiedTableSchema());
}
}
@Override
public void toStringClassDetails(StringBuilder sb) {
sb.append(getClass().getSimpleName());
sb.append(" (table=");
sb.append(tableName);
sb.append(", columnfamily=");
if (familyName != null) {
sb.append(getColumnFamilyName());
} else {
sb.append("Unknown");
}
sb.append(") user=");
sb.append(user);
}
@Override
public TableName getTableName() {
return tableName;
}
@Override
public TableOperationType getTableOperationType() {
return TableOperationType.EDIT;
}
/**
* Action before any real action of deleting column family.
* @param env MasterProcedureEnv
* @throws IOException
*/
private void prepareDelete(final MasterProcedureEnv env) throws IOException {
// Checks whether the table is allowed to be modified.
MasterDDLOperationHelper.checkTableModifiable(env, tableName);
// In order to update the descriptor, we need to retrieve the old descriptor for comparison.
unmodifiedHTableDescriptor = env.getMasterServices().getTableDescriptors().get(tableName);
if (unmodifiedHTableDescriptor == null) {
throw new IOException("HTableDescriptor missing for " + tableName);
}
if (!unmodifiedHTableDescriptor.hasFamily(familyName)) {
throw new InvalidFamilyOperationException("Family '" + getColumnFamilyName()
+ "' does not exist, so it cannot be deleted");
}
if (unmodifiedHTableDescriptor.getColumnFamilies().length == 1) {
throw new InvalidFamilyOperationException("Family '" + getColumnFamilyName()
+ "' is the only column family in the table, so it cannot be deleted");
}
}
/**
* Action before deleting column family.
* @param env MasterProcedureEnv
* @param state the procedure state
* @throws IOException
* @throws InterruptedException
*/
private void preDelete(final MasterProcedureEnv env, final DeleteColumnFamilyState state)
throws IOException, InterruptedException {
runCoprocessorAction(env, state);
}
/**
* Remove the column family from the file system and update the table descriptor
*/
private void updateTableDescriptor(final MasterProcedureEnv env) throws IOException {
// Update table descriptor
LOG.info("DeleteColumn. Table = " + tableName + " family = " + getColumnFamilyName());
HTableDescriptor htd = env.getMasterServices().getTableDescriptors().get(tableName);
if (!htd.hasFamily(familyName)) {
// It is possible to reach this situation, as we could already delete the column family
// from table descriptor, but the master failover happens before we complete this state.
// We should be able to handle running this function multiple times without causing problem.
return;
}
htd.removeFamily(familyName);
env.getMasterServices().getTableDescriptors().add(htd);
}
/**
* Restore back to the old descriptor
* @param env MasterProcedureEnv
* @throws IOException
**/
private void restoreTableDescriptor(final MasterProcedureEnv env) throws IOException {
env.getMasterServices().getTableDescriptors().add(unmodifiedHTableDescriptor);
// Make sure regions are opened after table descriptor is updated.
reOpenAllRegionsIfTableIsOnline(env);
}
/**
* Remove the column family from the file system
**/
private void deleteFromFs(final MasterProcedureEnv env) throws IOException {
MasterDDLOperationHelper.deleteColumnFamilyFromFileSystem(env, tableName,
getRegionInfoList(env), familyName);
}
/**
* Action after deleting column family.
* @param env MasterProcedureEnv
* @param state the procedure state
* @throws IOException
* @throws InterruptedException
*/
private void postDelete(final MasterProcedureEnv env, final DeleteColumnFamilyState state)
throws IOException, InterruptedException {
runCoprocessorAction(env, state);
}
/**
* Last action from the procedure - executed when online schema change is supported.
* @param env MasterProcedureEnv
* @throws IOException
*/
private void reOpenAllRegionsIfTableIsOnline(final MasterProcedureEnv env) throws IOException {
// This operation only run when the table is enabled.
if (!env.getMasterServices().getAssignmentManager().getTableStateManager()
.isTableState(getTableName(), ZooKeeperProtos.Table.State.ENABLED)) {
return;
}
if (MasterDDLOperationHelper.reOpenAllRegions(env, getTableName(), getRegionInfoList(env))) {
LOG.info("Completed delete column family operation on table " + getTableName());
} else {
LOG.warn("Error on reopening the regions on table " + getTableName());
}
}
/**
* 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;
}
private String getColumnFamilyName() {
return Bytes.toString(familyName);
}
/**
* Coprocessor Action.
* @param env MasterProcedureEnv
* @param state the procedure state
* @throws IOException
* @throws InterruptedException
*/
private void runCoprocessorAction(final MasterProcedureEnv env,
final DeleteColumnFamilyState state) throws IOException, InterruptedException {
final MasterCoprocessorHost cpHost = env.getMasterCoprocessorHost();
if (cpHost != null) {
user.doAs(new PrivilegedExceptionAction() {
@Override
public Void run() throws Exception {
switch (state) {
case DELETE_COLUMN_FAMILY_PRE_OPERATION:
cpHost.preDeleteColumnHandler(tableName, familyName);
break;
case DELETE_COLUMN_FAMILY_POST_OPERATION:
cpHost.postDeleteColumnHandler(tableName, familyName);
break;
default:
throw new UnsupportedOperationException(this + " unhandled state=" + state);
}
return null;
}
});
}
}
/*
* Check whether we are in the state that can be rollback
*/
private boolean isRollbackSupported(final DeleteColumnFamilyState state) {
switch (state) {
case DELETE_COLUMN_FAMILY_REOPEN_ALL_REGIONS:
case DELETE_COLUMN_FAMILY_POST_OPERATION:
case DELETE_COLUMN_FAMILY_DELETE_FS_LAYOUT:
// It is not safe to rollback if we reach to these states.
return false;
default:
break;
}
return true;
}
private List getRegionInfoList(final MasterProcedureEnv env) throws IOException {
if (regionInfoList == null) {
regionInfoList = ProcedureSyncWait.getRegionsFromMeta(env, getTableName());
}
return regionInfoList;
}
}