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 */ 018package org.apache.hadoop.hbase.master.procedure; 019 020import java.io.IOException; 021import java.util.ArrayList; 022import java.util.List; 023import org.apache.hadoop.hbase.TableName; 024import org.apache.hadoop.hbase.TableNotDisabledException; 025import org.apache.hadoop.hbase.TableNotFoundException; 026import org.apache.hadoop.hbase.client.RegionInfo; 027import org.apache.hadoop.hbase.client.RegionReplicaUtil; 028import org.apache.hadoop.hbase.client.TableDescriptor; 029import org.apache.hadoop.hbase.client.TableState; 030import org.apache.hadoop.hbase.master.MasterCoprocessorHost; 031import org.apache.hadoop.hbase.master.TableStateManager; 032import org.apache.hadoop.hbase.procedure2.ProcedureStateSerializer; 033import org.apache.yetus.audience.InterfaceAudience; 034import org.slf4j.Logger; 035import org.slf4j.LoggerFactory; 036 037import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil; 038import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProcedureProtos; 039import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProcedureProtos.EnableTableState; 040 041@InterfaceAudience.Private 042public class EnableTableProcedure 043 extends AbstractStateMachineTableProcedure<EnableTableState> { 044 private static final Logger LOG = LoggerFactory.getLogger(EnableTableProcedure.class); 045 046 private TableName tableName; 047 048 public EnableTableProcedure() { 049 } 050 051 /** 052 * Constructor 053 * @param env MasterProcedureEnv 054 * @param tableName the table to operate on 055 */ 056 public EnableTableProcedure(MasterProcedureEnv env, TableName tableName) { 057 this(env, tableName, null); 058 } 059 060 /** 061 * Constructor 062 * @param env MasterProcedureEnv 063 * @param tableName the table to operate on 064 */ 065 public EnableTableProcedure(MasterProcedureEnv env, TableName tableName, 066 ProcedurePrepareLatch syncLatch) { 067 super(env, syncLatch); 068 this.tableName = tableName; 069 } 070 071 @Override 072 protected Flow executeFromState(final MasterProcedureEnv env, final EnableTableState state) 073 throws InterruptedException { 074 LOG.trace("{} execute state={}", this, state); 075 076 try { 077 switch (state) { 078 case ENABLE_TABLE_PREPARE: 079 if (prepareEnable(env)) { 080 setNextState(EnableTableState.ENABLE_TABLE_PRE_OPERATION); 081 } else { 082 assert isFailed() : "enable should have an exception here"; 083 return Flow.NO_MORE_STATE; 084 } 085 break; 086 case ENABLE_TABLE_PRE_OPERATION: 087 preEnable(env, state); 088 setNextState(EnableTableState.ENABLE_TABLE_SET_ENABLING_TABLE_STATE); 089 break; 090 case ENABLE_TABLE_SET_ENABLING_TABLE_STATE: 091 setTableStateToEnabling(env, tableName); 092 setNextState(EnableTableState.ENABLE_TABLE_MARK_REGIONS_ONLINE); 093 break; 094 case ENABLE_TABLE_MARK_REGIONS_ONLINE: 095 // Get the region replica count. If changed since disable, need to do 096 // more work assigning. 097 TableDescriptor tableDescriptor = 098 env.getMasterServices().getTableDescriptors().get(tableName); 099 int configuredReplicaCount = tableDescriptor.getRegionReplication(); 100 // Get regions for the table from memory 101 List<RegionInfo> regionsOfTable = 102 env.getAssignmentManager().getRegionStates().getRegionsOfTableForEnabling(tableName); 103 104 // How many replicas do we currently have? Check regions returned from 105 // in-memory state. 106 int currentMaxReplica = getMaxReplicaId(regionsOfTable); 107 if (currentMaxReplica == configuredReplicaCount - 1) { 108 LOG.debug("No change in number of region replicas (configuredReplicaCount={});" 109 + " assigning.", configuredReplicaCount); 110 } else if (currentMaxReplica > (configuredReplicaCount - 1)) { 111 // We have additional regions as the replica count has been decreased. Delete 112 // those regions because already the table is in the unassigned state 113 LOG.warn( 114 "The number of replicas {} is more than the region replica count {}" + 115 ", usually this should not happen as we will delete them in ModifyTableProcedure", 116 currentMaxReplica + 1, configuredReplicaCount); 117 List<RegionInfo> copyOfRegions = new ArrayList<RegionInfo>(regionsOfTable); 118 for (RegionInfo regionInfo : copyOfRegions) { 119 if (regionInfo.getReplicaId() > (configuredReplicaCount - 1)) { 120 // delete the region from the regionStates 121 env.getAssignmentManager().getRegionStates().deleteRegion(regionInfo); 122 // remove it from the list of regions of the table 123 LOG.info("Removed replica={} of {}", regionInfo.getRegionId(), regionInfo); 124 regionsOfTable.remove(regionInfo); 125 } 126 } 127 } else if (currentMaxReplica < configuredReplicaCount - 1) { 128 // the replicasFound is less than the regionReplication 129 LOG.info("Number of replicas has increased for {}. Assigning new region replicas." + 130 "The previous replica count was {}. The current replica count is {}.", 131 this.tableName, currentMaxReplica + 1, configuredReplicaCount); 132 regionsOfTable = RegionReplicaUtil.addReplicas(regionsOfTable, 133 currentMaxReplica + 1, configuredReplicaCount); 134 } 135 // Assign all the table regions. (including region replicas if added). 136 // createAssignProcedure will try to retain old assignments if possible. 137 addChildProcedure(env.getAssignmentManager().createAssignProcedures(regionsOfTable)); 138 setNextState(EnableTableState.ENABLE_TABLE_SET_ENABLED_TABLE_STATE); 139 break; 140 case ENABLE_TABLE_SET_ENABLED_TABLE_STATE: 141 setTableStateToEnabled(env, tableName); 142 setNextState(EnableTableState.ENABLE_TABLE_POST_OPERATION); 143 break; 144 case ENABLE_TABLE_POST_OPERATION: 145 postEnable(env, state); 146 return Flow.NO_MORE_STATE; 147 default: 148 throw new UnsupportedOperationException("unhandled state=" + state); 149 } 150 } catch (IOException e) { 151 if (isRollbackSupported(state)) { 152 setFailure("master-enable-table", e); 153 } else { 154 LOG.warn( 155 "Retriable error trying to enable table=" + tableName + " (in state=" + state + ")", e); 156 } 157 } 158 return Flow.HAS_MORE_STATE; 159 } 160 161 @Override 162 protected void rollbackState(final MasterProcedureEnv env, final EnableTableState state) 163 throws IOException { 164 // nothing to rollback, prepare-disable is just table-state checks. 165 // We can fail if the table does not exist or is not disabled. 166 switch (state) { 167 case ENABLE_TABLE_PRE_OPERATION: 168 return; 169 case ENABLE_TABLE_PREPARE: 170 releaseSyncLatch(); 171 return; 172 default: 173 break; 174 } 175 176 // The delete doesn't have a rollback. The execution will succeed, at some point. 177 throw new UnsupportedOperationException("unhandled state=" + state); 178 } 179 180 @Override 181 protected boolean isRollbackSupported(final EnableTableState state) { 182 switch (state) { 183 case ENABLE_TABLE_PREPARE: 184 case ENABLE_TABLE_PRE_OPERATION: 185 return true; 186 default: 187 return false; 188 } 189 } 190 191 @Override 192 protected EnableTableState getState(final int stateId) { 193 return EnableTableState.forNumber(stateId); 194 } 195 196 @Override 197 protected int getStateId(final EnableTableState state) { 198 return state.getNumber(); 199 } 200 201 @Override 202 protected EnableTableState getInitialState() { 203 return EnableTableState.ENABLE_TABLE_PREPARE; 204 } 205 206 @Override 207 protected void serializeStateData(ProcedureStateSerializer serializer) throws IOException { 208 super.serializeStateData(serializer); 209 210 // the skipTableStateCheck is false so we still need to set it... 211 @SuppressWarnings("deprecation") 212 MasterProcedureProtos.EnableTableStateData.Builder enableTableMsg = 213 MasterProcedureProtos.EnableTableStateData.newBuilder() 214 .setUserInfo(MasterProcedureUtil.toProtoUserInfo(getUser())) 215 .setTableName(ProtobufUtil.toProtoTableName(tableName)).setSkipTableStateCheck(false); 216 217 serializer.serialize(enableTableMsg.build()); 218 } 219 220 @Override 221 protected void deserializeStateData(ProcedureStateSerializer serializer) throws IOException { 222 super.deserializeStateData(serializer); 223 224 MasterProcedureProtos.EnableTableStateData enableTableMsg = 225 serializer.deserialize(MasterProcedureProtos.EnableTableStateData.class); 226 setUser(MasterProcedureUtil.toUserInfo(enableTableMsg.getUserInfo())); 227 tableName = ProtobufUtil.toTableName(enableTableMsg.getTableName()); 228 } 229 230 @Override 231 public TableName getTableName() { 232 return tableName; 233 } 234 235 @Override 236 public TableOperationType getTableOperationType() { 237 return TableOperationType.ENABLE; 238 } 239 240 241 /** 242 * Action before any real action of enabling table. Set the exception in the procedure instead 243 * of throwing it. This approach is to deal with backward compatible with 1.0. 244 * @param env MasterProcedureEnv 245 * @return whether the table passes the necessary checks 246 * @throws IOException 247 */ 248 private boolean prepareEnable(final MasterProcedureEnv env) throws IOException { 249 boolean canTableBeEnabled = true; 250 // Check whether table exists 251 if (!env.getMasterServices().getTableDescriptors().exists(tableName)) { 252 setFailure("master-enable-table", new TableNotFoundException(tableName)); 253 canTableBeEnabled = false; 254 } else { 255 // There could be multiple client requests trying to disable or enable 256 // the table at the same time. Ensure only the first request is honored 257 // After that, no other requests can be accepted until the table reaches 258 // DISABLED or ENABLED. 259 // 260 // Note: in 1.0 release, we called TableStateManager.setTableStateIfInStates() to set 261 // the state to ENABLING from DISABLED. The implementation was done before table lock 262 // was implemented. With table lock, there is no need to set the state here (it will 263 // set the state later on). A quick state check should be enough for us to move forward. 264 TableStateManager tsm = env.getMasterServices().getTableStateManager(); 265 TableState ts = tsm.getTableState(tableName); 266 if(!ts.isDisabled()){ 267 LOG.info("Not DISABLED tableState={}; skipping enable; {}", ts.getState(), this); 268 setFailure("master-enable-table", new TableNotDisabledException(ts.toString())); 269 canTableBeEnabled = false; 270 } 271 } 272 273 // We are done the check. Future actions in this procedure could be done asynchronously. 274 releaseSyncLatch(); 275 276 return canTableBeEnabled; 277 } 278 279 /** 280 * Action before enabling table. 281 * @param env MasterProcedureEnv 282 * @param state the procedure state 283 * @throws IOException 284 * @throws InterruptedException 285 */ 286 private void preEnable(final MasterProcedureEnv env, final EnableTableState state) 287 throws IOException, InterruptedException { 288 runCoprocessorAction(env, state); 289 } 290 291 /** 292 * Mark table state to Enabling 293 * @param env MasterProcedureEnv 294 * @param tableName the target table 295 * @throws IOException 296 */ 297 protected static void setTableStateToEnabling( 298 final MasterProcedureEnv env, 299 final TableName tableName) throws IOException { 300 // Set table disabling flag up in zk. 301 LOG.info("Attempting to enable the table " + tableName); 302 env.getMasterServices().getTableStateManager().setTableState( 303 tableName, 304 TableState.State.ENABLING); 305 } 306 307 /** 308 * Mark table state to Enabled 309 * @param env MasterProcedureEnv 310 * @throws IOException 311 */ 312 protected static void setTableStateToEnabled( 313 final MasterProcedureEnv env, 314 final TableName tableName) throws IOException { 315 // Flip the table to Enabled 316 env.getMasterServices().getTableStateManager().setTableState( 317 tableName, 318 TableState.State.ENABLED); 319 LOG.info("Table '" + tableName + "' was successfully enabled."); 320 } 321 322 /** 323 * Action after enabling table. 324 * @param env MasterProcedureEnv 325 * @param state the procedure state 326 * @throws IOException 327 * @throws InterruptedException 328 */ 329 private void postEnable(final MasterProcedureEnv env, final EnableTableState state) 330 throws IOException, InterruptedException { 331 runCoprocessorAction(env, state); 332 } 333 334 /** 335 * Coprocessor Action. 336 * @param env MasterProcedureEnv 337 * @param state the procedure state 338 * @throws IOException 339 * @throws InterruptedException 340 */ 341 private void runCoprocessorAction(final MasterProcedureEnv env, final EnableTableState state) 342 throws IOException, InterruptedException { 343 final MasterCoprocessorHost cpHost = env.getMasterCoprocessorHost(); 344 if (cpHost != null) { 345 switch (state) { 346 case ENABLE_TABLE_PRE_OPERATION: 347 cpHost.preEnableTableAction(getTableName(), getUser()); 348 break; 349 case ENABLE_TABLE_POST_OPERATION: 350 cpHost.postCompletedEnableTableAction(getTableName(), getUser()); 351 break; 352 default: 353 throw new UnsupportedOperationException(this + " unhandled state=" + state); 354 } 355 } 356 } 357 358 /** 359 * @return Maximum region replica id found in passed list of regions. 360 */ 361 private static int getMaxReplicaId(List<RegionInfo> regions) { 362 int max = 0; 363 for (RegionInfo regionInfo: regions) { 364 if (regionInfo.getReplicaId() > max) { 365 // Iterating through all the list to identify the highest replicaID region. 366 // We can stop after checking with the first set of regions?? 367 max = regionInfo.getReplicaId(); 368 } 369 } 370 return max; 371 372 } 373}