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