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.WALSplitter; 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 private Boolean traceEnabled = null; 055 056 public DisableTableProcedure() { 057 super(); 058 } 059 060 /** 061 * Constructor 062 * @param env MasterProcedureEnv 063 * @param tableName the table to operate on 064 * @param skipTableStateCheck whether to check table state 065 */ 066 public DisableTableProcedure(final MasterProcedureEnv env, final TableName tableName, 067 final boolean skipTableStateCheck) 068 throws HBaseIOException { 069 this(env, tableName, skipTableStateCheck, null); 070 } 071 072 /** 073 * Constructor 074 * @param env MasterProcedureEnv 075 * @param tableName the table to operate on 076 * @param skipTableStateCheck whether to check table state 077 */ 078 public DisableTableProcedure(final MasterProcedureEnv env, final TableName tableName, 079 final boolean skipTableStateCheck, final ProcedurePrepareLatch syncLatch) 080 throws HBaseIOException { 081 super(env, syncLatch); 082 this.tableName = tableName; 083 preflightChecks(env, true); 084 this.skipTableStateCheck = skipTableStateCheck; 085 } 086 087 @Override 088 protected Flow executeFromState(final MasterProcedureEnv env, final DisableTableState state) 089 throws InterruptedException { 090 LOG.trace("{} execute state={}", this, state); 091 try { 092 switch (state) { 093 case DISABLE_TABLE_PREPARE: 094 if (prepareDisable(env)) { 095 setNextState(DisableTableState.DISABLE_TABLE_PRE_OPERATION); 096 } else { 097 assert isFailed() : "disable should have an exception here"; 098 return Flow.NO_MORE_STATE; 099 } 100 break; 101 case DISABLE_TABLE_PRE_OPERATION: 102 preDisable(env, state); 103 setNextState(DisableTableState.DISABLE_TABLE_SET_DISABLING_TABLE_STATE); 104 break; 105 case DISABLE_TABLE_SET_DISABLING_TABLE_STATE: 106 setTableStateToDisabling(env, tableName); 107 setNextState(DisableTableState.DISABLE_TABLE_MARK_REGIONS_OFFLINE); 108 break; 109 case DISABLE_TABLE_MARK_REGIONS_OFFLINE: 110 addChildProcedure(env.getAssignmentManager().createUnassignProcedures(tableName)); 111 setNextState(DisableTableState.DISABLE_TABLE_ADD_REPLICATION_BARRIER); 112 break; 113 case DISABLE_TABLE_ADD_REPLICATION_BARRIER: 114 if (env.getMasterServices().getTableDescriptors().get(tableName) 115 .hasGlobalReplicationScope()) { 116 MasterFileSystem fs = env.getMasterFileSystem(); 117 try (BufferedMutator mutator = env.getMasterServices().getConnection() 118 .getBufferedMutator(TableName.META_TABLE_NAME)) { 119 for (RegionInfo region : env.getAssignmentManager().getRegionStates() 120 .getRegionsOfTable(tableName)) { 121 long maxSequenceId = WALSplitter.getMaxRegionSequenceId( 122 env.getMasterConfiguration(), region, fs::getFileSystem, fs::getWALFileSystem); 123 long openSeqNum = maxSequenceId > 0 ? maxSequenceId + 1 : HConstants.NO_SEQNUM; 124 mutator.mutate(MetaTableAccessor.makePutForReplicationBarrier(region, openSeqNum, 125 EnvironmentEdgeManager.currentTime())); 126 } 127 } 128 } 129 setNextState(DisableTableState.DISABLE_TABLE_SET_DISABLED_TABLE_STATE); 130 break; 131 case DISABLE_TABLE_SET_DISABLED_TABLE_STATE: 132 setTableStateToDisabled(env, tableName); 133 setNextState(DisableTableState.DISABLE_TABLE_POST_OPERATION); 134 break; 135 case DISABLE_TABLE_POST_OPERATION: 136 postDisable(env, state); 137 return Flow.NO_MORE_STATE; 138 default: 139 throw new UnsupportedOperationException("Unhandled state=" + state); 140 } 141 } catch (IOException e) { 142 if (isRollbackSupported(state)) { 143 setFailure("master-disable-table", e); 144 } else { 145 LOG.warn("Retryable error in {}", this, e); 146 } 147 } 148 return Flow.HAS_MORE_STATE; 149 } 150 151 @Override 152 protected void rollbackState(final MasterProcedureEnv env, final DisableTableState state) 153 throws IOException { 154 // nothing to rollback, prepare-disable is just table-state checks. 155 // We can fail if the table does not exist or is not disabled. 156 switch (state) { 157 case DISABLE_TABLE_PRE_OPERATION: 158 return; 159 case DISABLE_TABLE_PREPARE: 160 releaseSyncLatch(); 161 return; 162 default: 163 break; 164 } 165 166 // The delete doesn't have a rollback. The execution will succeed, at some point. 167 throw new UnsupportedOperationException("Unhandled state=" + state); 168 } 169 170 @Override 171 protected boolean isRollbackSupported(final DisableTableState state) { 172 switch (state) { 173 case DISABLE_TABLE_PREPARE: 174 case DISABLE_TABLE_PRE_OPERATION: 175 return true; 176 default: 177 return false; 178 } 179 } 180 181 @Override 182 protected DisableTableState getState(final int stateId) { 183 return DisableTableState.valueOf(stateId); 184 } 185 186 @Override 187 protected int getStateId(final DisableTableState state) { 188 return state.getNumber(); 189 } 190 191 @Override 192 protected DisableTableState getInitialState() { 193 return DisableTableState.DISABLE_TABLE_PREPARE; 194 } 195 196 @Override 197 protected void serializeStateData(ProcedureStateSerializer serializer) 198 throws IOException { 199 super.serializeStateData(serializer); 200 201 MasterProcedureProtos.DisableTableStateData.Builder disableTableMsg = 202 MasterProcedureProtos.DisableTableStateData.newBuilder() 203 .setUserInfo(MasterProcedureUtil.toProtoUserInfo(getUser())) 204 .setTableName(ProtobufUtil.toProtoTableName(tableName)) 205 .setSkipTableStateCheck(skipTableStateCheck); 206 207 serializer.serialize(disableTableMsg.build()); 208 } 209 210 @Override 211 protected void deserializeStateData(ProcedureStateSerializer serializer) 212 throws IOException { 213 super.deserializeStateData(serializer); 214 215 MasterProcedureProtos.DisableTableStateData disableTableMsg = 216 serializer.deserialize(MasterProcedureProtos.DisableTableStateData.class); 217 setUser(MasterProcedureUtil.toUserInfo(disableTableMsg.getUserInfo())); 218 tableName = ProtobufUtil.toTableName(disableTableMsg.getTableName()); 219 skipTableStateCheck = disableTableMsg.getSkipTableStateCheck(); 220 } 221 222 @Override 223 public TableName getTableName() { 224 return tableName; 225 } 226 227 @Override 228 public TableOperationType getTableOperationType() { 229 return TableOperationType.DISABLE; 230 } 231 232 /** 233 * Action before any real action of disabling table. Set the exception in the procedure instead 234 * of throwing it. This approach is to deal with backward compatible with 1.0. 235 * @param env MasterProcedureEnv 236 * @throws IOException 237 */ 238 private boolean prepareDisable(final MasterProcedureEnv env) throws IOException { 239 boolean canTableBeDisabled = true; 240 if (tableName.equals(TableName.META_TABLE_NAME)) { 241 setFailure("master-disable-table", new ConstraintException("Cannot disable catalog table")); 242 canTableBeDisabled = false; 243 } else if (!MetaTableAccessor.tableExists(env.getMasterServices().getConnection(), tableName)) { 244 setFailure("master-disable-table", new TableNotFoundException(tableName)); 245 canTableBeDisabled = false; 246 } else if (!skipTableStateCheck) { 247 // There could be multiple client requests trying to disable or enable 248 // the table at the same time. Ensure only the first request is honored 249 // After that, no other requests can be accepted until the table reaches 250 // DISABLED or ENABLED. 251 // 252 // Note: in 1.0 release, we called TableStateManager.setTableStateIfInStates() to set 253 // the state to DISABLING from ENABLED. The implementation was done before table lock 254 // was implemented. With table lock, there is no need to set the state here (it will 255 // set the state later on). A quick state check should be enough for us to move forward. 256 TableStateManager tsm = env.getMasterServices().getTableStateManager(); 257 TableState ts = tsm.getTableState(tableName); 258 if (!ts.isEnabled()) { 259 LOG.info("Not ENABLED, state={}, skipping disable; {}", ts.getState(), this); 260 setFailure("master-disable-table", new TableNotEnabledException(ts.toString())); 261 canTableBeDisabled = false; 262 } 263 } 264 265 // We are done the check. Future actions in this procedure could be done asynchronously. 266 releaseSyncLatch(); 267 268 return canTableBeDisabled; 269 } 270 271 /** 272 * Action before disabling table. 273 * @param env MasterProcedureEnv 274 * @param state the procedure state 275 * @throws IOException 276 * @throws InterruptedException 277 */ 278 protected void preDisable(final MasterProcedureEnv env, final DisableTableState state) 279 throws IOException, InterruptedException { 280 runCoprocessorAction(env, state); 281 } 282 283 /** 284 * Mark table state to Disabling 285 * @param env MasterProcedureEnv 286 * @throws IOException 287 */ 288 protected static void setTableStateToDisabling( 289 final MasterProcedureEnv env, 290 final TableName tableName) throws IOException { 291 // Set table disabling flag up in zk. 292 env.getMasterServices().getTableStateManager().setTableState( 293 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 * @throws IOException 302 */ 303 protected static void setTableStateToDisabled( 304 final MasterProcedureEnv env, 305 final TableName tableName) throws IOException { 306 // Flip the table to disabled 307 env.getMasterServices().getTableStateManager().setTableState( 308 tableName, 309 TableState.State.DISABLED); 310 LOG.info("Set {} to state={}", tableName, TableState.State.DISABLED); 311 } 312 313 /** 314 * Action after disabling table. 315 * @param env MasterProcedureEnv 316 * @param state the procedure state 317 * @throws IOException 318 * @throws InterruptedException 319 */ 320 protected void postDisable(final MasterProcedureEnv env, final DisableTableState state) 321 throws IOException, InterruptedException { 322 runCoprocessorAction(env, state); 323 } 324 325 /** 326 * The procedure could be restarted from a different machine. If the variable is null, we need to 327 * retrieve it. 328 * @return traceEnabled 329 */ 330 private Boolean isTraceEnabled() { 331 if (traceEnabled == null) { 332 traceEnabled = LOG.isTraceEnabled(); 333 } 334 return traceEnabled; 335 } 336 337 /** 338 * Coprocessor Action. 339 * @param env MasterProcedureEnv 340 * @param state the procedure state 341 * @throws IOException 342 * @throws InterruptedException 343 */ 344 private void runCoprocessorAction(final MasterProcedureEnv env, final DisableTableState state) 345 throws IOException, InterruptedException { 346 final MasterCoprocessorHost cpHost = env.getMasterCoprocessorHost(); 347 if (cpHost != null) { 348 switch (state) { 349 case DISABLE_TABLE_PRE_OPERATION: 350 cpHost.preDisableTableAction(tableName, getUser()); 351 break; 352 case DISABLE_TABLE_POST_OPERATION: 353 cpHost.postCompletedDisableTableAction(tableName, getUser()); 354 break; 355 default: 356 throw new UnsupportedOperationException(this + " unhandled state=" + state); 357 } 358 } 359 } 360}