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