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 com.google.errorprone.annotations.RestrictedApi;
021import java.io.IOException;
022import java.util.ArrayList;
023import java.util.HashMap;
024import java.util.Iterator;
025import java.util.List;
026import java.util.Map;
027import org.apache.hadoop.conf.Configuration;
028import org.apache.hadoop.fs.FileSystem;
029import org.apache.hadoop.fs.Path;
030import org.apache.hadoop.hbase.DoNotRetryIOException;
031import org.apache.hadoop.hbase.HBaseIOException;
032import org.apache.hadoop.hbase.MetaTableAccessor;
033import org.apache.hadoop.hbase.TableName;
034import org.apache.hadoop.hbase.TableNotFoundException;
035import org.apache.hadoop.hbase.client.Connection;
036import org.apache.hadoop.hbase.client.RegionInfo;
037import org.apache.hadoop.hbase.client.RegionReplicaUtil;
038import org.apache.hadoop.hbase.client.TableDescriptor;
039import org.apache.hadoop.hbase.errorhandling.ForeignException;
040import org.apache.hadoop.hbase.errorhandling.ForeignExceptionDispatcher;
041import org.apache.hadoop.hbase.favored.FavoredNodesManager;
042import org.apache.hadoop.hbase.master.MasterFileSystem;
043import org.apache.hadoop.hbase.master.MetricsSnapshot;
044import org.apache.hadoop.hbase.master.RegionState;
045import org.apache.hadoop.hbase.master.assignment.AssignmentManager;
046import org.apache.hadoop.hbase.master.assignment.RegionStateStore;
047import org.apache.hadoop.hbase.monitoring.MonitoredTask;
048import org.apache.hadoop.hbase.monitoring.TaskMonitor;
049import org.apache.hadoop.hbase.procedure2.ProcedureStateSerializer;
050import org.apache.hadoop.hbase.snapshot.ClientSnapshotDescriptionUtils;
051import org.apache.hadoop.hbase.snapshot.RestoreSnapshotHelper;
052import org.apache.hadoop.hbase.snapshot.SnapshotDescriptionUtils;
053import org.apache.hadoop.hbase.snapshot.SnapshotManifest;
054import org.apache.hadoop.hbase.snapshot.SnapshotTTLExpiredException;
055import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
056import org.apache.hadoop.hbase.util.Pair;
057import org.apache.yetus.audience.InterfaceAudience;
058import org.slf4j.Logger;
059import org.slf4j.LoggerFactory;
060
061import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil;
062import org.apache.hadoop.hbase.shaded.protobuf.generated.HBaseProtos;
063import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProcedureProtos.RestoreParentToChildRegionsPair;
064import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProcedureProtos.RestoreSnapshotState;
065import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProcedureProtos.RestoreSnapshotStateData;
066import org.apache.hadoop.hbase.shaded.protobuf.generated.SnapshotProtos.SnapshotDescription;
067
068@InterfaceAudience.Private
069public class RestoreSnapshotProcedure
070  extends AbstractStateMachineTableProcedure<RestoreSnapshotState> {
071  private static final Logger LOG = LoggerFactory.getLogger(RestoreSnapshotProcedure.class);
072
073  private TableDescriptor modifiedTableDescriptor;
074  private List<RegionInfo> regionsToRestore = null;
075  private List<RegionInfo> regionsToRemove = null;
076  private List<RegionInfo> regionsToAdd = null;
077  private Map<String, Pair<String, String>> parentsToChildrenPairMap = new HashMap<>();
078
079  private SnapshotDescription snapshot;
080  private boolean restoreAcl;
081
082  // Monitor
083  private MonitoredTask monitorStatus = null;
084
085  /**
086   * Constructor (for failover)
087   */
088  public RestoreSnapshotProcedure() {
089  }
090
091  public RestoreSnapshotProcedure(final MasterProcedureEnv env,
092    final TableDescriptor tableDescriptor, final SnapshotDescription snapshot)
093    throws HBaseIOException {
094    this(env, tableDescriptor, snapshot, false);
095  }
096
097  /**
098   * Constructor
099   * @param env             MasterProcedureEnv
100   * @param tableDescriptor the table to operate on
101   * @param snapshot        snapshot to restore from
102   */
103  public RestoreSnapshotProcedure(final MasterProcedureEnv env,
104    final TableDescriptor tableDescriptor, final SnapshotDescription snapshot,
105    final boolean restoreAcl) throws HBaseIOException {
106    super(env);
107    // This is the new schema we are going to write out as this modification.
108    this.modifiedTableDescriptor = tableDescriptor;
109    preflightChecks(env, null/* Table can be online when restore is called? */);
110    // Snapshot information
111    this.snapshot = snapshot;
112    this.restoreAcl = restoreAcl;
113
114    // Monitor
115    getMonitorStatus();
116  }
117
118  /**
119   * Set up monitor status if it is not created.
120   */
121  private MonitoredTask getMonitorStatus() {
122    if (monitorStatus == null) {
123      monitorStatus = TaskMonitor.get().createStatus(
124        "Restoring  snapshot '" + snapshot.getName() + "' to table " + getTableName());
125    }
126    return monitorStatus;
127  }
128
129  @Override
130  protected Flow executeFromState(final MasterProcedureEnv env, final RestoreSnapshotState state)
131    throws InterruptedException {
132    LOG.trace("{} execute state={}", this, state);
133
134    // Make sure that the monitor status is set up
135    getMonitorStatus();
136
137    try {
138      switch (state) {
139        case RESTORE_SNAPSHOT_PRE_OPERATION:
140          // Verify if we can restore the table
141          prepareRestore(env);
142          setNextState(RestoreSnapshotState.RESTORE_SNAPSHOT_UPDATE_TABLE_DESCRIPTOR);
143          break;
144        case RESTORE_SNAPSHOT_UPDATE_TABLE_DESCRIPTOR:
145          updateTableDescriptor(env);
146          setNextState(RestoreSnapshotState.RESTORE_SNAPSHOT_WRITE_FS_LAYOUT);
147          break;
148        case RESTORE_SNAPSHOT_WRITE_FS_LAYOUT:
149          restoreSnapshot(env);
150          setNextState(RestoreSnapshotState.RESTORE_SNAPSHOT_UPDATE_META);
151          break;
152        case RESTORE_SNAPSHOT_UPDATE_META:
153          updateMETA(env);
154          setNextState(RestoreSnapshotState.RESTORE_SNAPSHOT_RESTORE_ACL);
155          break;
156        case RESTORE_SNAPSHOT_RESTORE_ACL:
157          restoreSnapshotAcl(env);
158          return Flow.NO_MORE_STATE;
159        default:
160          throw new UnsupportedOperationException("unhandled state=" + state);
161      }
162    } catch (IOException e) {
163      if (isRollbackSupported(state)) {
164        setFailure("master-restore-snapshot", e);
165      } else {
166        LOG.warn("Retriable error trying to restore snapshot=" + snapshot.getName() + " to table="
167          + getTableName() + " (in state=" + state + ")", e);
168      }
169    }
170    return Flow.HAS_MORE_STATE;
171  }
172
173  @Override
174  protected void rollbackState(final MasterProcedureEnv env, final RestoreSnapshotState state)
175    throws IOException {
176    if (state == RestoreSnapshotState.RESTORE_SNAPSHOT_PRE_OPERATION) {
177      // nothing to rollback
178      return;
179    }
180
181    // The restore snapshot doesn't have a rollback. The execution will succeed, at some point.
182    throw new UnsupportedOperationException("unhandled state=" + state);
183  }
184
185  @Override
186  protected boolean isRollbackSupported(final RestoreSnapshotState state) {
187    switch (state) {
188      case RESTORE_SNAPSHOT_PRE_OPERATION:
189        return true;
190      default:
191        return false;
192    }
193  }
194
195  @Override
196  protected RestoreSnapshotState getState(final int stateId) {
197    return RestoreSnapshotState.valueOf(stateId);
198  }
199
200  @Override
201  protected int getStateId(final RestoreSnapshotState state) {
202    return state.getNumber();
203  }
204
205  @Override
206  protected RestoreSnapshotState getInitialState() {
207    return RestoreSnapshotState.RESTORE_SNAPSHOT_PRE_OPERATION;
208  }
209
210  @Override
211  public TableName getTableName() {
212    return modifiedTableDescriptor.getTableName();
213  }
214
215  @Override
216  public TableOperationType getTableOperationType() {
217    return TableOperationType.EDIT; // Restore is modifying a table
218  }
219
220  @Override
221  public boolean abort(final MasterProcedureEnv env) {
222    // TODO: We may be able to abort if the procedure is not started yet.
223    return false;
224  }
225
226  @Override
227  public void toStringClassDetails(StringBuilder sb) {
228    sb.append(getClass().getSimpleName());
229    sb.append(" (table=");
230    sb.append(getTableName());
231    sb.append(" snapshot=");
232    sb.append(snapshot);
233    sb.append(")");
234  }
235
236  @Override
237  protected void serializeStateData(ProcedureStateSerializer serializer) throws IOException {
238    super.serializeStateData(serializer);
239
240    RestoreSnapshotStateData.Builder restoreSnapshotMsg = RestoreSnapshotStateData.newBuilder()
241      .setUserInfo(MasterProcedureUtil.toProtoUserInfo(getUser())).setSnapshot(this.snapshot)
242      .setModifiedTableSchema(ProtobufUtil.toTableSchema(modifiedTableDescriptor));
243
244    if (regionsToRestore != null) {
245      for (RegionInfo hri : regionsToRestore) {
246        restoreSnapshotMsg.addRegionInfoForRestore(ProtobufUtil.toRegionInfo(hri));
247      }
248    }
249    if (regionsToRemove != null) {
250      for (RegionInfo hri : regionsToRemove) {
251        restoreSnapshotMsg.addRegionInfoForRemove(ProtobufUtil.toRegionInfo(hri));
252      }
253    }
254    if (regionsToAdd != null) {
255      for (RegionInfo hri : regionsToAdd) {
256        restoreSnapshotMsg.addRegionInfoForAdd(ProtobufUtil.toRegionInfo(hri));
257      }
258    }
259    if (!parentsToChildrenPairMap.isEmpty()) {
260      final Iterator<Map.Entry<String, Pair<String, String>>> it =
261        parentsToChildrenPairMap.entrySet().iterator();
262      while (it.hasNext()) {
263        final Map.Entry<String, Pair<String, String>> entry = it.next();
264
265        RestoreParentToChildRegionsPair.Builder parentToChildrenPair =
266          RestoreParentToChildRegionsPair.newBuilder().setParentRegionName(entry.getKey())
267            .setChild1RegionName(entry.getValue().getFirst())
268            .setChild2RegionName(entry.getValue().getSecond());
269        restoreSnapshotMsg.addParentToChildRegionsPairList(parentToChildrenPair);
270      }
271    }
272    restoreSnapshotMsg.setRestoreAcl(restoreAcl);
273    serializer.serialize(restoreSnapshotMsg.build());
274  }
275
276  @Override
277  protected void deserializeStateData(ProcedureStateSerializer serializer) throws IOException {
278    super.deserializeStateData(serializer);
279
280    RestoreSnapshotStateData restoreSnapshotMsg =
281      serializer.deserialize(RestoreSnapshotStateData.class);
282    setUser(MasterProcedureUtil.toUserInfo(restoreSnapshotMsg.getUserInfo()));
283    snapshot = restoreSnapshotMsg.getSnapshot();
284    modifiedTableDescriptor =
285      ProtobufUtil.toTableDescriptor(restoreSnapshotMsg.getModifiedTableSchema());
286
287    if (restoreSnapshotMsg.getRegionInfoForRestoreCount() == 0) {
288      regionsToRestore = null;
289    } else {
290      regionsToRestore = new ArrayList<>(restoreSnapshotMsg.getRegionInfoForRestoreCount());
291      for (HBaseProtos.RegionInfo hri : restoreSnapshotMsg.getRegionInfoForRestoreList()) {
292        regionsToRestore.add(ProtobufUtil.toRegionInfo(hri));
293      }
294    }
295    if (restoreSnapshotMsg.getRegionInfoForRemoveCount() == 0) {
296      regionsToRemove = null;
297    } else {
298      regionsToRemove = new ArrayList<>(restoreSnapshotMsg.getRegionInfoForRemoveCount());
299      for (HBaseProtos.RegionInfo hri : restoreSnapshotMsg.getRegionInfoForRemoveList()) {
300        regionsToRemove.add(ProtobufUtil.toRegionInfo(hri));
301      }
302    }
303    if (restoreSnapshotMsg.getRegionInfoForAddCount() == 0) {
304      regionsToAdd = null;
305    } else {
306      regionsToAdd = new ArrayList<>(restoreSnapshotMsg.getRegionInfoForAddCount());
307      for (HBaseProtos.RegionInfo hri : restoreSnapshotMsg.getRegionInfoForAddList()) {
308        regionsToAdd.add(ProtobufUtil.toRegionInfo(hri));
309      }
310    }
311    if (restoreSnapshotMsg.getParentToChildRegionsPairListCount() > 0) {
312      for (RestoreParentToChildRegionsPair parentToChildrenPair : restoreSnapshotMsg
313        .getParentToChildRegionsPairListList()) {
314        parentsToChildrenPairMap.put(parentToChildrenPair.getParentRegionName(), new Pair<>(
315          parentToChildrenPair.getChild1RegionName(), parentToChildrenPair.getChild2RegionName()));
316      }
317    }
318    if (restoreSnapshotMsg.hasRestoreAcl()) {
319      restoreAcl = restoreSnapshotMsg.getRestoreAcl();
320    }
321  }
322
323  /**
324   * Action before any real action of restoring from snapshot.
325   * @param env MasterProcedureEnv
326   */
327  private void prepareRestore(final MasterProcedureEnv env) throws IOException {
328    final TableName tableName = getTableName();
329    // Checks whether the table exists
330    if (!env.getMasterServices().getTableDescriptors().exists(tableName)) {
331      throw new TableNotFoundException(tableName);
332    }
333
334    // check whether ttl has expired for this snapshot
335    if (
336      SnapshotDescriptionUtils.isExpiredSnapshot(snapshot.getTtl(), snapshot.getCreationTime(),
337        EnvironmentEdgeManager.currentTime())
338    ) {
339      throw new SnapshotTTLExpiredException(ProtobufUtil.createSnapshotDesc(snapshot));
340    }
341
342    // Check whether table is disabled.
343    env.getMasterServices().checkTableModifiable(tableName);
344
345    // Check that we have at least 1 CF
346    if (modifiedTableDescriptor.getColumnFamilyCount() == 0) {
347      throw new DoNotRetryIOException(
348        "Table " + getTableName().toString() + " should have at least one column family.");
349    }
350
351    if (!getTableName().isSystemTable()) {
352      // Table already exist. Check and update the region quota for this table namespace.
353      final MasterFileSystem mfs = env.getMasterServices().getMasterFileSystem();
354      SnapshotManifest manifest =
355        SnapshotManifest.open(env.getMasterConfiguration(), mfs.getFileSystem(),
356          SnapshotDescriptionUtils.getCompletedSnapshotDir(snapshot, mfs.getRootDir()), snapshot);
357      int snapshotRegionCount = manifest.getRegionManifestsMap().size();
358      int tableRegionCount =
359        ProcedureSyncWait.getMasterQuotaManager(env).getRegionCountOfTable(tableName);
360
361      if (snapshotRegionCount > 0 && tableRegionCount != snapshotRegionCount) {
362        ProcedureSyncWait.getMasterQuotaManager(env).checkAndUpdateNamespaceRegionQuota(tableName,
363          snapshotRegionCount);
364      }
365    }
366  }
367
368  /**
369   * Update descriptor
370   * @param env MasterProcedureEnv
371   **/
372  private void updateTableDescriptor(final MasterProcedureEnv env) throws IOException {
373    env.getMasterServices().getTableDescriptors().update(modifiedTableDescriptor);
374  }
375
376  /**
377   * Execute the on-disk Restore
378   * @param env MasterProcedureEnv
379   **/
380  private void restoreSnapshot(final MasterProcedureEnv env) throws IOException {
381    MasterFileSystem fileSystemManager = env.getMasterServices().getMasterFileSystem();
382    FileSystem fs = fileSystemManager.getFileSystem();
383    Path rootDir = fileSystemManager.getRootDir();
384    final ForeignExceptionDispatcher monitorException = new ForeignExceptionDispatcher();
385    final Configuration conf = new Configuration(env.getMasterConfiguration());
386
387    LOG.info("Starting restore snapshot=" + ClientSnapshotDescriptionUtils.toString(snapshot));
388    try {
389      Path snapshotDir = SnapshotDescriptionUtils.getCompletedSnapshotDir(snapshot, rootDir);
390      SnapshotManifest manifest = SnapshotManifest.open(conf, fs, snapshotDir, snapshot);
391      RestoreSnapshotHelper restoreHelper = new RestoreSnapshotHelper(conf, fs, manifest,
392        modifiedTableDescriptor, rootDir, monitorException, getMonitorStatus());
393
394      RestoreSnapshotHelper.RestoreMetaChanges metaChanges = restoreHelper.restoreHdfsRegions();
395      regionsToRestore = metaChanges.getRegionsToRestore();
396      regionsToRemove = metaChanges.getRegionsToRemove();
397      regionsToAdd = metaChanges.getRegionsToAdd();
398      parentsToChildrenPairMap = metaChanges.getParentToChildrenPairMap();
399    } catch (IOException e) {
400      String msg = "restore snapshot=" + ClientSnapshotDescriptionUtils.toString(snapshot)
401        + " failed in on-disk restore. Try re-running the restore command.";
402      LOG.error(msg, e);
403      monitorException
404        .receive(new ForeignException(env.getMasterServices().getServerName().toString(), e));
405      throw new IOException(msg, e);
406    }
407  }
408
409  /**
410   * Apply changes to hbase:meta
411   **/
412  private void updateMETA(final MasterProcedureEnv env) throws IOException {
413    try {
414      Connection conn = env.getMasterServices().getConnection();
415      RegionStateStore regionStateStore = env.getAssignmentManager().getRegionStateStore();
416      int regionReplication = modifiedTableDescriptor.getRegionReplication();
417
418      // 1. Prepare to restore
419      getMonitorStatus().setStatus("Preparing to restore each region");
420
421      // 2. Applies changes to hbase:meta and in-memory states
422      // (2.1). Removes the current set of regions from META and in-memory states
423      //
424      // By removing also the regions to restore (the ones present both in the snapshot
425      // and in the current state) we ensure that no extra fields are present in META
426      // e.g. with a simple add addRegionToMeta() the splitA and splitB attributes
427      // not overwritten/removed, so you end up with old informations
428      // that are not correct after the restore.
429      if (regionsToRemove != null) {
430        regionStateStore.deleteRegions(regionsToRemove);
431        deleteRegionsFromInMemoryStates(regionsToRemove, env, regionReplication);
432      }
433
434      // (2.2). Add the new set of regions to META and in-memory states
435      //
436      // At this point the old regions are no longer present in META.
437      // and the set of regions present in the snapshot will be written to META.
438      // All the information in hbase:meta are coming from the .regioninfo of each region present
439      // in the snapshot folder.
440      if (regionsToAdd != null) {
441        MetaTableAccessor.addRegionsToMeta(conn, regionsToAdd, regionReplication);
442        addRegionsToInMemoryStates(regionsToAdd, env, regionReplication);
443      }
444
445      if (regionsToRestore != null) {
446        regionStateStore.overwriteRegions(regionsToRestore, regionReplication);
447
448        deleteRegionsFromInMemoryStates(regionsToRestore, env, regionReplication);
449        addRegionsToInMemoryStates(regionsToRestore, env, regionReplication);
450      }
451
452      RestoreSnapshotHelper.RestoreMetaChanges metaChanges =
453        new RestoreSnapshotHelper.RestoreMetaChanges(modifiedTableDescriptor,
454          parentsToChildrenPairMap);
455      metaChanges.updateMetaParentRegions(conn, regionsToAdd);
456
457      // At this point the restore is complete.
458      LOG.info("Restore snapshot=" + ClientSnapshotDescriptionUtils.toString(snapshot)
459        + " on table=" + getTableName() + " completed!");
460    } catch (IOException e) {
461      final ForeignExceptionDispatcher monitorException = new ForeignExceptionDispatcher();
462      String msg = "restore snapshot=" + ClientSnapshotDescriptionUtils.toString(snapshot)
463        + " failed in meta update. Try re-running the restore command.";
464      LOG.error(msg, e);
465      monitorException
466        .receive(new ForeignException(env.getMasterServices().getServerName().toString(), e));
467      throw new IOException(msg, e);
468    }
469
470    monitorStatus.markComplete("Restore snapshot '" + snapshot.getName() + "'!");
471    MetricsSnapshot metricsSnapshot = new MetricsSnapshot();
472    metricsSnapshot
473      .addSnapshotRestore(monitorStatus.getCompletionTimestamp() - monitorStatus.getStartTime());
474  }
475
476  /**
477   * Delete regions from in-memory states
478   * @param regionInfos       regions to delete
479   * @param env               MasterProcedureEnv
480   * @param regionReplication the number of region replications
481   */
482  private void deleteRegionsFromInMemoryStates(List<RegionInfo> regionInfos, MasterProcedureEnv env,
483    int regionReplication) {
484    FavoredNodesManager fnm = env.getMasterServices().getFavoredNodesManager();
485
486    env.getAssignmentManager().getRegionStates().deleteRegions(regionInfos);
487    env.getMasterServices().getServerManager().removeRegions(regionInfos);
488    if (fnm != null) {
489      fnm.deleteFavoredNodesForRegions(regionInfos);
490    }
491
492    // For region replicas
493    if (regionReplication > 1) {
494      for (RegionInfo regionInfo : regionInfos) {
495        for (int i = 1; i < regionReplication; i++) {
496          RegionInfo regionInfoForReplica =
497            RegionReplicaUtil.getRegionInfoForReplica(regionInfo, i);
498          env.getAssignmentManager().getRegionStates().deleteRegion(regionInfoForReplica);
499          env.getMasterServices().getServerManager().removeRegion(regionInfoForReplica);
500          if (fnm != null) {
501            fnm.deleteFavoredNodesForRegion(regionInfoForReplica);
502          }
503        }
504      }
505    }
506  }
507
508  /**
509   * Add regions to in-memory states
510   * @param regionInfos       regions to add
511   * @param env               MasterProcedureEnv
512   * @param regionReplication the number of region replications
513   */
514  private void addRegionsToInMemoryStates(List<RegionInfo> regionInfos, MasterProcedureEnv env,
515    int regionReplication) {
516    AssignmentManager am = env.getAssignmentManager();
517    for (RegionInfo regionInfo : regionInfos) {
518      if (regionInfo.isSplit()) {
519        am.getRegionStates().updateRegionState(regionInfo, RegionState.State.SPLIT);
520      } else {
521        am.getRegionStates().updateRegionState(regionInfo, RegionState.State.CLOSED);
522
523        // For region replicas
524        for (int i = 1; i < regionReplication; i++) {
525          RegionInfo regionInfoForReplica =
526            RegionReplicaUtil.getRegionInfoForReplica(regionInfo, i);
527          am.getRegionStates().updateRegionState(regionInfoForReplica, RegionState.State.CLOSED);
528        }
529      }
530    }
531  }
532
533  private void restoreSnapshotAcl(final MasterProcedureEnv env) throws IOException {
534    if (
535      restoreAcl && snapshot.hasUsersAndPermissions() && snapshot.getUsersAndPermissions() != null
536        && SnapshotDescriptionUtils.isSecurityAvailable(env.getMasterServices().getConfiguration())
537    ) {
538      // restore acl of snapshot to table.
539      RestoreSnapshotHelper.restoreSnapshotAcl(snapshot, TableName.valueOf(snapshot.getTable()),
540        env.getMasterServices().getConfiguration());
541    }
542  }
543
544  /**
545   * Exposed for Testing: HBASE-26462
546   */
547  @RestrictedApi(explanation = "Should only be called in tests", link = "",
548      allowedOnPath = ".*/src/test/.*")
549  public boolean getRestoreAcl() {
550    return restoreAcl;
551  }
552}