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.Arrays; 022import java.util.Collections; 023import java.util.HashSet; 024import java.util.List; 025import java.util.Set; 026import java.util.function.Supplier; 027import java.util.stream.Collectors; 028import java.util.stream.IntStream; 029import org.apache.hadoop.hbase.ConcurrentTableModificationException; 030import org.apache.hadoop.hbase.DoNotRetryIOException; 031import org.apache.hadoop.hbase.HBaseIOException; 032import org.apache.hadoop.hbase.HConstants; 033import org.apache.hadoop.hbase.TableName; 034import org.apache.hadoop.hbase.TableNotFoundException; 035import org.apache.hadoop.hbase.client.RegionInfo; 036import org.apache.hadoop.hbase.client.RegionReplicaUtil; 037import org.apache.hadoop.hbase.client.TableDescriptor; 038import org.apache.hadoop.hbase.client.TableDescriptorBuilder; 039import org.apache.hadoop.hbase.fs.ErasureCodingUtils; 040import org.apache.hadoop.hbase.master.MasterCoprocessorHost; 041import org.apache.hadoop.hbase.master.zksyncer.MetaLocationSyncer; 042import org.apache.hadoop.hbase.procedure2.ProcedureStateSerializer; 043import org.apache.hadoop.hbase.regionserver.storefiletracker.StoreFileTrackerValidationUtils; 044import org.apache.hadoop.hbase.rsgroup.RSGroupInfo; 045import org.apache.hadoop.hbase.util.Bytes; 046import org.apache.yetus.audience.InterfaceAudience; 047import org.slf4j.Logger; 048import org.slf4j.LoggerFactory; 049 050import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil; 051import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProcedureProtos; 052import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProcedureProtos.ModifyTableState; 053 054@InterfaceAudience.Private 055public class ModifyTableProcedure extends AbstractStateMachineTableProcedure<ModifyTableState> { 056 private static final Logger LOG = LoggerFactory.getLogger(ModifyTableProcedure.class); 057 058 private TableDescriptor unmodifiedTableDescriptor = null; 059 private TableDescriptor modifiedTableDescriptor; 060 private boolean deleteColumnFamilyInModify; 061 private boolean shouldCheckDescriptor; 062 private boolean reopenRegions; 063 private String recoverySnapshotName; 064 065 /** 066 * List of column families that cannot be deleted from the hbase:meta table. They are critical to 067 * cluster operation. This is a bit of an odd place to keep this list but then this is the tooling 068 * that does add/remove. Keeping it local! 069 */ 070 private static final List<byte[]> UNDELETABLE_META_COLUMNFAMILIES = 071 Collections.unmodifiableList(Arrays.asList(HConstants.CATALOG_FAMILY, HConstants.TABLE_FAMILY, 072 HConstants.REPLICATION_BARRIER_FAMILY, HConstants.NAMESPACE_FAMILY)); 073 074 public ModifyTableProcedure() { 075 super(); 076 initialize(null, false); 077 } 078 079 public ModifyTableProcedure(final MasterProcedureEnv env, final TableDescriptor htd) 080 throws HBaseIOException { 081 this(env, htd, null); 082 } 083 084 public ModifyTableProcedure(final MasterProcedureEnv env, final TableDescriptor htd, 085 final ProcedurePrepareLatch latch) throws HBaseIOException { 086 this(env, htd, latch, null, false, true); 087 } 088 089 public ModifyTableProcedure(final MasterProcedureEnv env, 090 final TableDescriptor newTableDescriptor, final ProcedurePrepareLatch latch, 091 final TableDescriptor oldTableDescriptor, final boolean shouldCheckDescriptor, 092 final boolean reopenRegions) throws HBaseIOException { 093 super(env, latch); 094 this.reopenRegions = reopenRegions; 095 initialize(oldTableDescriptor, shouldCheckDescriptor); 096 this.modifiedTableDescriptor = newTableDescriptor; 097 preflightChecks(env, null/* No table checks; if changing peers, table can be online */); 098 } 099 100 @Override 101 protected void preflightChecks(MasterProcedureEnv env, Boolean enabled) throws HBaseIOException { 102 super.preflightChecks(env, enabled); 103 if (this.modifiedTableDescriptor.isMetaTable()) { 104 // If we are modifying the hbase:meta table, make sure we are not deleting critical 105 // column families else we'll damage the cluster. 106 Set<byte[]> cfs = this.modifiedTableDescriptor.getColumnFamilyNames(); 107 for (byte[] family : UNDELETABLE_META_COLUMNFAMILIES) { 108 if (!cfs.contains(family)) { 109 throw new HBaseIOException( 110 "Delete of hbase:meta column family " + Bytes.toString(family)); 111 } 112 } 113 } 114 115 if (!reopenRegions) { 116 if (this.unmodifiedTableDescriptor == null) { 117 throw new HBaseIOException( 118 "unmodifiedTableDescriptor cannot be null when this table modification won't reopen regions"); 119 } 120 if ( 121 !this.unmodifiedTableDescriptor.getTableName() 122 .equals(this.modifiedTableDescriptor.getTableName()) 123 ) { 124 throw new HBaseIOException( 125 "Cannot change the table name when this modification won't " + "reopen regions."); 126 } 127 if ( 128 this.unmodifiedTableDescriptor.getColumnFamilyCount() 129 != this.modifiedTableDescriptor.getColumnFamilyCount() 130 ) { 131 throw new HBaseIOException( 132 "Cannot add or remove column families when this modification " + "won't reopen regions."); 133 } 134 if ( 135 this.unmodifiedTableDescriptor.getCoprocessorDescriptors().hashCode() 136 != this.modifiedTableDescriptor.getCoprocessorDescriptors().hashCode() 137 ) { 138 throw new HBaseIOException( 139 "Can not modify Coprocessor when table modification won't reopen regions"); 140 } 141 final Set<String> s = new HashSet<>(Arrays.asList(TableDescriptorBuilder.REGION_REPLICATION, 142 TableDescriptorBuilder.REGION_MEMSTORE_REPLICATION, RSGroupInfo.TABLE_DESC_PROP_GROUP)); 143 for (String k : s) { 144 if ( 145 isTablePropertyModified(this.unmodifiedTableDescriptor, this.modifiedTableDescriptor, k) 146 ) { 147 throw new HBaseIOException( 148 "Can not modify " + k + " of a table when modification won't reopen regions"); 149 } 150 } 151 } 152 } 153 154 /** 155 * Comparing the value associated with a given key across two TableDescriptor instances' 156 * properties. 157 * @return True if the table property <code>key</code> is the same in both. 158 */ 159 private boolean isTablePropertyModified(TableDescriptor oldDescriptor, 160 TableDescriptor newDescriptor, String key) { 161 String oldV = oldDescriptor.getValue(key); 162 String newV = newDescriptor.getValue(key); 163 if (oldV == null && newV == null) { 164 return false; 165 } else if (oldV != null && newV != null && oldV.equals(newV)) { 166 return false; 167 } 168 return true; 169 } 170 171 private void initialize(final TableDescriptor unmodifiedTableDescriptor, 172 final boolean shouldCheckDescriptor) { 173 this.unmodifiedTableDescriptor = unmodifiedTableDescriptor; 174 this.shouldCheckDescriptor = shouldCheckDescriptor; 175 this.deleteColumnFamilyInModify = false; 176 } 177 178 @Override 179 protected Flow executeFromState(final MasterProcedureEnv env, final ModifyTableState state) 180 throws InterruptedException { 181 LOG.trace("{} execute state={}", this, state); 182 try { 183 switch (state) { 184 case MODIFY_TABLE_PREPARE: 185 prepareModify(env); 186 setNextState(ModifyTableState.MODIFY_TABLE_PRE_OPERATION); 187 break; 188 case MODIFY_TABLE_PRE_OPERATION: 189 preModify(env, state); 190 // We cannot allow changes to region replicas when 'reopenRegions==false', 191 // as this mode bypasses the state management required for modifying region replicas. 192 if (reopenRegions) { 193 // Check if we should create a recovery snapshot for column family deletion 194 if (deleteColumnFamilyInModify && RecoverySnapshotUtils.isRecoveryEnabled(env)) { 195 setNextState(ModifyTableState.MODIFY_TABLE_SNAPSHOT); 196 } else { 197 setNextState(ModifyTableState.MODIFY_TABLE_CLOSE_EXCESS_REPLICAS); 198 } 199 } else { 200 setNextState(ModifyTableState.MODIFY_TABLE_UPDATE_TABLE_DESCRIPTOR); 201 } 202 break; 203 case MODIFY_TABLE_SNAPSHOT: 204 // Create recovery snapshot procedure as child procedure 205 recoverySnapshotName = RecoverySnapshotUtils.generateSnapshotName(getTableName()); 206 SnapshotProcedure snapshotProcedure = RecoverySnapshotUtils.createSnapshotProcedure(env, 207 getTableName(), recoverySnapshotName, unmodifiedTableDescriptor); 208 // Submit snapshot procedure as child procedure 209 addChildProcedure(snapshotProcedure); 210 LOG.debug("Creating recovery snapshot {} for table {} before column deletion", 211 recoverySnapshotName, getTableName()); 212 setNextState(ModifyTableState.MODIFY_TABLE_CLOSE_EXCESS_REPLICAS); 213 break; 214 case MODIFY_TABLE_CLOSE_EXCESS_REPLICAS: 215 if (isTableEnabled(env)) { 216 closeExcessReplicasIfNeeded(env); 217 } 218 setNextState(ModifyTableState.MODIFY_TABLE_UPDATE_TABLE_DESCRIPTOR); 219 break; 220 case MODIFY_TABLE_UPDATE_TABLE_DESCRIPTOR: 221 updateTableDescriptor(env); 222 if (reopenRegions) { 223 setNextState(ModifyTableState.MODIFY_TABLE_REMOVE_REPLICA_COLUMN); 224 } else { 225 setNextState(ModifyTableState.MODIFY_TABLE_POST_OPERATION); 226 } 227 break; 228 case MODIFY_TABLE_REMOVE_REPLICA_COLUMN: 229 removeReplicaColumnsIfNeeded(env); 230 setNextState(ModifyTableState.MODIFY_TABLE_POST_OPERATION); 231 break; 232 case MODIFY_TABLE_POST_OPERATION: 233 postModify(env, state); 234 if (reopenRegions) { 235 setNextState(ModifyTableState.MODIFY_TABLE_REOPEN_ALL_REGIONS); 236 } else 237 if (ErasureCodingUtils.needsSync(unmodifiedTableDescriptor, modifiedTableDescriptor)) { 238 setNextState(ModifyTableState.MODIFY_TABLE_SYNC_ERASURE_CODING_POLICY); 239 } else { 240 return Flow.NO_MORE_STATE; 241 } 242 break; 243 case MODIFY_TABLE_REOPEN_ALL_REGIONS: 244 if (isTableEnabled(env)) { 245 addChildProcedure(ReopenTableRegionsProcedure.throttled(env.getMasterConfiguration(), 246 env.getMasterServices().getTableDescriptors().get(getTableName()))); 247 } 248 setNextState(ModifyTableState.MODIFY_TABLE_ASSIGN_NEW_REPLICAS); 249 break; 250 case MODIFY_TABLE_ASSIGN_NEW_REPLICAS: 251 assignNewReplicasIfNeeded(env); 252 if (TableName.isMetaTableName(getTableName())) { 253 MetaLocationSyncer syncer = env.getMasterServices().getMetaLocationSyncer(); 254 if (syncer != null) { 255 syncer.setMetaReplicaCount(modifiedTableDescriptor.getRegionReplication()); 256 } 257 } 258 if (deleteColumnFamilyInModify) { 259 setNextState(ModifyTableState.MODIFY_TABLE_DELETE_FS_LAYOUT); 260 } else 261 if (ErasureCodingUtils.needsSync(unmodifiedTableDescriptor, modifiedTableDescriptor)) { 262 setNextState(ModifyTableState.MODIFY_TABLE_SYNC_ERASURE_CODING_POLICY); 263 } else { 264 return Flow.NO_MORE_STATE; 265 } 266 break; 267 case MODIFY_TABLE_DELETE_FS_LAYOUT: 268 deleteFromFs(env, unmodifiedTableDescriptor, modifiedTableDescriptor); 269 if (ErasureCodingUtils.needsSync(unmodifiedTableDescriptor, modifiedTableDescriptor)) { 270 setNextState(ModifyTableState.MODIFY_TABLE_SYNC_ERASURE_CODING_POLICY); 271 break; 272 } else { 273 return Flow.NO_MORE_STATE; 274 } 275 case MODIFY_TABLE_SYNC_ERASURE_CODING_POLICY: 276 ErasureCodingUtils.sync(env.getMasterFileSystem().getFileSystem(), 277 env.getMasterFileSystem().getRootDir(), modifiedTableDescriptor); 278 return Flow.NO_MORE_STATE; 279 default: 280 throw new UnsupportedOperationException("unhandled state=" + state); 281 } 282 } catch (IOException e) { 283 if (isRollbackSupported(state)) { 284 setFailure("master-modify-table", e); 285 } else { 286 LOG.warn("Retriable error trying to modify table={} (in state={})", getTableName(), state, 287 e); 288 } 289 } 290 return Flow.HAS_MORE_STATE; 291 } 292 293 @Override 294 protected void rollbackState(final MasterProcedureEnv env, final ModifyTableState state) 295 throws IOException { 296 switch (state) { 297 case MODIFY_TABLE_PREPARE: 298 case MODIFY_TABLE_PRE_OPERATION: 299 // Nothing to roll back. 300 // TODO: Coprocessor rollback semantic is still undefined. 301 break; 302 case MODIFY_TABLE_SNAPSHOT: 303 // Handle recovery snapshot rollback. There is no DeleteSnapshotProcedure as such to use 304 // here directly as a child procedure, so we call a utility method to delete the snapshot 305 // which uses the SnapshotManager to delete the snapshot. 306 if (recoverySnapshotName != null) { 307 RecoverySnapshotUtils.deleteRecoverySnapshot(env, recoverySnapshotName, getTableName()); 308 recoverySnapshotName = null; 309 } 310 break; 311 default: 312 // Modify from other states doesn't have a rollback. The execution will succeed, at some 313 // point. 314 throw new UnsupportedOperationException("unhandled state=" + state); 315 } 316 } 317 318 @Override 319 protected boolean isRollbackSupported(final ModifyTableState state) { 320 switch (state) { 321 case MODIFY_TABLE_PREPARE: 322 case MODIFY_TABLE_PRE_OPERATION: 323 case MODIFY_TABLE_SNAPSHOT: 324 case MODIFY_TABLE_CLOSE_EXCESS_REPLICAS: 325 return true; 326 default: 327 return false; 328 } 329 } 330 331 @Override 332 protected void completionCleanup(final MasterProcedureEnv env) { 333 releaseSyncLatch(); 334 } 335 336 @Override 337 protected ModifyTableState getState(final int stateId) { 338 return ModifyTableState.forNumber(stateId); 339 } 340 341 @Override 342 protected int getStateId(final ModifyTableState state) { 343 return state.getNumber(); 344 } 345 346 @Override 347 protected ModifyTableState getInitialState() { 348 return ModifyTableState.MODIFY_TABLE_PREPARE; 349 } 350 351 @Override 352 protected void serializeStateData(ProcedureStateSerializer serializer) throws IOException { 353 super.serializeStateData(serializer); 354 355 MasterProcedureProtos.ModifyTableStateData.Builder modifyTableMsg = 356 MasterProcedureProtos.ModifyTableStateData.newBuilder() 357 .setUserInfo(MasterProcedureUtil.toProtoUserInfo(getUser())) 358 .setModifiedTableSchema(ProtobufUtil.toTableSchema(modifiedTableDescriptor)) 359 .setDeleteColumnFamilyInModify(deleteColumnFamilyInModify) 360 .setShouldCheckDescriptor(shouldCheckDescriptor).setReopenRegions(reopenRegions); 361 362 if (unmodifiedTableDescriptor != null) { 363 modifyTableMsg 364 .setUnmodifiedTableSchema(ProtobufUtil.toTableSchema(unmodifiedTableDescriptor)); 365 } 366 367 if (recoverySnapshotName != null) { 368 modifyTableMsg.setSnapshotName(recoverySnapshotName); 369 } 370 371 serializer.serialize(modifyTableMsg.build()); 372 } 373 374 @Override 375 protected void deserializeStateData(ProcedureStateSerializer serializer) throws IOException { 376 super.deserializeStateData(serializer); 377 378 MasterProcedureProtos.ModifyTableStateData modifyTableMsg = 379 serializer.deserialize(MasterProcedureProtos.ModifyTableStateData.class); 380 setUser(MasterProcedureUtil.toUserInfo(modifyTableMsg.getUserInfo())); 381 modifiedTableDescriptor = 382 ProtobufUtil.toTableDescriptor(modifyTableMsg.getModifiedTableSchema()); 383 deleteColumnFamilyInModify = modifyTableMsg.getDeleteColumnFamilyInModify(); 384 shouldCheckDescriptor = 385 modifyTableMsg.hasShouldCheckDescriptor() ? modifyTableMsg.getShouldCheckDescriptor() : false; 386 reopenRegions = modifyTableMsg.hasReopenRegions() ? modifyTableMsg.getReopenRegions() : true; 387 388 if (modifyTableMsg.hasUnmodifiedTableSchema()) { 389 unmodifiedTableDescriptor = 390 ProtobufUtil.toTableDescriptor(modifyTableMsg.getUnmodifiedTableSchema()); 391 } 392 393 if (modifyTableMsg.hasSnapshotName()) { 394 recoverySnapshotName = modifyTableMsg.getSnapshotName(); 395 } 396 } 397 398 @Override 399 public TableName getTableName() { 400 return modifiedTableDescriptor.getTableName(); 401 } 402 403 @Override 404 public TableOperationType getTableOperationType() { 405 return TableOperationType.EDIT; 406 } 407 408 /** 409 * Check conditions before any real action of modifying a table. 410 */ 411 private void prepareModify(final MasterProcedureEnv env) throws IOException { 412 // Checks whether the table exists 413 if (!env.getMasterServices().getTableDescriptors().exists(getTableName())) { 414 throw new TableNotFoundException(getTableName()); 415 } 416 417 // check that we have at least 1 CF 418 if (modifiedTableDescriptor.getColumnFamilyCount() == 0) { 419 throw new DoNotRetryIOException( 420 "Table " + getTableName().toString() + " should have at least one column family."); 421 } 422 423 // If descriptor check is enabled, check whether the table descriptor when procedure was 424 // submitted matches with the current 425 // table descriptor of the table, else retrieve the old descriptor 426 // for comparison in order to update the descriptor. 427 if (shouldCheckDescriptor) { 428 if ( 429 TableDescriptor.COMPARATOR.compare(unmodifiedTableDescriptor, 430 env.getMasterServices().getTableDescriptors().get(getTableName())) != 0 431 ) { 432 LOG.error("Error while modifying table '" + getTableName().toString() 433 + "' Skipping procedure : " + this); 434 throw new ConcurrentTableModificationException( 435 "Skipping modify table operation on table '" + getTableName().toString() 436 + "' as it has already been modified by some other concurrent operation, " 437 + "Please retry."); 438 } 439 } else { 440 this.unmodifiedTableDescriptor = 441 env.getMasterServices().getTableDescriptors().get(getTableName()); 442 } 443 444 this.deleteColumnFamilyInModify = 445 isDeleteColumnFamily(unmodifiedTableDescriptor, modifiedTableDescriptor); 446 if ( 447 !unmodifiedTableDescriptor.getRegionServerGroup() 448 .equals(modifiedTableDescriptor.getRegionServerGroup()) 449 ) { 450 Supplier<String> forWhom = () -> "table " + getTableName(); 451 RSGroupInfo rsGroupInfo = MasterProcedureUtil.checkGroupExists( 452 env.getMasterServices().getRSGroupInfoManager()::getRSGroup, 453 modifiedTableDescriptor.getRegionServerGroup(), forWhom); 454 MasterProcedureUtil.checkGroupNotEmpty(rsGroupInfo, forWhom); 455 } 456 457 // check for store file tracker configurations 458 StoreFileTrackerValidationUtils.checkForModifyTable(env.getMasterConfiguration(), 459 unmodifiedTableDescriptor, modifiedTableDescriptor, !isTableEnabled(env)); 460 } 461 462 /** 463 * Find out whether all column families in unmodifiedTableDescriptor also exists in the 464 * modifiedTableDescriptor. 465 * @return True if we are deleting a column family. 466 */ 467 private static boolean isDeleteColumnFamily(TableDescriptor originalDescriptor, 468 TableDescriptor newDescriptor) { 469 boolean result = false; 470 final Set<byte[]> originalFamilies = originalDescriptor.getColumnFamilyNames(); 471 final Set<byte[]> newFamilies = newDescriptor.getColumnFamilyNames(); 472 for (byte[] familyName : originalFamilies) { 473 if (!newFamilies.contains(familyName)) { 474 result = true; 475 break; 476 } 477 } 478 return result; 479 } 480 481 /** 482 * Action before modifying table. 483 * @param env MasterProcedureEnv 484 * @param state the procedure state 485 */ 486 private void preModify(final MasterProcedureEnv env, final ModifyTableState state) 487 throws IOException, InterruptedException { 488 runCoprocessorAction(env, state); 489 } 490 491 /** 492 * Update descriptor 493 * @param env MasterProcedureEnv 494 **/ 495 private void updateTableDescriptor(final MasterProcedureEnv env) throws IOException { 496 env.getMasterServices().getTableDescriptors().update(modifiedTableDescriptor); 497 } 498 499 /** 500 * Removes from hdfs the families that are not longer present in the new table descriptor. 501 * @param env MasterProcedureEnv 502 */ 503 private void deleteFromFs(final MasterProcedureEnv env, final TableDescriptor oldTableDescriptor, 504 final TableDescriptor newTableDescriptor) throws IOException { 505 final Set<byte[]> oldFamilies = oldTableDescriptor.getColumnFamilyNames(); 506 final Set<byte[]> newFamilies = newTableDescriptor.getColumnFamilyNames(); 507 for (byte[] familyName : oldFamilies) { 508 if (!newFamilies.contains(familyName)) { 509 MasterDDLOperationHelper.deleteColumnFamilyFromFileSystem(env, getTableName(), 510 getRegionInfoList(env), familyName, 511 oldTableDescriptor.getColumnFamily(familyName).isMobEnabled()); 512 } 513 } 514 } 515 516 /** 517 * remove replica columns if necessary. 518 */ 519 private void removeReplicaColumnsIfNeeded(MasterProcedureEnv env) throws IOException { 520 final int oldReplicaCount = unmodifiedTableDescriptor.getRegionReplication(); 521 final int newReplicaCount = modifiedTableDescriptor.getRegionReplication(); 522 if (newReplicaCount >= oldReplicaCount) { 523 return; 524 } 525 env.getAssignmentManager().getRegionStateStore().removeRegionReplicas(getTableName(), 526 oldReplicaCount, newReplicaCount); 527 env.getAssignmentManager().getRegionStates().getRegionsOfTable(getTableName()).stream() 528 .filter(r -> r.getReplicaId() >= newReplicaCount) 529 .forEach(env.getAssignmentManager().getRegionStates()::deleteRegion); 530 } 531 532 private void assignNewReplicasIfNeeded(MasterProcedureEnv env) throws IOException { 533 final int oldReplicaCount = unmodifiedTableDescriptor.getRegionReplication(); 534 final int newReplicaCount = modifiedTableDescriptor.getRegionReplication(); 535 if (newReplicaCount <= oldReplicaCount) { 536 return; 537 } 538 if (isTableEnabled(env)) { 539 List<RegionInfo> newReplicas = env.getAssignmentManager().getRegionStates() 540 .getRegionsOfTable(getTableName()).stream().filter(RegionReplicaUtil::isDefaultReplica) 541 .flatMap(primaryRegion -> IntStream.range(oldReplicaCount, newReplicaCount).mapToObj( 542 replicaId -> RegionReplicaUtil.getRegionInfoForReplica(primaryRegion, replicaId))) 543 .collect(Collectors.toList()); 544 addChildProcedure(env.getAssignmentManager().createAssignProcedures(newReplicas)); 545 } 546 } 547 548 private void closeExcessReplicasIfNeeded(MasterProcedureEnv env) { 549 final int oldReplicaCount = unmodifiedTableDescriptor.getRegionReplication(); 550 final int newReplicaCount = modifiedTableDescriptor.getRegionReplication(); 551 if (newReplicaCount >= oldReplicaCount) { 552 return; 553 } 554 addChildProcedure(new CloseExcessRegionReplicasProcedure(getTableName(), newReplicaCount)); 555 } 556 557 /** 558 * Action after modifying table. 559 * @param env MasterProcedureEnv 560 * @param state the procedure state 561 */ 562 private void postModify(final MasterProcedureEnv env, final ModifyTableState state) 563 throws IOException, InterruptedException { 564 runCoprocessorAction(env, state); 565 } 566 567 /** 568 * Coprocessor Action. 569 * @param env MasterProcedureEnv 570 * @param state the procedure state 571 */ 572 private void runCoprocessorAction(final MasterProcedureEnv env, final ModifyTableState state) 573 throws IOException, InterruptedException { 574 final MasterCoprocessorHost cpHost = env.getMasterCoprocessorHost(); 575 if (cpHost != null) { 576 switch (state) { 577 case MODIFY_TABLE_PRE_OPERATION: 578 cpHost.preModifyTableAction(getTableName(), unmodifiedTableDescriptor, 579 modifiedTableDescriptor, getUser()); 580 break; 581 case MODIFY_TABLE_POST_OPERATION: 582 cpHost.postCompletedModifyTableAction(getTableName(), unmodifiedTableDescriptor, 583 modifiedTableDescriptor, getUser()); 584 break; 585 default: 586 throw new UnsupportedOperationException(this + " unhandled state=" + state); 587 } 588 } 589 } 590 591 /** 592 * Fetches all Regions for a table. Cache the result of this method if you need to use it multiple 593 * times. Be aware that it may change over in between calls to this procedure. 594 */ 595 private List<RegionInfo> getRegionInfoList(final MasterProcedureEnv env) throws IOException { 596 return env.getAssignmentManager().getRegionStates().getRegionsOfTable(getTableName()); 597 } 598}