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