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