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