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.MetaTableAccessor; 024import org.apache.hadoop.hbase.TableName; 025import org.apache.hadoop.hbase.TableNotEnabledException; 026import org.apache.hadoop.hbase.TableNotFoundException; 027import org.apache.hadoop.hbase.client.TableState; 028import org.apache.hadoop.hbase.constraint.ConstraintException; 029import org.apache.hadoop.hbase.master.MasterCoprocessorHost; 030import org.apache.hadoop.hbase.master.TableStateManager; 031import org.apache.hadoop.hbase.procedure2.ProcedureStateSerializer; 032import org.apache.yetus.audience.InterfaceAudience; 033import org.slf4j.Logger; 034import org.slf4j.LoggerFactory; 035 036import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil; 037import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProcedureProtos; 038import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProcedureProtos.DisableTableState; 039 040@InterfaceAudience.Private 041public class DisableTableProcedure 042 extends AbstractStateMachineTableProcedure<DisableTableState> { 043 private static final Logger LOG = LoggerFactory.getLogger(DisableTableProcedure.class); 044 045 private TableName tableName; 046 private boolean skipTableStateCheck; 047 048 private Boolean traceEnabled = null; 049 050 public DisableTableProcedure() { 051 super(); 052 } 053 054 /** 055 * Constructor 056 * @param env MasterProcedureEnv 057 * @param tableName the table to operate on 058 * @param skipTableStateCheck whether to check table state 059 */ 060 public DisableTableProcedure(final MasterProcedureEnv env, final TableName tableName, 061 final boolean skipTableStateCheck) 062 throws HBaseIOException { 063 this(env, tableName, skipTableStateCheck, null); 064 } 065 066 /** 067 * Constructor 068 * @param env MasterProcedureEnv 069 * @param tableName the table to operate on 070 * @param skipTableStateCheck whether to check table state 071 */ 072 public DisableTableProcedure(final MasterProcedureEnv env, final TableName tableName, 073 final boolean skipTableStateCheck, final ProcedurePrepareLatch syncLatch) 074 throws HBaseIOException { 075 super(env, syncLatch); 076 this.tableName = tableName; 077 preflightChecks(env, true); 078 this.skipTableStateCheck = skipTableStateCheck; 079 } 080 081 @Override 082 protected Flow executeFromState(final MasterProcedureEnv env, final DisableTableState state) 083 throws InterruptedException { 084 if (isTraceEnabled()) { 085 LOG.trace(this + " execute state=" + state); 086 } 087 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(env.getAssignmentManager().createUnassignProcedures(tableName)); 108 setNextState(DisableTableState.DISABLE_TABLE_SET_DISABLED_TABLE_STATE); 109 break; 110 case DISABLE_TABLE_SET_DISABLED_TABLE_STATE: 111 setTableStateToDisabled(env, tableName); 112 setNextState(DisableTableState.DISABLE_TABLE_POST_OPERATION); 113 break; 114 case DISABLE_TABLE_POST_OPERATION: 115 postDisable(env, state); 116 return Flow.NO_MORE_STATE; 117 default: 118 throw new UnsupportedOperationException("Unhandled state=" + state); 119 } 120 } catch (IOException e) { 121 if (isRollbackSupported(state)) { 122 setFailure("master-disable-table", e); 123 } else { 124 LOG.warn("Retryable error in {}", this, e); 125 } 126 } 127 return Flow.HAS_MORE_STATE; 128 } 129 130 @Override 131 protected void rollbackState(final MasterProcedureEnv env, final DisableTableState state) 132 throws IOException { 133 // nothing to rollback, prepare-disable is just table-state checks. 134 // We can fail if the table does not exist or is not disabled. 135 switch (state) { 136 case DISABLE_TABLE_PRE_OPERATION: 137 return; 138 case DISABLE_TABLE_PREPARE: 139 releaseSyncLatch(); 140 return; 141 default: 142 break; 143 } 144 145 // The delete doesn't have a rollback. The execution will succeed, at some point. 146 throw new UnsupportedOperationException("Unhandled state=" + state); 147 } 148 149 @Override 150 protected boolean isRollbackSupported(final DisableTableState state) { 151 switch (state) { 152 case DISABLE_TABLE_PREPARE: 153 case DISABLE_TABLE_PRE_OPERATION: 154 return true; 155 default: 156 return false; 157 } 158 } 159 160 @Override 161 protected DisableTableState getState(final int stateId) { 162 return DisableTableState.valueOf(stateId); 163 } 164 165 @Override 166 protected int getStateId(final DisableTableState state) { 167 return state.getNumber(); 168 } 169 170 @Override 171 protected DisableTableState getInitialState() { 172 return DisableTableState.DISABLE_TABLE_PREPARE; 173 } 174 175 @Override 176 protected void serializeStateData(ProcedureStateSerializer serializer) 177 throws IOException { 178 super.serializeStateData(serializer); 179 180 MasterProcedureProtos.DisableTableStateData.Builder disableTableMsg = 181 MasterProcedureProtos.DisableTableStateData.newBuilder() 182 .setUserInfo(MasterProcedureUtil.toProtoUserInfo(getUser())) 183 .setTableName(ProtobufUtil.toProtoTableName(tableName)) 184 .setSkipTableStateCheck(skipTableStateCheck); 185 186 serializer.serialize(disableTableMsg.build()); 187 } 188 189 @Override 190 protected void deserializeStateData(ProcedureStateSerializer serializer) 191 throws IOException { 192 super.deserializeStateData(serializer); 193 194 MasterProcedureProtos.DisableTableStateData disableTableMsg = 195 serializer.deserialize(MasterProcedureProtos.DisableTableStateData.class); 196 setUser(MasterProcedureUtil.toUserInfo(disableTableMsg.getUserInfo())); 197 tableName = ProtobufUtil.toTableName(disableTableMsg.getTableName()); 198 skipTableStateCheck = disableTableMsg.getSkipTableStateCheck(); 199 } 200 201 @Override 202 public TableName getTableName() { 203 return tableName; 204 } 205 206 @Override 207 public TableOperationType getTableOperationType() { 208 return TableOperationType.DISABLE; 209 } 210 211 /** 212 * Action before any real action of disabling table. Set the exception in the procedure instead 213 * of throwing it. This approach is to deal with backward compatible with 1.0. 214 * @param env MasterProcedureEnv 215 * @throws IOException 216 */ 217 private boolean prepareDisable(final MasterProcedureEnv env) throws IOException { 218 boolean canTableBeDisabled = true; 219 if (tableName.equals(TableName.META_TABLE_NAME)) { 220 setFailure("master-disable-table", new ConstraintException("Cannot disable catalog table")); 221 canTableBeDisabled = false; 222 } else if (!MetaTableAccessor.tableExists(env.getMasterServices().getConnection(), tableName)) { 223 setFailure("master-disable-table", new TableNotFoundException(tableName)); 224 canTableBeDisabled = false; 225 } else if (!skipTableStateCheck) { 226 // There could be multiple client requests trying to disable or enable 227 // the table at the same time. Ensure only the first request is honored 228 // After that, no other requests can be accepted until the table reaches 229 // DISABLED or ENABLED. 230 // 231 // Note: in 1.0 release, we called TableStateManager.setTableStateIfInStates() to set 232 // the state to DISABLING from ENABLED. The implementation was done before table lock 233 // was implemented. With table lock, there is no need to set the state here (it will 234 // set the state later on). A quick state check should be enough for us to move forward. 235 TableStateManager tsm = env.getMasterServices().getTableStateManager(); 236 TableState ts = tsm.getTableState(tableName); 237 if (!ts.isEnabled()) { 238 LOG.info("Not ENABLED, state={}, skipping disable; {}", ts.getState(), this); 239 setFailure("master-disable-table", new TableNotEnabledException(ts.toString())); 240 canTableBeDisabled = false; 241 } 242 } 243 244 // We are done the check. Future actions in this procedure could be done asynchronously. 245 releaseSyncLatch(); 246 247 return canTableBeDisabled; 248 } 249 250 /** 251 * Action before disabling table. 252 * @param env MasterProcedureEnv 253 * @param state the procedure state 254 * @throws IOException 255 * @throws InterruptedException 256 */ 257 protected void preDisable(final MasterProcedureEnv env, final DisableTableState state) 258 throws IOException, InterruptedException { 259 runCoprocessorAction(env, state); 260 } 261 262 /** 263 * Mark table state to Disabling 264 * @param env MasterProcedureEnv 265 * @throws IOException 266 */ 267 protected static void setTableStateToDisabling( 268 final MasterProcedureEnv env, 269 final TableName tableName) throws IOException { 270 // Set table disabling flag up in zk. 271 env.getMasterServices().getTableStateManager().setTableState( 272 tableName, 273 TableState.State.DISABLING); 274 LOG.info("Set {} to state={}", tableName, TableState.State.DISABLING); 275 } 276 277 /** 278 * Mark table state to Disabled 279 * @param env MasterProcedureEnv 280 * @throws IOException 281 */ 282 protected static void setTableStateToDisabled( 283 final MasterProcedureEnv env, 284 final TableName tableName) throws IOException { 285 // Flip the table to disabled 286 env.getMasterServices().getTableStateManager().setTableState( 287 tableName, 288 TableState.State.DISABLED); 289 LOG.info("Set {} to state={}", tableName, TableState.State.DISABLED); 290 } 291 292 /** 293 * Action after disabling table. 294 * @param env MasterProcedureEnv 295 * @param state the procedure state 296 * @throws IOException 297 * @throws InterruptedException 298 */ 299 protected void postDisable(final MasterProcedureEnv env, final DisableTableState state) 300 throws IOException, InterruptedException { 301 runCoprocessorAction(env, state); 302 } 303 304 /** 305 * The procedure could be restarted from a different machine. If the variable is null, we need to 306 * retrieve it. 307 * @return traceEnabled 308 */ 309 private Boolean isTraceEnabled() { 310 if (traceEnabled == null) { 311 traceEnabled = LOG.isTraceEnabled(); 312 } 313 return traceEnabled; 314 } 315 316 /** 317 * Coprocessor Action. 318 * @param env MasterProcedureEnv 319 * @param state the procedure state 320 * @throws IOException 321 * @throws InterruptedException 322 */ 323 private void runCoprocessorAction(final MasterProcedureEnv env, final DisableTableState state) 324 throws IOException, InterruptedException { 325 final MasterCoprocessorHost cpHost = env.getMasterCoprocessorHost(); 326 if (cpHost != null) { 327 switch (state) { 328 case DISABLE_TABLE_PRE_OPERATION: 329 cpHost.preDisableTableAction(tableName, getUser()); 330 break; 331 case DISABLE_TABLE_POST_OPERATION: 332 cpHost.postCompletedDisableTableAction(tableName, getUser()); 333 break; 334 default: 335 throw new UnsupportedOperationException(this + " unhandled state=" + state); 336 } 337 } 338 } 339}