001/** 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018 019package org.apache.hadoop.hbase.master.procedure; 020 021import java.io.IOException; 022import org.apache.hadoop.hbase.HBaseIOException; 023import org.apache.hadoop.hbase.HConstants; 024import org.apache.hadoop.hbase.MetaTableAccessor; 025import org.apache.hadoop.hbase.TableName; 026import org.apache.hadoop.hbase.TableNotEnabledException; 027import org.apache.hadoop.hbase.TableNotFoundException; 028import org.apache.hadoop.hbase.client.BufferedMutator; 029import org.apache.hadoop.hbase.client.RegionInfo; 030import org.apache.hadoop.hbase.client.TableState; 031import org.apache.hadoop.hbase.constraint.ConstraintException; 032import org.apache.hadoop.hbase.master.MasterCoprocessorHost; 033import org.apache.hadoop.hbase.master.MasterFileSystem; 034import org.apache.hadoop.hbase.master.TableStateManager; 035import org.apache.hadoop.hbase.procedure2.ProcedureStateSerializer; 036import org.apache.hadoop.hbase.util.EnvironmentEdgeManager; 037import org.apache.hadoop.hbase.wal.WALSplitUtil; 038import org.apache.yetus.audience.InterfaceAudience; 039import org.slf4j.Logger; 040import org.slf4j.LoggerFactory; 041 042import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil; 043import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProcedureProtos; 044import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProcedureProtos.DisableTableState; 045 046@InterfaceAudience.Private 047public class DisableTableProcedure 048 extends AbstractStateMachineTableProcedure<DisableTableState> { 049 private static final Logger LOG = LoggerFactory.getLogger(DisableTableProcedure.class); 050 051 private TableName tableName; 052 private boolean skipTableStateCheck; 053 054 public DisableTableProcedure() { 055 super(); 056 } 057 058 /** 059 * Constructor 060 * @param env MasterProcedureEnv 061 * @param tableName the table to operate on 062 * @param skipTableStateCheck whether to check table state 063 */ 064 public DisableTableProcedure(final MasterProcedureEnv env, final TableName tableName, 065 final boolean skipTableStateCheck) throws HBaseIOException { 066 this(env, tableName, skipTableStateCheck, null); 067 } 068 069 /** 070 * Constructor 071 * @param env MasterProcedureEnv 072 * @param tableName the table to operate on 073 * @param skipTableStateCheck whether to check table state 074 */ 075 public DisableTableProcedure(final MasterProcedureEnv env, final TableName tableName, 076 final boolean skipTableStateCheck, final ProcedurePrepareLatch syncLatch) 077 throws HBaseIOException { 078 super(env, syncLatch); 079 this.tableName = tableName; 080 preflightChecks(env, true); 081 this.skipTableStateCheck = skipTableStateCheck; 082 } 083 084 @Override 085 protected Flow executeFromState(final MasterProcedureEnv env, final DisableTableState state) 086 throws InterruptedException { 087 LOG.trace("{} execute state={}", this, state); 088 try { 089 switch (state) { 090 case DISABLE_TABLE_PREPARE: 091 if (prepareDisable(env)) { 092 setNextState(DisableTableState.DISABLE_TABLE_PRE_OPERATION); 093 } else { 094 assert isFailed() : "disable should have an exception here"; 095 return Flow.NO_MORE_STATE; 096 } 097 break; 098 case DISABLE_TABLE_PRE_OPERATION: 099 preDisable(env, state); 100 setNextState(DisableTableState.DISABLE_TABLE_SET_DISABLING_TABLE_STATE); 101 break; 102 case DISABLE_TABLE_SET_DISABLING_TABLE_STATE: 103 setTableStateToDisabling(env, tableName); 104 setNextState(DisableTableState.DISABLE_TABLE_MARK_REGIONS_OFFLINE); 105 break; 106 case DISABLE_TABLE_MARK_REGIONS_OFFLINE: 107 addChildProcedure( 108 env.getAssignmentManager().createUnassignProceduresForDisabling(tableName)); 109 setNextState(DisableTableState.DISABLE_TABLE_ADD_REPLICATION_BARRIER); 110 break; 111 case DISABLE_TABLE_ADD_REPLICATION_BARRIER: 112 if (env.getMasterServices().getTableDescriptors().get(tableName) 113 .hasGlobalReplicationScope()) { 114 MasterFileSystem fs = env.getMasterFileSystem(); 115 try (BufferedMutator mutator = env.getMasterServices().getConnection() 116 .getBufferedMutator(TableName.META_TABLE_NAME)) { 117 for (RegionInfo region : env.getAssignmentManager().getRegionStates() 118 .getRegionsOfTable(tableName)) { 119 long maxSequenceId = WALSplitUtil.getMaxRegionSequenceId( 120 env.getMasterConfiguration(), region, fs::getFileSystem, fs::getWALFileSystem); 121 long openSeqNum = maxSequenceId > 0 ? maxSequenceId + 1 : HConstants.NO_SEQNUM; 122 mutator.mutate(MetaTableAccessor.makePutForReplicationBarrier(region, openSeqNum, 123 EnvironmentEdgeManager.currentTime())); 124 } 125 } 126 } 127 setNextState(DisableTableState.DISABLE_TABLE_SET_DISABLED_TABLE_STATE); 128 break; 129 case DISABLE_TABLE_SET_DISABLED_TABLE_STATE: 130 setTableStateToDisabled(env, tableName); 131 setNextState(DisableTableState.DISABLE_TABLE_POST_OPERATION); 132 break; 133 case DISABLE_TABLE_POST_OPERATION: 134 postDisable(env, state); 135 return Flow.NO_MORE_STATE; 136 default: 137 throw new UnsupportedOperationException("Unhandled state=" + state); 138 } 139 } catch (IOException e) { 140 if (isRollbackSupported(state)) { 141 setFailure("master-disable-table", e); 142 } else { 143 LOG.warn("Retryable error in {}", this, e); 144 } 145 } 146 return Flow.HAS_MORE_STATE; 147 } 148 149 @Override 150 protected void rollbackState(final MasterProcedureEnv env, final DisableTableState state) 151 throws IOException { 152 // nothing to rollback, prepare-disable is just table-state checks. 153 // We can fail if the table does not exist or is not disabled. 154 switch (state) { 155 case DISABLE_TABLE_PRE_OPERATION: 156 return; 157 case DISABLE_TABLE_PREPARE: 158 releaseSyncLatch(); 159 return; 160 default: 161 break; 162 } 163 164 // The delete doesn't have a rollback. The execution will succeed, at some point. 165 throw new UnsupportedOperationException("Unhandled state=" + state); 166 } 167 168 @Override 169 protected boolean isRollbackSupported(final DisableTableState state) { 170 switch (state) { 171 case DISABLE_TABLE_PREPARE: 172 case DISABLE_TABLE_PRE_OPERATION: 173 return true; 174 default: 175 return false; 176 } 177 } 178 179 @Override 180 protected DisableTableState getState(final int stateId) { 181 return DisableTableState.forNumber(stateId); 182 } 183 184 @Override 185 protected int getStateId(final DisableTableState state) { 186 return state.getNumber(); 187 } 188 189 @Override 190 protected DisableTableState getInitialState() { 191 return DisableTableState.DISABLE_TABLE_PREPARE; 192 } 193 194 @Override 195 protected void serializeStateData(ProcedureStateSerializer serializer) 196 throws IOException { 197 super.serializeStateData(serializer); 198 199 MasterProcedureProtos.DisableTableStateData.Builder disableTableMsg = 200 MasterProcedureProtos.DisableTableStateData.newBuilder() 201 .setUserInfo(MasterProcedureUtil.toProtoUserInfo(getUser())) 202 .setTableName(ProtobufUtil.toProtoTableName(tableName)) 203 .setSkipTableStateCheck(skipTableStateCheck); 204 205 serializer.serialize(disableTableMsg.build()); 206 } 207 208 @Override 209 protected void deserializeStateData(ProcedureStateSerializer serializer) 210 throws IOException { 211 super.deserializeStateData(serializer); 212 213 MasterProcedureProtos.DisableTableStateData disableTableMsg = 214 serializer.deserialize(MasterProcedureProtos.DisableTableStateData.class); 215 setUser(MasterProcedureUtil.toUserInfo(disableTableMsg.getUserInfo())); 216 tableName = ProtobufUtil.toTableName(disableTableMsg.getTableName()); 217 skipTableStateCheck = disableTableMsg.getSkipTableStateCheck(); 218 } 219 220 // For disabling a table, we does not care whether a region can be online so hold the table xlock 221 // for ever. This will simplify the logic as we will not be conflict with procedures other than 222 // SCP. 223 @Override 224 protected boolean holdLock(MasterProcedureEnv env) { 225 return true; 226 } 227 228 @Override 229 public TableName getTableName() { 230 return tableName; 231 } 232 233 @Override 234 public TableOperationType getTableOperationType() { 235 return TableOperationType.DISABLE; 236 } 237 238 /** 239 * Action before any real action of disabling table. Set the exception in the procedure instead 240 * of throwing it. This approach is to deal with backward compatible with 1.0. 241 * @param env MasterProcedureEnv 242 */ 243 private boolean prepareDisable(final MasterProcedureEnv env) throws IOException { 244 boolean canTableBeDisabled = true; 245 if (tableName.equals(TableName.META_TABLE_NAME)) { 246 setFailure("master-disable-table", new ConstraintException("Cannot disable catalog table")); 247 canTableBeDisabled = false; 248 } else if (!MetaTableAccessor.tableExists(env.getMasterServices().getConnection(), tableName)) { 249 setFailure("master-disable-table", new TableNotFoundException(tableName)); 250 canTableBeDisabled = false; 251 } else if (!skipTableStateCheck) { 252 // There could be multiple client requests trying to disable or enable 253 // the table at the same time. Ensure only the first request is honored 254 // After that, no other requests can be accepted until the table reaches 255 // DISABLED or ENABLED. 256 // 257 // Note: in 1.0 release, we called TableStateManager.setTableStateIfInStates() to set 258 // the state to DISABLING from ENABLED. The implementation was done before table lock 259 // was implemented. With table lock, there is no need to set the state here (it will 260 // set the state later on). A quick state check should be enough for us to move forward. 261 TableStateManager tsm = env.getMasterServices().getTableStateManager(); 262 TableState ts = tsm.getTableState(tableName); 263 if (!ts.isEnabled()) { 264 LOG.info("Not ENABLED, state={}, skipping disable; {}", ts.getState(), this); 265 setFailure("master-disable-table", new TableNotEnabledException(ts.toString())); 266 canTableBeDisabled = false; 267 } 268 } 269 270 // We are done the check. Future actions in this procedure could be done asynchronously. 271 releaseSyncLatch(); 272 273 return canTableBeDisabled; 274 } 275 276 /** 277 * Action before disabling table. 278 * @param env MasterProcedureEnv 279 * @param state the procedure state 280 */ 281 protected void preDisable(final MasterProcedureEnv env, final DisableTableState state) 282 throws IOException, InterruptedException { 283 runCoprocessorAction(env, state); 284 } 285 286 /** 287 * Mark table state to Disabling 288 * @param env MasterProcedureEnv 289 */ 290 private static void setTableStateToDisabling(final MasterProcedureEnv env, 291 final TableName tableName) throws IOException { 292 // Set table disabling flag up in zk. 293 env.getMasterServices().getTableStateManager().setTableState(tableName, 294 TableState.State.DISABLING); 295 LOG.info("Set {} to state={}", tableName, TableState.State.DISABLING); 296 } 297 298 /** 299 * Mark table state to Disabled 300 * @param env MasterProcedureEnv 301 */ 302 protected static void setTableStateToDisabled(final MasterProcedureEnv env, 303 final TableName tableName) throws IOException { 304 // Flip the table to disabled 305 env.getMasterServices().getTableStateManager().setTableState(tableName, 306 TableState.State.DISABLED); 307 LOG.info("Set {} to state={}", tableName, TableState.State.DISABLED); 308 } 309 310 /** 311 * Action after disabling table. 312 * @param env MasterProcedureEnv 313 * @param state the procedure state 314 */ 315 protected void postDisable(final MasterProcedureEnv env, final DisableTableState state) 316 throws IOException, InterruptedException { 317 runCoprocessorAction(env, state); 318 } 319 320 /** 321 * Coprocessor Action. 322 * @param env MasterProcedureEnv 323 * @param state the procedure state 324 */ 325 private void runCoprocessorAction(final MasterProcedureEnv env, final DisableTableState state) 326 throws IOException, InterruptedException { 327 final MasterCoprocessorHost cpHost = env.getMasterCoprocessorHost(); 328 if (cpHost != null) { 329 switch (state) { 330 case DISABLE_TABLE_PRE_OPERATION: 331 cpHost.preDisableTableAction(tableName, getUser()); 332 break; 333 case DISABLE_TABLE_POST_OPERATION: 334 cpHost.postCompletedDisableTableAction(tableName, getUser()); 335 break; 336 default: 337 throw new UnsupportedOperationException(this + " unhandled state=" + state); 338 } 339 } 340 } 341}