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.snapshot;
020
021import java.io.IOException;
022import java.io.InputStream;
023import java.io.OutputStream;
024import java.util.ArrayList;
025import java.util.Arrays;
026import java.util.Collections;
027import java.util.HashMap;
028import java.util.HashSet;
029import java.util.LinkedList;
030import java.util.List;
031import java.util.Map;
032import java.util.Map.Entry;
033import java.util.Set;
034import java.util.TreeMap;
035import java.util.concurrent.ThreadPoolExecutor;
036
037import org.apache.hadoop.conf.Configuration;
038import org.apache.hadoop.fs.FileStatus;
039import org.apache.hadoop.fs.FileSystem;
040import org.apache.hadoop.fs.Path;
041import org.apache.hadoop.hbase.MetaTableAccessor;
042import org.apache.hadoop.hbase.TableName;
043import org.apache.hadoop.hbase.backup.HFileArchiver;
044import org.apache.hadoop.hbase.client.Connection;
045import org.apache.hadoop.hbase.client.ConnectionFactory;
046import org.apache.hadoop.hbase.client.RegionInfo;
047import org.apache.hadoop.hbase.client.RegionInfoBuilder;
048import org.apache.hadoop.hbase.client.TableDescriptor;
049import org.apache.hadoop.hbase.errorhandling.ForeignExceptionDispatcher;
050import org.apache.hadoop.hbase.io.HFileLink;
051import org.apache.hadoop.hbase.io.Reference;
052import org.apache.hadoop.hbase.mob.MobUtils;
053import org.apache.hadoop.hbase.monitoring.MonitoredTask;
054import org.apache.hadoop.hbase.monitoring.TaskMonitor;
055import org.apache.hadoop.hbase.regionserver.HRegion;
056import org.apache.hadoop.hbase.regionserver.HRegionFileSystem;
057import org.apache.hadoop.hbase.regionserver.StoreFileInfo;
058import org.apache.hadoop.hbase.security.access.AccessControlClient;
059import org.apache.hadoop.hbase.security.access.ShadedAccessControlUtil;
060import org.apache.hadoop.hbase.security.access.TablePermission;
061import org.apache.hadoop.hbase.util.Bytes;
062import org.apache.hadoop.hbase.util.FSUtils;
063import org.apache.hadoop.hbase.util.ModifyRegionUtils;
064import org.apache.hadoop.hbase.util.Pair;
065import org.apache.hadoop.io.IOUtils;
066import org.apache.yetus.audience.InterfaceAudience;
067import org.slf4j.Logger;
068import org.slf4j.LoggerFactory;
069import org.apache.hbase.thirdparty.com.google.common.collect.ListMultimap;
070import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil;
071import org.apache.hadoop.hbase.shaded.protobuf.generated.SnapshotProtos.SnapshotDescription;
072import org.apache.hadoop.hbase.shaded.protobuf.generated.SnapshotProtos.SnapshotRegionManifest;
073
074/**
075 * Helper to Restore/Clone a Snapshot
076 *
077 * <p>The helper assumes that a table is already created, and by calling restore()
078 * the content present in the snapshot will be restored as the new content of the table.
079 *
080 * <p>Clone from Snapshot: If the target table is empty, the restore operation
081 * is just a "clone operation", where the only operations are:
082 * <ul>
083 *  <li>for each region in the snapshot create a new region
084 *    (note that the region will have a different name, since the encoding contains the table name)
085 *  <li>for each file in the region create a new HFileLink to point to the original file.
086 *  <li>restore the logs, if any
087 * </ul>
088 *
089 * <p>Restore from Snapshot:
090 * <ul>
091 *  <li>for each region in the table verify which are available in the snapshot and which are not
092 *    <ul>
093 *    <li>if the region is not present in the snapshot, remove it.
094 *    <li>if the region is present in the snapshot
095 *      <ul>
096 *      <li>for each file in the table region verify which are available in the snapshot
097 *        <ul>
098 *          <li>if the hfile is not present in the snapshot, remove it
099 *          <li>if the hfile is present, keep it (nothing to do)
100 *        </ul>
101 *      <li>for each file in the snapshot region but not in the table
102 *        <ul>
103 *          <li>create a new HFileLink that point to the original file
104 *        </ul>
105 *      </ul>
106 *    </ul>
107 *  <li>for each region in the snapshot not present in the current table state
108 *    <ul>
109 *    <li>create a new region and for each file in the region create a new HFileLink
110 *      (This is the same as the clone operation)
111 *    </ul>
112 *  <li>restore the logs, if any
113 * </ul>
114 */
115@InterfaceAudience.Private
116public class RestoreSnapshotHelper {
117  private static final Logger LOG = LoggerFactory.getLogger(RestoreSnapshotHelper.class);
118
119  private final Map<byte[], byte[]> regionsMap = new TreeMap<>(Bytes.BYTES_COMPARATOR);
120
121  private final Map<String, Pair<String, String> > parentsMap = new HashMap<>();
122
123  private final ForeignExceptionDispatcher monitor;
124  private final MonitoredTask status;
125
126  private final SnapshotManifest snapshotManifest;
127  private final SnapshotDescription snapshotDesc;
128  private final TableName snapshotTable;
129
130  private final TableDescriptor tableDesc;
131  private final Path rootDir;
132  private final Path tableDir;
133
134  private final Configuration conf;
135  private final FileSystem fs;
136  private final boolean createBackRefs;
137
138  public RestoreSnapshotHelper(final Configuration conf,
139      final FileSystem fs,
140      final SnapshotManifest manifest,
141      final TableDescriptor tableDescriptor,
142      final Path rootDir,
143      final ForeignExceptionDispatcher monitor,
144      final MonitoredTask status) {
145    this(conf, fs, manifest, tableDescriptor, rootDir, monitor, status, true);
146  }
147
148  public RestoreSnapshotHelper(final Configuration conf,
149      final FileSystem fs,
150      final SnapshotManifest manifest,
151      final TableDescriptor tableDescriptor,
152      final Path rootDir,
153      final ForeignExceptionDispatcher monitor,
154      final MonitoredTask status,
155      final boolean createBackRefs)
156  {
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 = FSUtils.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 = MobUtils.getMobRegionInfo(snapshotManifest.getTableDescriptor()
201        .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(ProtobufUtil.toRegionInfo(regionManifests.get(regionName)
233              .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.add(ProtobufUtil.toRegionInfo(regionManifests.get(regionName)
248            .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    /**
307     * @return true if there're new regions
308     */
309    public boolean hasRegionsToAdd() {
310      return this.regionsToAdd != null && this.regionsToAdd.size() > 0;
311    }
312
313    /**
314     * Returns the list of new regions added during the on-disk restore.
315     * The caller is responsible to add the regions to META.
316     * e.g MetaTableAccessor.addRegionsToMeta(...)
317     * @return the list of regions to add to META
318     */
319    public List<RegionInfo> getRegionsToAdd() {
320      return this.regionsToAdd;
321    }
322
323    /**
324     * @return true if there're regions to restore
325     */
326    public boolean hasRegionsToRestore() {
327      return this.regionsToRestore != null && this.regionsToRestore.size() > 0;
328    }
329
330    /**
331     * Returns the list of 'restored regions' during the on-disk restore.
332     * The caller is responsible to add the regions to hbase:meta if not present.
333     * @return the list of regions restored
334     */
335    public List<RegionInfo> getRegionsToRestore() {
336      return this.regionsToRestore;
337    }
338
339    /**
340     * @return true if there're regions to remove
341     */
342    public boolean hasRegionsToRemove() {
343      return this.regionsToRemove != null && this.regionsToRemove.size() > 0;
344    }
345
346    /**
347     * Returns the list of regions removed during the on-disk restore.
348     * The caller is responsible to remove the regions from META.
349     * e.g. MetaTableAccessor.deleteRegions(...)
350     * @return the list of regions to remove from META
351     */
352    public List<RegionInfo> getRegionsToRemove() {
353      return this.regionsToRemove;
354    }
355
356    void setNewRegions(final RegionInfo[] hris) {
357      if (hris != null) {
358        regionsToAdd = Arrays.asList(hris);
359      } else {
360        regionsToAdd = null;
361      }
362    }
363
364    void addRegionToRemove(final RegionInfo hri) {
365      if (regionsToRemove == null) {
366        regionsToRemove = new LinkedList<>();
367      }
368      regionsToRemove.add(hri);
369    }
370
371    void addRegionToRestore(final RegionInfo hri) {
372      if (regionsToRestore == null) {
373        regionsToRestore = new LinkedList<>();
374      }
375      regionsToRestore.add(hri);
376    }
377
378    public void updateMetaParentRegions(Connection connection,
379        final List<RegionInfo> regionInfos) throws IOException {
380      if (regionInfos == null || parentsMap.isEmpty()) return;
381
382      // Extract region names and offlined regions
383      Map<String, RegionInfo> regionsByName = new HashMap<>(regionInfos.size());
384      List<RegionInfo> parentRegions = new LinkedList<>();
385      for (RegionInfo regionInfo: regionInfos) {
386        if (regionInfo.isSplitParent()) {
387          parentRegions.add(regionInfo);
388        } else {
389          regionsByName.put(regionInfo.getEncodedName(), regionInfo);
390        }
391      }
392
393      // Update Offline parents
394      for (RegionInfo regionInfo: parentRegions) {
395        Pair<String, String> daughters = parentsMap.get(regionInfo.getEncodedName());
396        if (daughters == null) {
397          // The snapshot contains an unreferenced region.
398          // It will be removed by the CatalogJanitor.
399          LOG.warn("Skip update of unreferenced offline parent: " + regionInfo);
400          continue;
401        }
402
403        // One side of the split is already compacted
404        if (daughters.getSecond() == null) {
405          daughters.setSecond(daughters.getFirst());
406        }
407
408        LOG.debug("Update splits parent " + regionInfo.getEncodedName() + " -> " + daughters);
409        MetaTableAccessor.addSplitsToParent(connection, regionInfo,
410          regionsByName.get(daughters.getFirst()),
411          regionsByName.get(daughters.getSecond()));
412      }
413    }
414  }
415
416  /**
417   * Remove specified regions from the file-system, using the archiver.
418   */
419  private void removeHdfsRegions(final ThreadPoolExecutor exec, final List<RegionInfo> regions)
420      throws IOException {
421    if (regions == null || regions.isEmpty()) return;
422    ModifyRegionUtils.editRegions(exec, regions, new ModifyRegionUtils.RegionEditTask() {
423      @Override
424      public void editRegion(final RegionInfo hri) throws IOException {
425        HFileArchiver.archiveRegion(conf, fs, hri);
426      }
427    });
428  }
429
430  /**
431   * Restore specified regions by restoring content to the snapshot state.
432   */
433  private void restoreHdfsRegions(final ThreadPoolExecutor exec,
434      final Map<String, SnapshotRegionManifest> regionManifests,
435      final List<RegionInfo> regions) throws IOException {
436    if (regions == null || regions.isEmpty()) return;
437    ModifyRegionUtils.editRegions(exec, regions, new ModifyRegionUtils.RegionEditTask() {
438      @Override
439      public void editRegion(final RegionInfo hri) throws IOException {
440        restoreRegion(hri, regionManifests.get(hri.getEncodedName()));
441      }
442    });
443  }
444
445  /**
446   * Restore specified mob regions by restoring content to the snapshot state.
447   */
448  private void restoreHdfsMobRegions(final ThreadPoolExecutor exec,
449      final Map<String, SnapshotRegionManifest> regionManifests,
450      final List<RegionInfo> regions) throws IOException {
451    if (regions == null || regions.isEmpty()) return;
452    ModifyRegionUtils.editRegions(exec, regions, new ModifyRegionUtils.RegionEditTask() {
453      @Override
454      public void editRegion(final RegionInfo hri) throws IOException {
455        restoreMobRegion(hri, regionManifests.get(hri.getEncodedName()));
456      }
457    });
458  }
459
460  private Map<String, List<SnapshotRegionManifest.StoreFile>> getRegionHFileReferences(
461      final SnapshotRegionManifest manifest) {
462    Map<String, List<SnapshotRegionManifest.StoreFile>> familyMap =
463      new HashMap<>(manifest.getFamilyFilesCount());
464    for (SnapshotRegionManifest.FamilyFiles familyFiles: manifest.getFamilyFilesList()) {
465      familyMap.put(familyFiles.getFamilyName().toStringUtf8(),
466        new ArrayList<>(familyFiles.getStoreFilesList()));
467    }
468    return familyMap;
469  }
470
471  /**
472   * Restore region by removing files not in the snapshot
473   * and adding the missing ones from the snapshot.
474   */
475  private void restoreRegion(final RegionInfo regionInfo,
476      final SnapshotRegionManifest regionManifest) throws IOException {
477    restoreRegion(regionInfo, regionManifest, new Path(tableDir, regionInfo.getEncodedName()));
478  }
479
480  /**
481   * Restore mob region by removing files not in the snapshot
482   * and adding the missing ones from the snapshot.
483   */
484  private void restoreMobRegion(final RegionInfo regionInfo,
485      final SnapshotRegionManifest regionManifest) throws IOException {
486    if (regionManifest == null) {
487      return;
488    }
489    restoreRegion(regionInfo, regionManifest,
490      MobUtils.getMobRegionPath(conf, tableDesc.getTableName()));
491  }
492
493  /**
494   * Restore region by removing files not in the snapshot
495   * and adding the missing ones from the snapshot.
496   */
497  private void restoreRegion(final RegionInfo regionInfo,
498      final SnapshotRegionManifest regionManifest, Path regionDir) throws IOException {
499    Map<String, List<SnapshotRegionManifest.StoreFile>> snapshotFiles =
500                getRegionHFileReferences(regionManifest);
501
502    String tableName = tableDesc.getTableName().getNameAsString();
503
504    // Restore families present in the table
505    for (Path familyDir: FSUtils.getFamilyDirs(fs, regionDir)) {
506      byte[] family = Bytes.toBytes(familyDir.getName());
507      Set<String> familyFiles = getTableRegionFamilyFiles(familyDir);
508      List<SnapshotRegionManifest.StoreFile> snapshotFamilyFiles =
509          snapshotFiles.remove(familyDir.getName());
510      if (snapshotFamilyFiles != null) {
511        List<SnapshotRegionManifest.StoreFile> hfilesToAdd = new ArrayList<>();
512        for (SnapshotRegionManifest.StoreFile storeFile: snapshotFamilyFiles) {
513          if (familyFiles.contains(storeFile.getName())) {
514            // HFile already present
515            familyFiles.remove(storeFile.getName());
516          } else {
517            // HFile missing
518            hfilesToAdd.add(storeFile);
519          }
520        }
521
522        // Remove hfiles not present in the snapshot
523        for (String hfileName: familyFiles) {
524          Path hfile = new Path(familyDir, hfileName);
525          LOG.trace("Removing hfile=" + hfileName +
526            " from region=" + regionInfo.getEncodedName() + " table=" + tableName);
527          HFileArchiver.archiveStoreFile(conf, fs, regionInfo, tableDir, family, hfile);
528        }
529
530        // Restore Missing files
531        for (SnapshotRegionManifest.StoreFile storeFile: hfilesToAdd) {
532          LOG.debug("Adding HFileLink " + storeFile.getName() +
533            " to region=" + regionInfo.getEncodedName() + " table=" + tableName);
534          restoreStoreFile(familyDir, regionInfo, storeFile, createBackRefs);
535        }
536      } else {
537        // Family doesn't exists in the snapshot
538        LOG.trace("Removing family=" + Bytes.toString(family) +
539          " from region=" + regionInfo.getEncodedName() + " table=" + tableName);
540        HFileArchiver.archiveFamilyByFamilyDir(fs, conf, regionInfo, familyDir, family);
541        fs.delete(familyDir, true);
542      }
543    }
544
545    // Add families not present in the table
546    for (Map.Entry<String, List<SnapshotRegionManifest.StoreFile>> familyEntry:
547                                                                      snapshotFiles.entrySet()) {
548      Path familyDir = new Path(regionDir, familyEntry.getKey());
549      if (!fs.mkdirs(familyDir)) {
550        throw new IOException("Unable to create familyDir=" + familyDir);
551      }
552
553      for (SnapshotRegionManifest.StoreFile storeFile: familyEntry.getValue()) {
554        LOG.trace("Adding HFileLink " + storeFile.getName() + " to table=" + tableName);
555        restoreStoreFile(familyDir, regionInfo, storeFile, createBackRefs);
556      }
557    }
558  }
559
560  /**
561   * @return The set of files in the specified family directory.
562   */
563  private Set<String> getTableRegionFamilyFiles(final Path familyDir) throws IOException {
564    FileStatus[] hfiles = FSUtils.listStatus(fs, familyDir);
565    if (hfiles == null) return Collections.emptySet();
566
567    Set<String> familyFiles = new HashSet<>(hfiles.length);
568    for (int i = 0; i < hfiles.length; ++i) {
569      String hfileName = hfiles[i].getPath().getName();
570      familyFiles.add(hfileName);
571    }
572
573    return familyFiles;
574  }
575
576  /**
577   * Clone specified regions. For each region create a new region
578   * and create a HFileLink for each hfile.
579   */
580  private RegionInfo[] cloneHdfsRegions(final ThreadPoolExecutor exec,
581      final Map<String, SnapshotRegionManifest> regionManifests,
582      final List<RegionInfo> regions) throws IOException {
583    if (regions == null || regions.isEmpty()) return null;
584
585    final Map<String, RegionInfo> snapshotRegions = new HashMap<>(regions.size());
586
587    // clone region info (change embedded tableName with the new one)
588    RegionInfo[] clonedRegionsInfo = new RegionInfo[regions.size()];
589    for (int i = 0; i < clonedRegionsInfo.length; ++i) {
590      // clone the region info from the snapshot region info
591      RegionInfo snapshotRegionInfo = regions.get(i);
592      clonedRegionsInfo[i] = cloneRegionInfo(snapshotRegionInfo);
593
594      // add the region name mapping between snapshot and cloned
595      String snapshotRegionName = snapshotRegionInfo.getEncodedName();
596      String clonedRegionName = clonedRegionsInfo[i].getEncodedName();
597      regionsMap.put(Bytes.toBytes(snapshotRegionName), Bytes.toBytes(clonedRegionName));
598      LOG.info("clone region=" + snapshotRegionName + " as " + clonedRegionName);
599
600      // Add mapping between cloned region name and snapshot region info
601      snapshotRegions.put(clonedRegionName, snapshotRegionInfo);
602    }
603
604    // create the regions on disk
605    ModifyRegionUtils.createRegions(exec, conf, rootDir,
606      tableDesc, clonedRegionsInfo, new ModifyRegionUtils.RegionFillTask() {
607        @Override
608        public void fillRegion(final HRegion region) throws IOException {
609          RegionInfo snapshotHri = snapshotRegions.get(region.getRegionInfo().getEncodedName());
610          cloneRegion(region, snapshotHri, regionManifests.get(snapshotHri.getEncodedName()));
611        }
612      });
613
614    return clonedRegionsInfo;
615  }
616
617  /**
618   * Clone the mob region. For the region create a new region
619   * and create a HFileLink for each hfile.
620   */
621  private void cloneHdfsMobRegion(final Map<String, SnapshotRegionManifest> regionManifests,
622      final RegionInfo region) throws IOException {
623    // clone region info (change embedded tableName with the new one)
624    Path clonedRegionPath = MobUtils.getMobRegionPath(rootDir, tableDesc.getTableName());
625    cloneRegion(clonedRegionPath, region, regionManifests.get(region.getEncodedName()));
626  }
627
628  /**
629   * Clone region directory content from the snapshot info.
630   *
631   * Each region is encoded with the table name, so the cloned region will have
632   * a different region name.
633   *
634   * Instead of copying the hfiles a HFileLink is created.
635   *
636   * @param regionDir {@link Path} cloned dir
637   * @param snapshotRegionInfo
638   */
639  private void cloneRegion(final Path regionDir, final RegionInfo snapshotRegionInfo,
640      final SnapshotRegionManifest manifest) throws IOException {
641    final String tableName = tableDesc.getTableName().getNameAsString();
642    for (SnapshotRegionManifest.FamilyFiles familyFiles: manifest.getFamilyFilesList()) {
643      Path familyDir = new Path(regionDir, familyFiles.getFamilyName().toStringUtf8());
644      for (SnapshotRegionManifest.StoreFile storeFile: familyFiles.getStoreFilesList()) {
645        LOG.info("Adding HFileLink " + storeFile.getName() + " to table=" + tableName);
646        restoreStoreFile(familyDir, snapshotRegionInfo, storeFile, createBackRefs);
647      }
648    }
649  }
650
651  /**
652   * Clone region directory content from the snapshot info.
653   *
654   * Each region is encoded with the table name, so the cloned region will have
655   * a different region name.
656   *
657   * Instead of copying the hfiles a HFileLink is created.
658   *
659   * @param region {@link HRegion} cloned
660   * @param snapshotRegionInfo
661   */
662  private void cloneRegion(final HRegion region, final RegionInfo snapshotRegionInfo,
663      final SnapshotRegionManifest manifest) throws IOException {
664    cloneRegion(new Path(tableDir, region.getRegionInfo().getEncodedName()), snapshotRegionInfo,
665      manifest);
666  }
667
668  /**
669   * Create a new {@link HFileLink} to reference the store file.
670   * <p>The store file in the snapshot can be a simple hfile, an HFileLink or a reference.
671   * <ul>
672   *   <li>hfile: abc -> table=region-abc
673   *   <li>reference: abc.1234 -> table=region-abc.1234
674   *   <li>hfilelink: table=region-hfile -> table=region-hfile
675   * </ul>
676   * @param familyDir destination directory for the store file
677   * @param regionInfo destination region info for the table
678   * @param createBackRef - Whether back reference should be created. Defaults to true.
679   * @param storeFile store file name (can be a Reference, HFileLink or simple HFile)
680   */
681  private void restoreStoreFile(final Path familyDir, final RegionInfo regionInfo,
682      final SnapshotRegionManifest.StoreFile storeFile, final boolean createBackRef)
683          throws IOException {
684    String hfileName = storeFile.getName();
685    if (HFileLink.isHFileLink(hfileName)) {
686      HFileLink.createFromHFileLink(conf, fs, familyDir, hfileName, createBackRef);
687    } else if (StoreFileInfo.isReference(hfileName)) {
688      restoreReferenceFile(familyDir, regionInfo, storeFile);
689    } else {
690      HFileLink.create(conf, fs, familyDir, regionInfo, hfileName, createBackRef);
691    }
692  }
693
694  /**
695   * Create a new {@link Reference} as copy of the source one.
696   * <p><blockquote><pre>
697   * The source table looks like:
698   *    1234/abc      (original file)
699   *    5678/abc.1234 (reference file)
700   *
701   * After the clone operation looks like:
702   *   wxyz/table=1234-abc
703   *   stuv/table=1234-abc.wxyz
704   *
705   * NOTE that the region name in the clone changes (md5 of regioninfo)
706   * and the reference should reflect that change.
707   * </pre></blockquote>
708   * @param familyDir destination directory for the store file
709   * @param regionInfo destination region info for the table
710   * @param storeFile reference file name
711   */
712  private void restoreReferenceFile(final Path familyDir, final RegionInfo regionInfo,
713      final SnapshotRegionManifest.StoreFile storeFile) throws IOException {
714    String hfileName = storeFile.getName();
715
716    // Extract the referred information (hfile name and parent region)
717    Path refPath =
718        StoreFileInfo.getReferredToFile(new Path(new Path(new Path(new Path(snapshotTable
719            .getNamespaceAsString(), snapshotTable.getQualifierAsString()), regionInfo
720            .getEncodedName()), familyDir.getName()), hfileName));
721    String snapshotRegionName = refPath.getParent().getParent().getName();
722    String fileName = refPath.getName();
723
724    // The new reference should have the cloned region name as parent, if it is a clone.
725    String clonedRegionName = Bytes.toString(regionsMap.get(Bytes.toBytes(snapshotRegionName)));
726    if (clonedRegionName == null) clonedRegionName = snapshotRegionName;
727
728    // The output file should be a reference link table=snapshotRegion-fileName.clonedRegionName
729    Path linkPath = null;
730    String refLink = fileName;
731    if (!HFileLink.isHFileLink(fileName)) {
732      refLink = HFileLink.createHFileLinkName(snapshotTable, snapshotRegionName, fileName);
733      linkPath = new Path(familyDir,
734        HFileLink.createHFileLinkName(snapshotTable, regionInfo.getEncodedName(), hfileName));
735    }
736
737    Path outPath = new Path(familyDir, refLink + '.' + clonedRegionName);
738
739    // Create the new reference
740    if (storeFile.hasReference()) {
741      Reference reference = Reference.convert(storeFile.getReference());
742      reference.write(fs, outPath);
743    } else {
744      InputStream in;
745      if (linkPath != null) {
746        in = HFileLink.buildFromHFileLinkPattern(conf, linkPath).open(fs);
747      } else {
748        linkPath = new Path(new Path(HRegion.getRegionDir(snapshotManifest.getSnapshotDir(),
749                        regionInfo.getEncodedName()), familyDir.getName()), hfileName);
750        in = fs.open(linkPath);
751      }
752      OutputStream out = fs.create(outPath);
753      IOUtils.copyBytes(in, out, conf);
754    }
755
756    // Add the daughter region to the map
757    String regionName = Bytes.toString(regionsMap.get(regionInfo.getEncodedNameAsBytes()));
758    if (regionName == null) {
759      regionName = regionInfo.getEncodedName();
760    }
761    LOG.debug("Restore reference " + regionName + " to " + clonedRegionName);
762    synchronized (parentsMap) {
763      Pair<String, String> daughters = parentsMap.get(clonedRegionName);
764      if (daughters == null) {
765        // In case one side of the split is already compacted, regionName is put as both first and
766        // second of Pair
767        daughters = new Pair<>(regionName, regionName);
768        parentsMap.put(clonedRegionName, daughters);
769      } else if (!regionName.equals(daughters.getFirst())) {
770        daughters.setSecond(regionName);
771      }
772    }
773  }
774
775  /**
776   * Create a new {@link RegionInfo} from the snapshot region info.
777   * Keep the same startKey, endKey, regionId and split information but change
778   * the table name.
779   *
780   * @param snapshotRegionInfo Info for region to clone.
781   * @return the new HRegion instance
782   */
783  public RegionInfo cloneRegionInfo(final RegionInfo snapshotRegionInfo) {
784    return cloneRegionInfo(tableDesc.getTableName(), snapshotRegionInfo);
785  }
786
787  public static RegionInfo cloneRegionInfo(TableName tableName, RegionInfo snapshotRegionInfo) {
788    return RegionInfoBuilder.newBuilder(tableName)
789        .setStartKey(snapshotRegionInfo.getStartKey())
790        .setEndKey(snapshotRegionInfo.getEndKey())
791        .setSplit(snapshotRegionInfo.isSplit())
792        .setRegionId(snapshotRegionInfo.getRegionId())
793        .setOffline(snapshotRegionInfo.isOffline())
794        .build();
795  }
796
797  /**
798   * @return the set of the regions contained in the table
799   */
800  private List<RegionInfo> getTableRegions() throws IOException {
801    LOG.debug("get table regions: " + tableDir);
802    FileStatus[] regionDirs = FSUtils.listStatus(fs, tableDir, new FSUtils.RegionDirFilter(fs));
803    if (regionDirs == null) return null;
804
805    List<RegionInfo> regions = new ArrayList<>(regionDirs.length);
806    for (int i = 0; i < regionDirs.length; ++i) {
807      RegionInfo hri = HRegionFileSystem.loadRegionInfoFileContent(fs, regionDirs[i].getPath());
808      regions.add(hri);
809    }
810    LOG.debug("found " + regions.size() + " regions for table=" +
811        tableDesc.getTableName().getNameAsString());
812    return regions;
813  }
814
815  /**
816   * Copy the snapshot files for a snapshot scanner, discards meta changes.
817   * @param conf
818   * @param fs
819   * @param rootDir
820   * @param restoreDir
821   * @param snapshotName
822   * @throws IOException
823   */
824  public static RestoreMetaChanges copySnapshotForScanner(Configuration conf, FileSystem fs,
825      Path rootDir, Path restoreDir, String snapshotName) throws IOException {
826    // ensure that restore dir is not under root dir
827    if (!restoreDir.getFileSystem(conf).getUri().equals(rootDir.getFileSystem(conf).getUri())) {
828      throw new IllegalArgumentException("Filesystems for restore directory and HBase root " +
829          "directory should be the same");
830    }
831    if (restoreDir.toUri().getPath().startsWith(rootDir.toUri().getPath() +"/")) {
832      throw new IllegalArgumentException("Restore directory cannot be a sub directory of HBase " +
833          "root directory. RootDir: " + rootDir + ", restoreDir: " + restoreDir);
834    }
835
836    Path snapshotDir = SnapshotDescriptionUtils.getCompletedSnapshotDir(snapshotName, rootDir);
837    SnapshotDescription snapshotDesc = SnapshotDescriptionUtils.readSnapshotInfo(fs, snapshotDir);
838    SnapshotManifest manifest = SnapshotManifest.open(conf, fs, snapshotDir, snapshotDesc);
839
840    MonitoredTask status = TaskMonitor.get().createStatus(
841        "Restoring  snapshot '" + snapshotName + "' to directory " + restoreDir);
842    ForeignExceptionDispatcher monitor = new ForeignExceptionDispatcher();
843
844    // we send createBackRefs=false so that restored hfiles do not create back reference links
845    // in the base hbase root dir.
846    RestoreSnapshotHelper helper = new RestoreSnapshotHelper(conf, fs,
847      manifest, manifest.getTableDescriptor(), restoreDir, monitor, status, false);
848    RestoreMetaChanges metaChanges = helper.restoreHdfsRegions(); // TODO: parallelize.
849
850    if (LOG.isDebugEnabled()) {
851      LOG.debug("Restored table dir:" + restoreDir);
852      FSUtils.logFileSystemState(fs, restoreDir, LOG);
853    }
854    return metaChanges;
855  }
856
857  public static void restoreSnapshotAcl(SnapshotDescription snapshot, TableName newTableName,
858      Configuration conf) throws IOException {
859    if (snapshot.hasUsersAndPermissions() && snapshot.getUsersAndPermissions() != null) {
860      LOG.info("Restore snapshot acl to table. snapshot: " + snapshot + ", table: " + newTableName);
861      ListMultimap<String, TablePermission> perms =
862          ShadedAccessControlUtil.toUserTablePermissions(snapshot.getUsersAndPermissions());
863      try (Connection conn = ConnectionFactory.createConnection(conf)) {
864        for (Entry<String, TablePermission> e : perms.entries()) {
865          String user = e.getKey();
866          TablePermission perm = e.getValue();
867          perm.setTableName(newTableName);
868          AccessControlClient.grant(conn, perm.getTableName(), user, perm.getFamily(),
869            perm.getQualifier(), perm.getActions());
870        }
871      } catch (Throwable e) {
872        throw new IOException("Grant acl into newly creatd table failed. snapshot: " + snapshot
873            + ", table: " + newTableName, e);
874      }
875    }
876  }
877}