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}