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.snapshot;
019
020import java.io.IOException;
021import java.io.InputStream;
022import java.io.OutputStream;
023import java.util.ArrayList;
024import java.util.Arrays;
025import java.util.Collections;
026import java.util.HashMap;
027import java.util.HashSet;
028import java.util.LinkedList;
029import java.util.List;
030import java.util.Map;
031import java.util.Map.Entry;
032import java.util.Set;
033import java.util.TreeMap;
034import java.util.concurrent.ThreadPoolExecutor;
035import org.apache.hadoop.conf.Configuration;
036import org.apache.hadoop.fs.FileStatus;
037import org.apache.hadoop.fs.FileSystem;
038import org.apache.hadoop.fs.Path;
039import org.apache.hadoop.hbase.MetaTableAccessor;
040import org.apache.hadoop.hbase.TableName;
041import org.apache.hadoop.hbase.backup.HFileArchiver;
042import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder;
043import org.apache.hadoop.hbase.client.Connection;
044import org.apache.hadoop.hbase.client.ConnectionFactory;
045import org.apache.hadoop.hbase.client.RegionInfo;
046import org.apache.hadoop.hbase.client.RegionInfoBuilder;
047import org.apache.hadoop.hbase.client.TableDescriptor;
048import org.apache.hadoop.hbase.errorhandling.ForeignExceptionDispatcher;
049import org.apache.hadoop.hbase.io.HFileLink;
050import org.apache.hadoop.hbase.io.Reference;
051import org.apache.hadoop.hbase.mob.MobUtils;
052import org.apache.hadoop.hbase.monitoring.MonitoredTask;
053import org.apache.hadoop.hbase.monitoring.TaskMonitor;
054import org.apache.hadoop.hbase.regionserver.HRegion;
055import org.apache.hadoop.hbase.regionserver.HRegionFileSystem;
056import org.apache.hadoop.hbase.regionserver.StoreContext;
057import org.apache.hadoop.hbase.regionserver.StoreFileInfo;
058import org.apache.hadoop.hbase.regionserver.StoreUtils;
059import org.apache.hadoop.hbase.regionserver.storefiletracker.StoreFileTracker;
060import org.apache.hadoop.hbase.regionserver.storefiletracker.StoreFileTrackerFactory;
061import org.apache.hadoop.hbase.security.access.AccessControlClient;
062import org.apache.hadoop.hbase.security.access.Permission;
063import org.apache.hadoop.hbase.security.access.ShadedAccessControlUtil;
064import org.apache.hadoop.hbase.security.access.TablePermission;
065import org.apache.hadoop.hbase.snapshot.RestoreSnapshotHelper.RestoreMetaChanges;
066import org.apache.hadoop.hbase.util.Bytes;
067import org.apache.hadoop.hbase.util.CommonFSUtils;
068import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
069import org.apache.hadoop.hbase.util.FSUtils;
070import org.apache.hadoop.hbase.util.ModifyRegionUtils;
071import org.apache.hadoop.hbase.util.Pair;
072import org.apache.hadoop.io.IOUtils;
073import org.apache.yetus.audience.InterfaceAudience;
074import org.slf4j.Logger;
075import org.slf4j.LoggerFactory;
076
077import org.apache.hbase.thirdparty.com.google.common.collect.ListMultimap;
078
079import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil;
080import org.apache.hadoop.hbase.shaded.protobuf.generated.SnapshotProtos.SnapshotDescription;
081import org.apache.hadoop.hbase.shaded.protobuf.generated.SnapshotProtos.SnapshotRegionManifest;
082
083/**
084 * Helper to Restore/Clone a Snapshot
085 * <p>
086 * The helper assumes that a table is already created, and by calling restore() the content present
087 * in the snapshot will be restored as the new content of the table.
088 * <p>
089 * Clone from Snapshot: If the target table is empty, the restore operation is just a "clone
090 * operation", where the only operations are:
091 * <ul>
092 * <li>for each region in the snapshot create a new region (note that the region will have a
093 * different name, since the encoding contains the table name)
094 * <li>for each file in the region create a new HFileLink to point to the original file.
095 * <li>restore the logs, if any
096 * </ul>
097 * <p>
098 * Restore from Snapshot:
099 * <ul>
100 * <li>for each region in the table verify which are available in the snapshot and which are not
101 * <ul>
102 * <li>if the region is not present in the snapshot, remove it.
103 * <li>if the region is present in the snapshot
104 * <ul>
105 * <li>for each file in the table region verify which are available in the snapshot
106 * <ul>
107 * <li>if the hfile is not present in the snapshot, remove it
108 * <li>if the hfile is present, keep it (nothing to do)
109 * </ul>
110 * <li>for each file in the snapshot region but not in the table
111 * <ul>
112 * <li>create a new HFileLink that point to the original file
113 * </ul>
114 * </ul>
115 * </ul>
116 * <li>for each region in the snapshot not present in the current table state
117 * <ul>
118 * <li>create a new region and for each file in the region create a new HFileLink (This is the same
119 * as the clone operation)
120 * </ul>
121 * <li>restore the logs, if any
122 * </ul>
123 */
124@InterfaceAudience.Private
125public class RestoreSnapshotHelper {
126  private static final Logger LOG = LoggerFactory.getLogger(RestoreSnapshotHelper.class);
127
128  private final Map<byte[], byte[]> regionsMap = new TreeMap<>(Bytes.BYTES_COMPARATOR);
129
130  private final Map<String, Pair<String, String>> parentsMap = new HashMap<>();
131
132  private final ForeignExceptionDispatcher monitor;
133  private final MonitoredTask status;
134
135  private final SnapshotManifest snapshotManifest;
136  private final SnapshotDescription snapshotDesc;
137  private final TableName snapshotTable;
138
139  private final TableDescriptor tableDesc;
140  private final Path rootDir;
141  private final Path tableDir;
142
143  private final Configuration conf;
144  private final FileSystem fs;
145  private final boolean createBackRefs;
146
147  public RestoreSnapshotHelper(final Configuration conf, final FileSystem fs,
148    final SnapshotManifest manifest, final TableDescriptor tableDescriptor, final Path rootDir,
149    final ForeignExceptionDispatcher monitor, final MonitoredTask status) {
150    this(conf, fs, manifest, tableDescriptor, rootDir, monitor, status, true);
151  }
152
153  public RestoreSnapshotHelper(final Configuration conf, final FileSystem fs,
154    final SnapshotManifest manifest, final TableDescriptor tableDescriptor, final Path rootDir,
155    final ForeignExceptionDispatcher monitor, final MonitoredTask status,
156    final boolean createBackRefs) {
157    this.fs = fs;
158    this.conf = conf;
159    this.snapshotManifest = manifest;
160    this.snapshotDesc = manifest.getSnapshotDescription();
161    this.snapshotTable = TableName.valueOf(snapshotDesc.getTable());
162    this.tableDesc = tableDescriptor;
163    this.rootDir = rootDir;
164    this.tableDir = CommonFSUtils.getTableDir(rootDir, tableDesc.getTableName());
165    this.monitor = monitor;
166    this.status = status;
167    this.createBackRefs = createBackRefs;
168  }
169
170  /**
171   * Restore the on-disk table to a specified snapshot state.
172   * @return the set of regions touched by the restore operation
173   */
174  public RestoreMetaChanges restoreHdfsRegions() throws IOException {
175    ThreadPoolExecutor exec = SnapshotManifest.createExecutor(conf, "RestoreSnapshot");
176    try {
177      return restoreHdfsRegions(exec);
178    } finally {
179      exec.shutdown();
180    }
181  }
182
183  private RestoreMetaChanges restoreHdfsRegions(final ThreadPoolExecutor exec) throws IOException {
184    LOG.info("starting restore table regions using snapshot=" + snapshotDesc);
185
186    Map<String, SnapshotRegionManifest> regionManifests = snapshotManifest.getRegionManifestsMap();
187    if (regionManifests == null) {
188      LOG.warn("Nothing to restore. Snapshot " + snapshotDesc + " looks empty");
189      return null;
190    }
191
192    RestoreMetaChanges metaChanges = new RestoreMetaChanges(tableDesc, parentsMap);
193
194    // Take a copy of the manifest.keySet() since we are going to modify
195    // this instance, by removing the regions already present in the restore dir.
196    Set<String> regionNames = new HashSet<>(regionManifests.keySet());
197
198    List<RegionInfo> tableRegions = getTableRegions();
199
200    RegionInfo mobRegion =
201      MobUtils.getMobRegionInfo(snapshotManifest.getTableDescriptor().getTableName());
202    if (tableRegions != null) {
203      // restore the mob region in case
204      if (regionNames.contains(mobRegion.getEncodedName())) {
205        monitor.rethrowException();
206        status.setStatus("Restoring mob region...");
207        List<RegionInfo> mobRegions = new ArrayList<>(1);
208        mobRegions.add(mobRegion);
209        restoreHdfsMobRegions(exec, regionManifests, mobRegions);
210        regionNames.remove(mobRegion.getEncodedName());
211        status.setStatus("Finished restoring mob region.");
212      }
213    }
214    if (regionNames.contains(mobRegion.getEncodedName())) {
215      // add the mob region
216      monitor.rethrowException();
217      status.setStatus("Cloning mob region...");
218      cloneHdfsMobRegion(regionManifests, mobRegion);
219      regionNames.remove(mobRegion.getEncodedName());
220      status.setStatus("Finished cloning mob region.");
221    }
222
223    // Identify which region are still available and which not.
224    // NOTE: we rely upon the region name as: "table name, start key, end key"
225    if (tableRegions != null) {
226      monitor.rethrowException();
227      for (RegionInfo regionInfo : tableRegions) {
228        String regionName = regionInfo.getEncodedName();
229        if (regionNames.contains(regionName)) {
230          LOG.info("region to restore: " + regionName);
231          regionNames.remove(regionName);
232          metaChanges.addRegionToRestore(
233            ProtobufUtil.toRegionInfo(regionManifests.get(regionName).getRegionInfo()));
234        } else {
235          LOG.info("region to remove: " + regionName);
236          metaChanges.addRegionToRemove(regionInfo);
237        }
238      }
239    }
240
241    // Regions to Add: present in the snapshot but not in the current table
242    List<RegionInfo> regionsToAdd = new ArrayList<>(regionNames.size());
243    if (regionNames.size() > 0) {
244      monitor.rethrowException();
245      for (String regionName : regionNames) {
246        LOG.info("region to add: " + regionName);
247        regionsToAdd
248          .add(ProtobufUtil.toRegionInfo(regionManifests.get(regionName).getRegionInfo()));
249      }
250    }
251
252    // Create new regions cloning from the snapshot
253    // HBASE-19980: We need to call cloneHdfsRegions() before restoreHdfsRegions() because
254    // regionsMap is constructed in cloneHdfsRegions() and it can be used in restoreHdfsRegions().
255    monitor.rethrowException();
256    status.setStatus("Cloning regions...");
257    RegionInfo[] clonedRegions = cloneHdfsRegions(exec, regionManifests, regionsToAdd);
258    metaChanges.setNewRegions(clonedRegions);
259    status.setStatus("Finished cloning regions.");
260
261    // Restore regions using the snapshot data
262    monitor.rethrowException();
263    status.setStatus("Restoring table regions...");
264    restoreHdfsRegions(exec, regionManifests, metaChanges.getRegionsToRestore());
265    status.setStatus("Finished restoring all table regions.");
266
267    // Remove regions from the current table
268    monitor.rethrowException();
269    status.setStatus("Starting to delete excess regions from table");
270    removeHdfsRegions(exec, metaChanges.getRegionsToRemove());
271    status.setStatus("Finished deleting excess regions from table.");
272
273    LOG.info("finishing restore table regions using snapshot=" + snapshotDesc);
274
275    return metaChanges;
276  }
277
278  /**
279   * Describe the set of operations needed to update hbase:meta after restore.
280   */
281  public static class RestoreMetaChanges {
282    private final Map<String, Pair<String, String>> parentsMap;
283    private final TableDescriptor htd;
284
285    private List<RegionInfo> regionsToRestore = null;
286    private List<RegionInfo> regionsToRemove = null;
287    private List<RegionInfo> regionsToAdd = null;
288
289    public RestoreMetaChanges(TableDescriptor htd, Map<String, Pair<String, String>> parentsMap) {
290      this.parentsMap = parentsMap;
291      this.htd = htd;
292    }
293
294    public TableDescriptor getTableDescriptor() {
295      return htd;
296    }
297
298    /**
299     * Returns the map of parent-children_pair.
300     * @return the map
301     */
302    public Map<String, Pair<String, String>> getParentToChildrenPairMap() {
303      return this.parentsMap;
304    }
305
306    /** Returns true if there're new regions */
307    public boolean hasRegionsToAdd() {
308      return this.regionsToAdd != null && this.regionsToAdd.size() > 0;
309    }
310
311    /**
312     * Returns the list of new regions added during the on-disk restore. The caller is responsible
313     * to add the regions to META. e.g MetaTableAccessor.addRegionsToMeta(...)
314     * @return the list of regions to add to META
315     */
316    public List<RegionInfo> getRegionsToAdd() {
317      return this.regionsToAdd;
318    }
319
320    /** Returns true if there're regions to restore */
321    public boolean hasRegionsToRestore() {
322      return this.regionsToRestore != null && this.regionsToRestore.size() > 0;
323    }
324
325    /**
326     * Returns the list of 'restored regions' during the on-disk restore. The caller is responsible
327     * to add the regions to hbase:meta if not present.
328     * @return the list of regions restored
329     */
330    public List<RegionInfo> getRegionsToRestore() {
331      return this.regionsToRestore;
332    }
333
334    /** Returns true if there're regions to remove */
335    public boolean hasRegionsToRemove() {
336      return this.regionsToRemove != null && this.regionsToRemove.size() > 0;
337    }
338
339    /**
340     * Returns the list of regions removed during the on-disk restore. The caller is responsible to
341     * remove the regions from META. e.g. MetaTableAccessor.deleteRegions(...)
342     * @return the list of regions to remove from META
343     */
344    public List<RegionInfo> getRegionsToRemove() {
345      return this.regionsToRemove;
346    }
347
348    void setNewRegions(final RegionInfo[] hris) {
349      if (hris != null) {
350        regionsToAdd = Arrays.asList(hris);
351      } else {
352        regionsToAdd = null;
353      }
354    }
355
356    void addRegionToRemove(final RegionInfo hri) {
357      if (regionsToRemove == null) {
358        regionsToRemove = new LinkedList<>();
359      }
360      regionsToRemove.add(hri);
361    }
362
363    void addRegionToRestore(final RegionInfo hri) {
364      if (regionsToRestore == null) {
365        regionsToRestore = new LinkedList<>();
366      }
367      regionsToRestore.add(hri);
368    }
369
370    public void updateMetaParentRegions(Connection connection, final List<RegionInfo> regionInfos)
371      throws IOException {
372      if (regionInfos == null || parentsMap.isEmpty()) return;
373
374      // Extract region names and offlined regions
375      Map<String, RegionInfo> regionsByName = new HashMap<>(regionInfos.size());
376      List<RegionInfo> parentRegions = new LinkedList<>();
377      for (RegionInfo regionInfo : regionInfos) {
378        if (regionInfo.isSplitParent()) {
379          parentRegions.add(regionInfo);
380        } else {
381          regionsByName.put(regionInfo.getEncodedName(), regionInfo);
382        }
383      }
384
385      // Update Offline parents
386      for (RegionInfo regionInfo : parentRegions) {
387        Pair<String, String> daughters = parentsMap.get(regionInfo.getEncodedName());
388        if (daughters == null) {
389          // The snapshot contains an unreferenced region.
390          // It will be removed by the CatalogJanitor.
391          LOG.warn("Skip update of unreferenced offline parent: " + regionInfo);
392          continue;
393        }
394
395        // One side of the split is already compacted
396        if (daughters.getSecond() == null) {
397          daughters.setSecond(daughters.getFirst());
398        }
399
400        LOG.debug("Update splits parent " + regionInfo.getEncodedName() + " -> " + daughters);
401        MetaTableAccessor.addSplitsToParent(connection, regionInfo,
402          regionsByName.get(daughters.getFirst()), regionsByName.get(daughters.getSecond()));
403      }
404    }
405  }
406
407  /**
408   * Remove specified regions from the file-system, using the archiver.
409   */
410  private void removeHdfsRegions(final ThreadPoolExecutor exec, final List<RegionInfo> regions)
411    throws IOException {
412    if (regions == null || regions.isEmpty()) return;
413    ModifyRegionUtils.editRegions(exec, regions, new ModifyRegionUtils.RegionEditTask() {
414      @Override
415      public void editRegion(final RegionInfo hri) throws IOException {
416        HFileArchiver.archiveRegion(conf, fs, hri);
417      }
418    });
419  }
420
421  /**
422   * Restore specified regions by restoring content to the snapshot state.
423   */
424  private void restoreHdfsRegions(final ThreadPoolExecutor exec,
425    final Map<String, SnapshotRegionManifest> regionManifests, final List<RegionInfo> regions)
426    throws IOException {
427    if (regions == null || regions.isEmpty()) return;
428    ModifyRegionUtils.editRegions(exec, regions, new ModifyRegionUtils.RegionEditTask() {
429      @Override
430      public void editRegion(final RegionInfo hri) throws IOException {
431        restoreRegion(hri, regionManifests.get(hri.getEncodedName()));
432      }
433    });
434  }
435
436  /**
437   * Restore specified mob regions by restoring content to the snapshot state.
438   */
439  private void restoreHdfsMobRegions(final ThreadPoolExecutor exec,
440    final Map<String, SnapshotRegionManifest> regionManifests, final List<RegionInfo> regions)
441    throws IOException {
442    if (regions == null || regions.isEmpty()) return;
443    ModifyRegionUtils.editRegions(exec, regions, new ModifyRegionUtils.RegionEditTask() {
444      @Override
445      public void editRegion(final RegionInfo hri) throws IOException {
446        restoreMobRegion(hri, regionManifests.get(hri.getEncodedName()));
447      }
448    });
449  }
450
451  private Map<String, List<SnapshotRegionManifest.StoreFile>>
452    getRegionHFileReferences(final SnapshotRegionManifest manifest) {
453    Map<String, List<SnapshotRegionManifest.StoreFile>> familyMap =
454      new HashMap<>(manifest.getFamilyFilesCount());
455    for (SnapshotRegionManifest.FamilyFiles familyFiles : manifest.getFamilyFilesList()) {
456      familyMap.put(familyFiles.getFamilyName().toStringUtf8(),
457        new ArrayList<>(familyFiles.getStoreFilesList()));
458    }
459    return familyMap;
460  }
461
462  /**
463   * Restore region by removing files not in the snapshot and adding the missing ones from the
464   * snapshot.
465   */
466  private void restoreRegion(final RegionInfo regionInfo,
467    final SnapshotRegionManifest regionManifest) throws IOException {
468    restoreRegion(regionInfo, regionManifest, new Path(tableDir, regionInfo.getEncodedName()));
469  }
470
471  /**
472   * Restore mob region by removing files not in the snapshot and adding the missing ones from the
473   * snapshot.
474   */
475  private void restoreMobRegion(final RegionInfo regionInfo,
476    final SnapshotRegionManifest regionManifest) throws IOException {
477    if (regionManifest == null) {
478      return;
479    }
480    restoreRegion(regionInfo, regionManifest,
481      MobUtils.getMobRegionPath(conf, tableDesc.getTableName()));
482  }
483
484  /**
485   * Restore region by removing files not in the snapshot and adding the missing ones from the
486   * snapshot.
487   */
488  private void restoreRegion(final RegionInfo regionInfo,
489    final SnapshotRegionManifest regionManifest, Path regionDir) throws IOException {
490    Map<String, List<SnapshotRegionManifest.StoreFile>> snapshotFiles =
491      getRegionHFileReferences(regionManifest);
492
493    String tableName = tableDesc.getTableName().getNameAsString();
494    final String snapshotName = snapshotDesc.getName();
495
496    Path regionPath = new Path(tableDir, regionInfo.getEncodedName());
497    HRegionFileSystem regionFS = (fs.exists(regionPath))
498      ? HRegionFileSystem.openRegionFromFileSystem(conf, fs, tableDir, regionInfo, false)
499      : HRegionFileSystem.createRegionOnFileSystem(conf, fs, tableDir, regionInfo);
500
501    // Restore families present in the table
502    for (Path familyDir : FSUtils.getFamilyDirs(fs, regionDir)) {
503      byte[] family = Bytes.toBytes(familyDir.getName());
504
505      StoreFileTracker tracker = StoreFileTrackerFactory.create(conf, true,
506        StoreContext.getBuilder().withColumnFamilyDescriptor(tableDesc.getColumnFamily(family))
507          .withFamilyStoreDirectoryPath(familyDir).withRegionFileSystem(regionFS).build());
508      Set<String> familyFiles = getTableRegionFamilyFiles(familyDir);
509      List<SnapshotRegionManifest.StoreFile> snapshotFamilyFiles =
510        snapshotFiles.remove(familyDir.getName());
511      List<StoreFileInfo> filesToTrack = new ArrayList<>();
512      if (snapshotFamilyFiles != null) {
513        List<SnapshotRegionManifest.StoreFile> hfilesToAdd = new ArrayList<>();
514        for (SnapshotRegionManifest.StoreFile storeFile : snapshotFamilyFiles) {
515          if (familyFiles.contains(storeFile.getName())) {
516            // HFile already present
517            familyFiles.remove(storeFile.getName());
518            // no need to restore already present files, but we need to add those to tracker
519            filesToTrack
520              .add(tracker.getStoreFileInfo(new Path(familyDir, storeFile.getName()), true));
521          } else {
522            // HFile missing
523            hfilesToAdd.add(storeFile);
524          }
525        }
526
527        // Remove hfiles not present in the snapshot
528        for (String hfileName : familyFiles) {
529          Path hfile = new Path(familyDir, hfileName);
530          if (!fs.getFileStatus(hfile).isDirectory()) {
531            LOG.trace("Removing HFile=" + hfileName + " not present in snapshot=" + snapshotName
532              + " from region=" + regionInfo.getEncodedName() + " table=" + tableName);
533            HFileArchiver.archiveStoreFile(conf, fs, regionInfo, tableDir, family, hfile);
534          }
535        }
536
537        // Restore Missing files
538        for (SnapshotRegionManifest.StoreFile storeFile : hfilesToAdd) {
539          LOG.debug("Restoring missing HFileLink " + storeFile.getName() + " of snapshot="
540            + snapshotName + " to region=" + regionInfo.getEncodedName() + " table=" + tableName);
541          String fileName =
542            restoreStoreFile(familyDir, regionInfo, storeFile, createBackRefs, tracker);
543          // mark the reference file to be added to tracker
544          filesToTrack.add(tracker.getStoreFileInfo(new Path(familyDir, fileName), true));
545        }
546      } else {
547        // Family doesn't exists in the snapshot
548        LOG.trace("Removing family=" + Bytes.toString(family) + " in snapshot=" + snapshotName
549          + " from region=" + regionInfo.getEncodedName() + " table=" + tableName);
550        HFileArchiver.archiveFamilyByFamilyDir(fs, conf, regionInfo, familyDir, family);
551        fs.delete(familyDir, true);
552      }
553
554      // simply reset list of tracked files with the matching files
555      // and the extra one present in the snapshot
556      tracker.set(filesToTrack);
557    }
558
559    // Add families not present in the table
560    for (Map.Entry<String, List<SnapshotRegionManifest.StoreFile>> familyEntry : snapshotFiles
561      .entrySet()) {
562      Path familyDir = new Path(regionDir, familyEntry.getKey());
563      StoreFileTracker tracker =
564        StoreFileTrackerFactory.create(conf, true, StoreContext.getBuilder()
565          .withFamilyStoreDirectoryPath(familyDir).withRegionFileSystem(regionFS).build());
566      List<StoreFileInfo> files = new ArrayList<>();
567      if (!fs.mkdirs(familyDir)) {
568        throw new IOException("Unable to create familyDir=" + familyDir);
569      }
570
571      for (SnapshotRegionManifest.StoreFile storeFile : familyEntry.getValue()) {
572        LOG.trace("Adding HFileLink (Not present in the table) " + storeFile.getName()
573          + " of snapshot " + snapshotName + " to table=" + tableName);
574        String fileName =
575          restoreStoreFile(familyDir, regionInfo, storeFile, createBackRefs, tracker);
576        files.add(tracker.getStoreFileInfo(new Path(familyDir, fileName), true));
577      }
578      tracker.set(files);
579    }
580  }
581
582  private Set<String> getTableRegionFamilyFiles(final Path familyDir) throws IOException {
583    FileStatus[] hfiles = CommonFSUtils.listStatus(fs, familyDir);
584    if (hfiles == null) {
585      return Collections.emptySet();
586    }
587
588    Set<String> familyFiles = new HashSet<>(hfiles.length);
589    for (int i = 0; i < hfiles.length; ++i) {
590      String hfileName = hfiles[i].getPath().getName();
591      familyFiles.add(hfileName);
592    }
593
594    return familyFiles;
595  }
596
597  /**
598   * Clone specified regions. For each region create a new region and create a HFileLink for each
599   * hfile.
600   */
601  private RegionInfo[] cloneHdfsRegions(final ThreadPoolExecutor exec,
602    final Map<String, SnapshotRegionManifest> regionManifests, final List<RegionInfo> regions)
603    throws IOException {
604    if (regions == null || regions.isEmpty()) return null;
605
606    final Map<String, RegionInfo> snapshotRegions = new HashMap<>(regions.size());
607    final String snapshotName = snapshotDesc.getName();
608
609    // clone region info (change embedded tableName with the new one)
610    RegionInfo[] clonedRegionsInfo = new RegionInfo[regions.size()];
611    for (int i = 0; i < clonedRegionsInfo.length; ++i) {
612      // clone the region info from the snapshot region info
613      RegionInfo snapshotRegionInfo = regions.get(i);
614      clonedRegionsInfo[i] = cloneRegionInfo(snapshotRegionInfo);
615
616      // add the region name mapping between snapshot and cloned
617      String snapshotRegionName = snapshotRegionInfo.getEncodedName();
618      String clonedRegionName = clonedRegionsInfo[i].getEncodedName();
619      regionsMap.put(Bytes.toBytes(snapshotRegionName), Bytes.toBytes(clonedRegionName));
620      LOG.info("clone region=" + snapshotRegionName + " as " + clonedRegionName + " in snapshot "
621        + snapshotName);
622
623      // Add mapping between cloned region name and snapshot region info
624      snapshotRegions.put(clonedRegionName, snapshotRegionInfo);
625    }
626
627    // create the regions on disk
628    ModifyRegionUtils.createRegions(exec, conf, rootDir, tableDesc, clonedRegionsInfo,
629      new ModifyRegionUtils.RegionFillTask() {
630        @Override
631        public void fillRegion(final HRegion region) throws IOException {
632          RegionInfo snapshotHri = snapshotRegions.get(region.getRegionInfo().getEncodedName());
633          cloneRegion(region, snapshotHri, regionManifests.get(snapshotHri.getEncodedName()));
634        }
635      });
636
637    return clonedRegionsInfo;
638  }
639
640  /**
641   * Clone the mob region. For the region create a new region and create a HFileLink for each hfile.
642   */
643  private void cloneHdfsMobRegion(final Map<String, SnapshotRegionManifest> regionManifests,
644    final RegionInfo region) throws IOException {
645    // clone region info (change embedded tableName with the new one)
646    Path clonedRegionPath = MobUtils.getMobRegionPath(rootDir, tableDesc.getTableName());
647    cloneRegion(MobUtils.getMobRegionInfo(tableDesc.getTableName()), clonedRegionPath, region,
648      regionManifests.get(region.getEncodedName()));
649  }
650
651  /**
652   * Clone region directory content from the snapshot info. Each region is encoded with the table
653   * name, so the cloned region will have a different region name. Instead of copying the hfiles a
654   * HFileLink is created.
655   * @param regionDir {@link Path} cloned dir
656   */
657  private void cloneRegion(final RegionInfo newRegionInfo, final Path regionDir,
658    final RegionInfo snapshotRegionInfo, final SnapshotRegionManifest manifest) throws IOException {
659    final String tableName = tableDesc.getTableName().getNameAsString();
660    final String snapshotName = snapshotDesc.getName();
661    for (SnapshotRegionManifest.FamilyFiles familyFiles : manifest.getFamilyFilesList()) {
662      Path familyDir = new Path(regionDir, familyFiles.getFamilyName().toStringUtf8());
663      List<StoreFileInfo> clonedFiles = new ArrayList<>();
664      Path regionPath = new Path(tableDir, newRegionInfo.getEncodedName());
665      HRegionFileSystem regionFS = (fs.exists(regionPath))
666        ? HRegionFileSystem.openRegionFromFileSystem(conf, fs, tableDir, newRegionInfo, false)
667        : HRegionFileSystem.createRegionOnFileSystem(conf, fs, tableDir, newRegionInfo);
668
669      Configuration sftConf = StoreUtils.createStoreConfiguration(conf, tableDesc,
670        tableDesc.getColumnFamily(familyFiles.getFamilyName().toByteArray()));
671      StoreFileTracker tracker =
672        StoreFileTrackerFactory
673          .create(sftConf, true,
674            StoreContext.getBuilder().withFamilyStoreDirectoryPath(familyDir)
675              .withRegionFileSystem(regionFS)
676              .withColumnFamilyDescriptor(
677                ColumnFamilyDescriptorBuilder.of(familyFiles.getFamilyName().toByteArray()))
678              .build());
679      for (SnapshotRegionManifest.StoreFile storeFile : familyFiles.getStoreFilesList()) {
680        LOG.info("Adding HFileLink " + storeFile.getName() + " from cloned region " + "in snapshot "
681          + snapshotName + " to table=" + tableName);
682        if (MobUtils.isMobRegionInfo(newRegionInfo)) {
683          String mobFileName =
684            HFileLink.createHFileLinkName(snapshotRegionInfo, storeFile.getName());
685          Path mobPath = new Path(familyDir, mobFileName);
686          if (fs.exists(mobPath)) {
687            fs.delete(mobPath, true);
688          }
689          restoreStoreFile(familyDir, snapshotRegionInfo, storeFile, createBackRefs, tracker);
690        } else {
691          String file =
692            restoreStoreFile(familyDir, snapshotRegionInfo, storeFile, createBackRefs, tracker);
693          clonedFiles.add(tracker.getStoreFileInfo(new Path(familyDir, file), true));
694        }
695      }
696      // we don't need to track files under mobdir
697      if (!MobUtils.isMobRegionInfo(newRegionInfo)) {
698        tracker.set(clonedFiles);
699      }
700    }
701
702  }
703
704  /**
705   * Clone region directory content from the snapshot info. Each region is encoded with the table
706   * name, so the cloned region will have a different region name. Instead of copying the hfiles a
707   * HFileLink is created.
708   * @param region {@link HRegion} cloned
709   */
710  private void cloneRegion(final HRegion region, final RegionInfo snapshotRegionInfo,
711    final SnapshotRegionManifest manifest) throws IOException {
712    cloneRegion(region.getRegionInfo(), new Path(tableDir, region.getRegionInfo().getEncodedName()),
713      snapshotRegionInfo, manifest);
714  }
715
716  /**
717   * Create a new {@link HFileLink} to reference the store file.
718   * <p>
719   * The store file in the snapshot can be a simple hfile, an HFileLink or a reference.
720   * <ul>
721   * <li>hfile: abc -> table=region-abc
722   * <li>reference: abc.1234 -> table=region-abc.1234
723   * <li>hfilelink: table=region-hfile -> table=region-hfile
724   * </ul>
725   * @param familyDir     destination directory for the store file
726   * @param regionInfo    destination region info for the table
727   * @param createBackRef - Whether back reference should be created. Defaults to true.
728   * @param storeFile     store file name (can be a Reference, HFileLink or simple HFile)
729   */
730  private String restoreStoreFile(final Path familyDir, final RegionInfo regionInfo,
731    final SnapshotRegionManifest.StoreFile storeFile, final boolean createBackRef,
732    final StoreFileTracker tracker) throws IOException {
733    String hfileName = storeFile.getName();
734    if (HFileLink.isHFileLink(hfileName)) {
735      return tracker.createFromHFileLink(hfileName, createBackRef);
736    } else if (StoreFileInfo.isReference(hfileName)) {
737      return restoreReferenceFile(familyDir, regionInfo, storeFile, tracker);
738    } else {
739      return tracker.createHFileLink(regionInfo.getTable(), regionInfo.getEncodedName(), hfileName,
740        createBackRef);
741    }
742  }
743
744  /**
745   * Create a new {@link Reference} as copy of the source one.
746   * <p>
747   * <blockquote>
748   *
749   * <pre>
750   * The source table looks like:
751   *    1234/abc      (original file)
752   *    5678/abc.1234 (reference file)
753   *
754   * After the clone operation looks like:
755   *   wxyz/table=1234-abc
756   *   stuv/table=1234-abc.wxyz
757   *
758   * NOTE that the region name in the clone changes (md5 of regioninfo)
759   * and the reference should reflect that change.
760   * </pre>
761   *
762   * </blockquote>
763   * @param familyDir  destination directory for the store file
764   * @param regionInfo destination region info for the table
765   * @param storeFile  reference file name
766   */
767  private String restoreReferenceFile(final Path familyDir, final RegionInfo regionInfo,
768    final SnapshotRegionManifest.StoreFile storeFile, final StoreFileTracker tracker)
769    throws IOException {
770    String hfileName = storeFile.getName();
771
772    // Extract the referred information (hfile name and parent region)
773    Path refPath =
774      StoreFileInfo
775        .getReferredToFile(
776          new Path(
777            new Path(
778              new Path(new Path(snapshotTable.getNamespaceAsString(),
779                snapshotTable.getQualifierAsString()), regionInfo.getEncodedName()),
780              familyDir.getName()),
781            hfileName));
782    String snapshotRegionName = refPath.getParent().getParent().getName();
783    String fileName = refPath.getName();
784
785    // The new reference should have the cloned region name as parent, if it is a clone.
786    String clonedRegionName = Bytes.toString(regionsMap.get(Bytes.toBytes(snapshotRegionName)));
787    if (clonedRegionName == null) clonedRegionName = snapshotRegionName;
788
789    // The output file should be a reference link table=snapshotRegion-fileName.clonedRegionName
790    Path linkPath = null;
791    String refLink = fileName;
792    if (!HFileLink.isHFileLink(fileName)) {
793      refLink = HFileLink.createHFileLinkName(snapshotTable, snapshotRegionName, fileName);
794      linkPath = new Path(familyDir,
795        HFileLink.createHFileLinkName(snapshotTable, regionInfo.getEncodedName(), hfileName));
796    }
797
798    Path outPath = new Path(familyDir, refLink + '.' + clonedRegionName);
799
800    // Create the new reference
801    if (storeFile.hasReference()) {
802      Reference reference = Reference.convert(storeFile.getReference());
803      tracker.createReference(reference, outPath);
804    } else {
805      InputStream in;
806      if (linkPath != null) {
807        in = HFileLink.buildFromHFileLinkPattern(conf, linkPath).open(fs);
808      } else {
809        linkPath = new Path(new Path(
810          HRegion.getRegionDir(snapshotManifest.getSnapshotDir(), regionInfo.getEncodedName()),
811          familyDir.getName()), hfileName);
812        in = fs.open(linkPath);
813      }
814      OutputStream out = fs.create(outPath);
815      IOUtils.copyBytes(in, out, conf);
816    }
817
818    // Add the daughter region to the map
819    String regionName = Bytes.toString(regionsMap.get(regionInfo.getEncodedNameAsBytes()));
820    if (regionName == null) {
821      regionName = regionInfo.getEncodedName();
822    }
823    LOG.debug("Restore reference " + regionName + " to " + clonedRegionName);
824    synchronized (parentsMap) {
825      Pair<String, String> daughters = parentsMap.get(clonedRegionName);
826      if (daughters == null) {
827        // In case one side of the split is already compacted, regionName is put as both first and
828        // second of Pair
829        daughters = new Pair<>(regionName, regionName);
830        parentsMap.put(clonedRegionName, daughters);
831      } else if (!regionName.equals(daughters.getFirst())) {
832        daughters.setSecond(regionName);
833      }
834    }
835    return outPath.getName();
836  }
837
838  /**
839   * Create a new {@link RegionInfo} from the snapshot region info. Keep the same startKey, endKey,
840   * regionId and split information but change the table name.
841   * @param snapshotRegionInfo Info for region to clone.
842   * @return the new HRegion instance
843   */
844  public RegionInfo cloneRegionInfo(final RegionInfo snapshotRegionInfo) {
845    return cloneRegionInfo(tableDesc.getTableName(), snapshotRegionInfo);
846  }
847
848  public static RegionInfo cloneRegionInfo(TableName tableName, RegionInfo snapshotRegionInfo) {
849    return RegionInfoBuilder.newBuilder(tableName).setStartKey(snapshotRegionInfo.getStartKey())
850      .setEndKey(snapshotRegionInfo.getEndKey()).setSplit(snapshotRegionInfo.isSplit())
851      .setRegionId(snapshotRegionInfo.getRegionId()).setOffline(snapshotRegionInfo.isOffline())
852      .build();
853  }
854
855  /** Returns the set of the regions contained in the table */
856  private List<RegionInfo> getTableRegions() throws IOException {
857    LOG.debug("get table regions: " + tableDir);
858    FileStatus[] regionDirs =
859      CommonFSUtils.listStatus(fs, tableDir, new FSUtils.RegionDirFilter(fs));
860    if (regionDirs == null) {
861      return null;
862    }
863
864    List<RegionInfo> regions = new ArrayList<>(regionDirs.length);
865    for (int i = 0; i < regionDirs.length; ++i) {
866      RegionInfo hri = HRegionFileSystem.loadRegionInfoFileContent(fs, regionDirs[i].getPath());
867      regions.add(hri);
868    }
869    LOG.debug("found " + regions.size() + " regions for table="
870      + tableDesc.getTableName().getNameAsString());
871    return regions;
872  }
873
874  /**
875   * Copy the snapshot files for a snapshot scanner, discards meta changes.
876   */
877  public static RestoreMetaChanges copySnapshotForScanner(Configuration conf, FileSystem fs,
878    Path rootDir, Path restoreDir, String snapshotName) throws IOException {
879    // ensure that restore dir is not under root dir
880    if (!restoreDir.getFileSystem(conf).getUri().equals(rootDir.getFileSystem(conf).getUri())) {
881      throw new IllegalArgumentException(
882        "Filesystems for restore directory and HBase root " + "directory should be the same");
883    }
884    if (restoreDir.toUri().getPath().startsWith(rootDir.toUri().getPath() + "/")) {
885      throw new IllegalArgumentException("Restore directory cannot be a sub directory of HBase "
886        + "root directory. RootDir: " + rootDir + ", restoreDir: " + restoreDir);
887    }
888
889    Path snapshotDir = SnapshotDescriptionUtils.getCompletedSnapshotDir(snapshotName, rootDir);
890    SnapshotDescription snapshotDesc = SnapshotDescriptionUtils.readSnapshotInfo(fs, snapshotDir);
891    // check if the snapshot is expired.
892    boolean isExpired = SnapshotDescriptionUtils.isExpiredSnapshot(snapshotDesc.getTtl(),
893      snapshotDesc.getCreationTime(), EnvironmentEdgeManager.currentTime());
894    if (isExpired) {
895      throw new SnapshotTTLExpiredException(ProtobufUtil.createSnapshotDesc(snapshotDesc));
896    }
897    SnapshotManifest manifest = SnapshotManifest.open(conf, fs, snapshotDir, snapshotDesc);
898
899    MonitoredTask status = TaskMonitor.get()
900      .createStatus("Restoring  snapshot '" + snapshotName + "' to directory " + restoreDir);
901    ForeignExceptionDispatcher monitor = new ForeignExceptionDispatcher();
902
903    // we send createBackRefs=false so that restored hfiles do not create back reference links
904    // in the base hbase root dir.
905    RestoreSnapshotHelper helper = new RestoreSnapshotHelper(conf, fs, manifest,
906      manifest.getTableDescriptor(), restoreDir, monitor, status, false);
907    RestoreMetaChanges metaChanges = helper.restoreHdfsRegions(); // TODO: parallelize.
908
909    if (LOG.isDebugEnabled()) {
910      LOG.debug("Restored table dir:" + restoreDir);
911      CommonFSUtils.logFileSystemState(fs, restoreDir, LOG);
912    }
913    return metaChanges;
914  }
915
916  public static void restoreSnapshotAcl(SnapshotDescription snapshot, TableName newTableName,
917    Configuration conf) throws IOException {
918    if (snapshot.hasUsersAndPermissions() && snapshot.getUsersAndPermissions() != null) {
919      LOG.info("Restore snapshot acl to table. snapshot: " + snapshot + ", table: " + newTableName);
920      ListMultimap<String, Permission> perms =
921        ShadedAccessControlUtil.toUserTablePermissions(snapshot.getUsersAndPermissions());
922      try (Connection conn = ConnectionFactory.createConnection(conf)) {
923        for (Entry<String, Permission> e : perms.entries()) {
924          String user = e.getKey();
925          TablePermission tablePerm = (TablePermission) e.getValue();
926          AccessControlClient.grant(conn, newTableName, user, tablePerm.getFamily(),
927            tablePerm.getQualifier(), tablePerm.getActions());
928        }
929      } catch (Throwable e) {
930        throw new IOException("Grant acl into newly creatd table failed. snapshot: " + snapshot
931          + ", table: " + newTableName, e);
932      }
933    }
934  }
935}