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