View Javadoc

1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  
19  package org.apache.hadoop.hbase.snapshot;
20  
21  import java.io.IOException;
22  import java.io.InputStream;
23  import java.io.OutputStream;
24  import java.util.ArrayList;
25  import java.util.Arrays;
26  import java.util.Collections;
27  import java.util.HashMap;
28  import java.util.HashSet;
29  import java.util.LinkedList;
30  import java.util.List;
31  import java.util.Map;
32  import java.util.Map.Entry;
33  import java.util.Set;
34  import java.util.TreeMap;
35  import java.util.concurrent.ThreadPoolExecutor;
36  
37  import com.google.common.collect.ListMultimap;
38  import org.apache.commons.logging.Log;
39  import org.apache.commons.logging.LogFactory;
40  import org.apache.hadoop.hbase.classification.InterfaceAudience;
41  import org.apache.hadoop.conf.Configuration;
42  import org.apache.hadoop.fs.FileStatus;
43  import org.apache.hadoop.fs.FileSystem;
44  import org.apache.hadoop.fs.Path;
45  import org.apache.hadoop.hbase.HRegionInfo;
46  import org.apache.hadoop.hbase.HTableDescriptor;
47  import org.apache.hadoop.hbase.TableName;
48  import org.apache.hadoop.hbase.backup.HFileArchiver;
49  import org.apache.hadoop.hbase.MetaTableAccessor;
50  import org.apache.hadoop.hbase.client.Connection;
51  import org.apache.hadoop.hbase.errorhandling.ForeignExceptionDispatcher;
52  import org.apache.hadoop.hbase.io.HFileLink;
53  import org.apache.hadoop.hbase.io.Reference;
54  import org.apache.hadoop.hbase.monitoring.MonitoredTask;
55  import org.apache.hadoop.hbase.monitoring.TaskMonitor;
56  import org.apache.hadoop.hbase.protobuf.ProtobufUtil;
57  import org.apache.hadoop.hbase.protobuf.generated.HBaseProtos.SnapshotDescription;
58  import org.apache.hadoop.hbase.protobuf.generated.SnapshotProtos.SnapshotRegionManifest;
59  import org.apache.hadoop.hbase.regionserver.HRegion;
60  import org.apache.hadoop.hbase.regionserver.HRegionFileSystem;
61  import org.apache.hadoop.hbase.regionserver.StoreFileInfo;
62  import org.apache.hadoop.hbase.security.access.AccessControlClient;
63  import org.apache.hadoop.hbase.security.access.TablePermission;
64  import org.apache.hadoop.hbase.util.Bytes;
65  import org.apache.hadoop.hbase.util.FSUtils;
66  import org.apache.hadoop.hbase.util.ModifyRegionUtils;
67  import org.apache.hadoop.hbase.util.Pair;
68  import org.apache.hadoop.io.IOUtils;
69  
70  /**
71   * Helper to Restore/Clone a Snapshot
72   *
73   * <p>The helper assumes that a table is already created, and by calling restore()
74   * the content present in the snapshot will be restored as the new content of the table.
75   *
76   * <p>Clone from Snapshot: If the target table is empty, the restore operation
77   * is just a "clone operation", where the only operations are:
78   * <ul>
79   *  <li>for each region in the snapshot create a new region
80   *    (note that the region will have a different name, since the encoding contains the table name)
81   *  <li>for each file in the region create a new HFileLink to point to the original file.
82   *  <li>restore the logs, if any
83   * </ul>
84   *
85   * <p>Restore from Snapshot:
86   * <ul>
87   *  <li>for each region in the table verify which are available in the snapshot and which are not
88   *    <ul>
89   *    <li>if the region is not present in the snapshot, remove it.
90   *    <li>if the region is present in the snapshot
91   *      <ul>
92   *      <li>for each file in the table region verify which are available in the snapshot
93   *        <ul>
94   *          <li>if the hfile is not present in the snapshot, remove it
95   *          <li>if the hfile is present, keep it (nothing to do)
96   *        </ul>
97   *      <li>for each file in the snapshot region but not in the table
98   *        <ul>
99   *          <li>create a new HFileLink that point to the original file
100  *        </ul>
101  *      </ul>
102  *    </ul>
103  *  <li>for each region in the snapshot not present in the current table state
104  *    <ul>
105  *    <li>create a new region and for each file in the region create a new HFileLink
106  *      (This is the same as the clone operation)
107  *    </ul>
108  *  <li>restore the logs, if any
109  * </ul>
110  */
111 @InterfaceAudience.Private
112 public class RestoreSnapshotHelper {
113   private static final Log LOG = LogFactory.getLog(RestoreSnapshotHelper.class);
114 
115   private final Map<byte[], byte[]> regionsMap =
116         new TreeMap<byte[], byte[]>(Bytes.BYTES_COMPARATOR);
117 
118   private final Map<String, Pair<String, String> > parentsMap =
119       new HashMap<String, Pair<String, String> >();
120 
121   private final ForeignExceptionDispatcher monitor;
122   private final MonitoredTask status;
123 
124   private final SnapshotManifest snapshotManifest;
125   private final SnapshotDescription snapshotDesc;
126   private final TableName snapshotTable;
127 
128   private final HTableDescriptor tableDesc;
129   private final Path rootDir;
130   private final Path tableDir;
131 
132   private final Configuration conf;
133   private final FileSystem fs;
134   private final boolean createBackRefs;
135 
136   public RestoreSnapshotHelper(final Configuration conf,
137       final FileSystem fs,
138       final SnapshotManifest manifest,
139       final HTableDescriptor tableDescriptor,
140       final Path rootDir,
141       final ForeignExceptionDispatcher monitor,
142       final MonitoredTask status) {
143     this(conf, fs, manifest, tableDescriptor, rootDir, monitor, status, true);
144   }
145 
146   public RestoreSnapshotHelper(final Configuration conf,
147       final FileSystem fs,
148       final SnapshotManifest manifest,
149       final HTableDescriptor tableDescriptor,
150       final Path rootDir,
151       final ForeignExceptionDispatcher monitor,
152       final MonitoredTask status,
153       final boolean createBackRefs)
154   {
155     this.fs = fs;
156     this.conf = conf;
157     this.snapshotManifest = manifest;
158     this.snapshotDesc = manifest.getSnapshotDescription();
159     this.snapshotTable = TableName.valueOf(snapshotDesc.getTable());
160     this.tableDesc = tableDescriptor;
161     this.rootDir = rootDir;
162     this.tableDir = FSUtils.getTableDir(rootDir, tableDesc.getTableName());
163     this.monitor = monitor;
164     this.status = status;
165     this.createBackRefs = createBackRefs;
166   }
167 
168   /**
169    * Restore the on-disk table to a specified snapshot state.
170    * @return the set of regions touched by the restore operation
171    */
172   public RestoreMetaChanges restoreHdfsRegions() throws IOException {
173     ThreadPoolExecutor exec = SnapshotManifest.createExecutor(conf, "RestoreSnapshot");
174     try {
175       return restoreHdfsRegions(exec);
176     } finally {
177       exec.shutdown();
178     }
179   }
180 
181   private RestoreMetaChanges restoreHdfsRegions(final ThreadPoolExecutor exec) throws IOException {
182     LOG.debug("starting restore");
183 
184     Map<String, SnapshotRegionManifest> regionManifests = snapshotManifest.getRegionManifestsMap();
185     if (regionManifests == null) {
186       LOG.warn("Nothing to restore. Snapshot " + snapshotDesc + " looks empty");
187       return null;
188     }
189 
190     RestoreMetaChanges metaChanges = new RestoreMetaChanges(tableDesc, parentsMap);
191 
192     // Take a copy of the manifest.keySet() since we are going to modify
193     // this instance, by removing the regions already present in the restore dir.
194     Set<String> regionNames = new HashSet<String>(regionManifests.keySet());
195 
196     // Identify which region are still available and which not.
197     // NOTE: we rely upon the region name as: "table name, start key, end key"
198     List<HRegionInfo> tableRegions = getTableRegions();
199     if (tableRegions != null) {
200       monitor.rethrowException();
201       for (HRegionInfo regionInfo: tableRegions) {
202         String regionName = regionInfo.getEncodedName();
203         if (regionNames.contains(regionName)) {
204           LOG.info("region to restore: " + regionName);
205           regionNames.remove(regionName);
206           // Add the regionInfo from snapshot manifest, so that will not miss parent region details
207           metaChanges.addRegionToRestore(
208             HRegionInfo.convert(regionManifests.get(regionName).getRegionInfo()));
209         } else {
210           LOG.info("region to remove: " + regionName);
211           metaChanges.addRegionToRemove(regionInfo);
212         }
213       }
214     }
215 
216     // Regions to Add: present in the snapshot but not in the current table
217     List<HRegionInfo> regionsToAdd = new ArrayList<HRegionInfo>(regionNames.size());
218     if (regionNames.size() > 0) {
219       monitor.rethrowException();
220       for (String regionName: regionNames) {
221         LOG.info("region to add: " + regionName);
222         regionsToAdd.add(HRegionInfo.convert(regionManifests.get(regionName).getRegionInfo()));
223       }
224     }
225 
226     // Create new regions cloning from the snapshot
227     // HBASE-20008: We need to call cloneHdfsRegions() before restoreHdfsRegions() because
228     // regionsMap is constructed in cloneHdfsRegions() and it can be used in restoreHdfsRegions().
229     monitor.rethrowException();
230     status.setStatus("Cloning regions...");
231     HRegionInfo[] clonedRegions = cloneHdfsRegions(exec, regionManifests, regionsToAdd);
232     metaChanges.setNewRegions(clonedRegions);
233     status.setStatus("Finished cloning regions.");
234 
235     // Restore regions using the snapshot data
236     monitor.rethrowException();
237     status.setStatus("Restoring table regions...");
238     restoreHdfsRegions(exec, regionManifests, metaChanges.getRegionsToRestore());
239     status.setStatus("Finished restoring all table regions.");
240 
241     // Remove regions from the current table
242     monitor.rethrowException();
243     status.setStatus("Starting to delete excess regions from table");
244     removeHdfsRegions(exec, metaChanges.getRegionsToRemove());
245     status.setStatus("Finished deleting excess regions from table.");
246 
247     return metaChanges;
248   }
249 
250   /**
251    * Describe the set of operations needed to update hbase:meta after restore.
252    */
253   public static class RestoreMetaChanges {
254     private final Map<String, Pair<String, String> > parentsMap;
255     private final HTableDescriptor htd;
256 
257     private List<HRegionInfo> regionsToRestore = null;
258     private List<HRegionInfo> regionsToRemove = null;
259     private List<HRegionInfo> regionsToAdd = null;
260 
261     RestoreMetaChanges(HTableDescriptor htd, Map<String, Pair<String, String> > parentsMap) {
262       this.parentsMap = parentsMap;
263       this.htd = htd;
264     }
265 
266     public HTableDescriptor getTableDescriptor() {
267       return htd;
268     }
269 
270     /**
271      * @return true if there're new regions
272      */
273     public boolean hasRegionsToAdd() {
274       return this.regionsToAdd != null && this.regionsToAdd.size() > 0;
275     }
276 
277     /**
278      * Returns the list of new regions added during the on-disk restore.
279      * The caller is responsible to add the regions to META.
280      * e.g MetaTableAccessor.addRegionsToMeta(...)
281      * @return the list of regions to add to META
282      */
283     public List<HRegionInfo> getRegionsToAdd() {
284       return this.regionsToAdd;
285     }
286 
287     /**
288      * @return true if there're regions to restore
289      */
290     public boolean hasRegionsToRestore() {
291       return this.regionsToRestore != null && this.regionsToRestore.size() > 0;
292     }
293 
294     /**
295      * Returns the list of 'restored regions' during the on-disk restore.
296      * The caller is responsible to add the regions to hbase:meta if not present.
297      * @return the list of regions restored
298      */
299     public List<HRegionInfo> getRegionsToRestore() {
300       return this.regionsToRestore;
301     }
302 
303     /**
304      * @return true if there're regions to remove
305      */
306     public boolean hasRegionsToRemove() {
307       return this.regionsToRemove != null && this.regionsToRemove.size() > 0;
308     }
309 
310     /**
311      * Returns the list of regions removed during the on-disk restore.
312      * The caller is responsible to remove the regions from META.
313      * e.g. MetaTableAccessor.deleteRegions(...)
314      * @return the list of regions to remove from META
315      */
316     public List<HRegionInfo> getRegionsToRemove() {
317       return this.regionsToRemove;
318     }
319 
320     void setNewRegions(final HRegionInfo[] hris) {
321       if (hris != null) {
322         regionsToAdd = Arrays.asList(hris);
323       } else {
324         regionsToAdd = null;
325       }
326     }
327 
328     void addRegionToRemove(final HRegionInfo hri) {
329       if (regionsToRemove == null) {
330         regionsToRemove = new LinkedList<HRegionInfo>();
331       }
332       regionsToRemove.add(hri);
333     }
334 
335     void addRegionToRestore(final HRegionInfo hri) {
336       if (regionsToRestore == null) {
337         regionsToRestore = new LinkedList<HRegionInfo>();
338       }
339       regionsToRestore.add(hri);
340     }
341 
342     public void updateMetaParentRegions(Connection connection,
343         final List<HRegionInfo> regionInfos) throws IOException {
344       if (regionInfos == null || parentsMap.isEmpty()) return;
345 
346       // Extract region names and offlined regions
347       Map<String, HRegionInfo> regionsByName = new HashMap<String, HRegionInfo>(regionInfos.size());
348       List<HRegionInfo> parentRegions = new LinkedList<>();
349       for (HRegionInfo regionInfo: regionInfos) {
350         if (regionInfo.isSplitParent()) {
351           parentRegions.add(regionInfo);
352         } else {
353           regionsByName.put(regionInfo.getEncodedName(), regionInfo);
354         }
355       }
356 
357       // Update Offline parents
358       for (HRegionInfo regionInfo: parentRegions) {
359         Pair<String, String> daughters = parentsMap.get(regionInfo.getEncodedName());
360         if (daughters == null) {
361           // The snapshot contains an unreferenced region.
362           // It will be removed by the CatalogJanitor.
363           LOG.warn("Skip update of unreferenced offline parent: " + regionInfo);
364           continue;
365         }
366 
367         // One side of the split is already compacted
368         if (daughters.getSecond() == null) {
369           daughters.setSecond(daughters.getFirst());
370         }
371 
372         LOG.debug("Update splits parent " + regionInfo.getEncodedName() + " -> " + daughters);
373         MetaTableAccessor.addRegionToMeta(connection, regionInfo,
374           regionsByName.get(daughters.getFirst()),
375           regionsByName.get(daughters.getSecond()));
376       }
377     }
378   }
379 
380   /**
381    * Remove specified regions from the file-system, using the archiver.
382    */
383   private void removeHdfsRegions(final ThreadPoolExecutor exec, final List<HRegionInfo> regions)
384       throws IOException {
385     if (regions == null || regions.size() == 0) return;
386     ModifyRegionUtils.editRegions(exec, regions, new ModifyRegionUtils.RegionEditTask() {
387       @Override
388       public void editRegion(final HRegionInfo hri) throws IOException {
389         HFileArchiver.archiveRegion(conf, fs, hri);
390       }
391     });
392   }
393 
394   /**
395    * Restore specified regions by restoring content to the snapshot state.
396    */
397   private void restoreHdfsRegions(final ThreadPoolExecutor exec,
398       final Map<String, SnapshotRegionManifest> regionManifests,
399       final List<HRegionInfo> regions) throws IOException {
400     if (regions == null || regions.size() == 0) return;
401     ModifyRegionUtils.editRegions(exec, regions, new ModifyRegionUtils.RegionEditTask() {
402       @Override
403       public void editRegion(final HRegionInfo hri) throws IOException {
404         restoreRegion(hri, regionManifests.get(hri.getEncodedName()));
405       }
406     });
407   }
408 
409   private Map<String, List<SnapshotRegionManifest.StoreFile>> getRegionHFileReferences(
410       final SnapshotRegionManifest manifest) {
411     Map<String, List<SnapshotRegionManifest.StoreFile>> familyMap =
412       new HashMap<String, List<SnapshotRegionManifest.StoreFile>>(manifest.getFamilyFilesCount());
413     for (SnapshotRegionManifest.FamilyFiles familyFiles: manifest.getFamilyFilesList()) {
414       familyMap.put(familyFiles.getFamilyName().toStringUtf8(),
415         new ArrayList<SnapshotRegionManifest.StoreFile>(familyFiles.getStoreFilesList()));
416     }
417     return familyMap;
418   }
419 
420   /**
421    * Restore region by removing files not in the snapshot
422    * and adding the missing ones from the snapshot.
423    */
424   private void restoreRegion(final HRegionInfo regionInfo,
425       final SnapshotRegionManifest regionManifest) throws IOException {
426     Map<String, List<SnapshotRegionManifest.StoreFile>> snapshotFiles =
427                 getRegionHFileReferences(regionManifest);
428 
429     Path regionDir = new Path(tableDir, regionInfo.getEncodedName());
430     String tableName = tableDesc.getTableName().getNameAsString();
431 
432     // Restore families present in the table
433     for (Path familyDir: FSUtils.getFamilyDirs(fs, regionDir)) {
434       byte[] family = Bytes.toBytes(familyDir.getName());
435       Set<String> familyFiles = getTableRegionFamilyFiles(familyDir);
436       List<SnapshotRegionManifest.StoreFile> snapshotFamilyFiles =
437           snapshotFiles.remove(familyDir.getName());
438       if (snapshotFamilyFiles != null) {
439         List<SnapshotRegionManifest.StoreFile> hfilesToAdd =
440             new ArrayList<SnapshotRegionManifest.StoreFile>();
441         for (SnapshotRegionManifest.StoreFile storeFile: snapshotFamilyFiles) {
442           if (familyFiles.contains(storeFile.getName())) {
443             // HFile already present
444             familyFiles.remove(storeFile.getName());
445           } else {
446             // HFile missing
447             hfilesToAdd.add(storeFile);
448           }
449         }
450 
451         // Remove hfiles not present in the snapshot
452         for (String hfileName: familyFiles) {
453           Path hfile = new Path(familyDir, hfileName);
454           LOG.trace("Removing hfile=" + hfileName +
455             " from region=" + regionInfo.getEncodedName() + " table=" + tableName);
456           HFileArchiver.archiveStoreFile(conf, fs, regionInfo, tableDir, family, hfile);
457         }
458 
459         // Restore Missing files
460         for (SnapshotRegionManifest.StoreFile storeFile: hfilesToAdd) {
461           LOG.debug("Adding HFileLink " + storeFile.getName() +
462             " to region=" + regionInfo.getEncodedName() + " table=" + tableName);
463           restoreStoreFile(familyDir, regionInfo, storeFile, createBackRefs);
464         }
465       } else {
466         // Family doesn't exists in the snapshot
467         LOG.trace("Removing family=" + Bytes.toString(family) +
468           " from region=" + regionInfo.getEncodedName() + " table=" + tableName);
469         HFileArchiver.archiveFamily(fs, conf, regionInfo, tableDir, family);
470         fs.delete(familyDir, true);
471       }
472     }
473 
474     // Add families not present in the table
475     for (Map.Entry<String, List<SnapshotRegionManifest.StoreFile>> familyEntry:
476                                                                       snapshotFiles.entrySet()) {
477       Path familyDir = new Path(regionDir, familyEntry.getKey());
478       if (!fs.mkdirs(familyDir)) {
479         throw new IOException("Unable to create familyDir=" + familyDir);
480       }
481 
482       for (SnapshotRegionManifest.StoreFile storeFile: familyEntry.getValue()) {
483         LOG.trace("Adding HFileLink " + storeFile.getName() + " to table=" + tableName);
484         restoreStoreFile(familyDir, regionInfo, storeFile, createBackRefs);
485       }
486     }
487   }
488 
489   /**
490    * @return The set of files in the specified family directory.
491    */
492   private Set<String> getTableRegionFamilyFiles(final Path familyDir) throws IOException {
493     FileStatus[] hfiles = FSUtils.listStatus(fs, familyDir);
494     if (hfiles == null) return Collections.emptySet();
495 
496     Set<String> familyFiles = new HashSet<String>(hfiles.length);
497     for (int i = 0; i < hfiles.length; ++i) {
498       String hfileName = hfiles[i].getPath().getName();
499       familyFiles.add(hfileName);
500     }
501 
502     return familyFiles;
503   }
504 
505   /**
506    * Clone specified regions. For each region create a new region
507    * and create a HFileLink for each hfile.
508    */
509   private HRegionInfo[] cloneHdfsRegions(final ThreadPoolExecutor exec,
510       final Map<String, SnapshotRegionManifest> regionManifests,
511       final List<HRegionInfo> regions) throws IOException {
512     if (regions == null || regions.size() == 0) return null;
513 
514     final Map<String, HRegionInfo> snapshotRegions =
515       new HashMap<String, HRegionInfo>(regions.size());
516 
517     // clone region info (change embedded tableName with the new one)
518     HRegionInfo[] clonedRegionsInfo = new HRegionInfo[regions.size()];
519     for (int i = 0; i < clonedRegionsInfo.length; ++i) {
520       // clone the region info from the snapshot region info
521       HRegionInfo snapshotRegionInfo = regions.get(i);
522       clonedRegionsInfo[i] = cloneRegionInfo(snapshotRegionInfo);
523 
524       // add the region name mapping between snapshot and cloned
525       String snapshotRegionName = snapshotRegionInfo.getEncodedName();
526       String clonedRegionName = clonedRegionsInfo[i].getEncodedName();
527       regionsMap.put(Bytes.toBytes(snapshotRegionName), Bytes.toBytes(clonedRegionName));
528       LOG.info("clone region=" + snapshotRegionName + " as " + clonedRegionName);
529 
530       // Add mapping between cloned region name and snapshot region info
531       snapshotRegions.put(clonedRegionName, snapshotRegionInfo);
532     }
533 
534     // create the regions on disk
535     ModifyRegionUtils.createRegions(exec, conf, rootDir, tableDir,
536       tableDesc, clonedRegionsInfo, new ModifyRegionUtils.RegionFillTask() {
537         @Override
538         public void fillRegion(final HRegion region) throws IOException {
539           HRegionInfo snapshotHri = snapshotRegions.get(region.getRegionInfo().getEncodedName());
540           cloneRegion(region, snapshotHri, regionManifests.get(snapshotHri.getEncodedName()));
541         }
542       });
543 
544     return clonedRegionsInfo;
545   }
546 
547   /**
548    * Clone region directory content from the snapshot info.
549    *
550    * Each region is encoded with the table name, so the cloned region will have
551    * a different region name.
552    *
553    * Instead of copying the hfiles a HFileLink is created.
554    *
555    * @param region {@link HRegion} cloned
556    * @param snapshotRegionInfo
557    */
558   private void cloneRegion(final HRegion region, final HRegionInfo snapshotRegionInfo,
559       final SnapshotRegionManifest manifest) throws IOException {
560     final Path regionDir = new Path(tableDir, region.getRegionInfo().getEncodedName());
561     final String tableName = tableDesc.getTableName().getNameAsString();
562     for (SnapshotRegionManifest.FamilyFiles familyFiles: manifest.getFamilyFilesList()) {
563       Path familyDir = new Path(regionDir, familyFiles.getFamilyName().toStringUtf8());
564       for (SnapshotRegionManifest.StoreFile storeFile: familyFiles.getStoreFilesList()) {
565         LOG.info("Adding HFileLink " + storeFile.getName() + " to table=" + tableName);
566         restoreStoreFile(familyDir, snapshotRegionInfo, storeFile, createBackRefs);
567       }
568     }
569   }
570 
571   /**
572    * Create a new {@link HFileLink} to reference the store file.
573    * <p>The store file in the snapshot can be a simple hfile, an HFileLink or a reference.
574    * <ul>
575    *   <li>hfile: abc -> table=region-abc
576    *   <li>reference: abc.1234 -> table=region-abc.1234
577    *   <li>hfilelink: table=region-hfile -> table=region-hfile
578    * </ul>
579    * @param familyDir destination directory for the store file
580    * @param regionInfo destination region info for the table
581    * @param storeFile store file name (can be a Reference, HFileLink or simple HFile)
582    * @param createBackRef - Whether back reference should be created. Defaults to true.
583    */
584   private void restoreStoreFile(final Path familyDir, final HRegionInfo regionInfo,
585       final SnapshotRegionManifest.StoreFile storeFile, final boolean createBackRef)
586           throws IOException {
587     String hfileName = storeFile.getName();
588     if (HFileLink.isHFileLink(hfileName)) {
589       HFileLink.createFromHFileLink(conf, fs, familyDir, hfileName, createBackRef);
590     } else if (StoreFileInfo.isReference(hfileName)) {
591       restoreReferenceFile(familyDir, regionInfo, storeFile);
592     } else {
593       HFileLink.create(conf, fs, familyDir, regionInfo, hfileName, createBackRef);
594     }
595   }
596 
597   /**
598    * Create a new {@link Reference} as copy of the source one.
599    * <p><blockquote><pre>
600    * The source table looks like:
601    *    1234/abc      (original file)
602    *    5678/abc.1234 (reference file)
603    *
604    * After the clone operation looks like:
605    *   wxyz/table=1234-abc
606    *   stuv/table=1234-abc.wxyz
607    *
608    * NOTE that the region name in the clone changes (md5 of regioninfo)
609    * and the reference should reflect that change.
610    * </pre></blockquote>
611    * @param familyDir destination directory for the store file
612    * @param regionInfo destination region info for the table
613    * @param storeFile reference file name
614    */
615   private void restoreReferenceFile(final Path familyDir, final HRegionInfo regionInfo,
616       final SnapshotRegionManifest.StoreFile storeFile) throws IOException {
617     String hfileName = storeFile.getName();
618 
619     // Extract the referred information (hfile name and parent region)
620     Path refPath =
621         StoreFileInfo.getReferredToFile(new Path(new Path(new Path(new Path(snapshotTable
622             .getNamespaceAsString(), snapshotTable.getQualifierAsString()), regionInfo
623             .getEncodedName()), familyDir.getName()), hfileName)); 
624     String snapshotRegionName = refPath.getParent().getParent().getName();
625     String fileName = refPath.getName();
626 
627     // The new reference should have the cloned region name as parent, if it is a clone.
628     String clonedRegionName = Bytes.toString(regionsMap.get(Bytes.toBytes(snapshotRegionName)));
629     if (clonedRegionName == null) clonedRegionName = snapshotRegionName;
630 
631     // The output file should be a reference link table=snapshotRegion-fileName.clonedRegionName
632     Path linkPath = null;
633     String refLink = fileName;
634     if (!HFileLink.isHFileLink(fileName)) {
635       refLink = HFileLink.createHFileLinkName(snapshotTable, snapshotRegionName, fileName);
636       linkPath = new Path(familyDir,
637         HFileLink.createHFileLinkName(snapshotTable, regionInfo.getEncodedName(), hfileName));
638     }
639 
640     Path outPath = new Path(familyDir, refLink + '.' + clonedRegionName);
641 
642     // Create the new reference
643     if (storeFile.hasReference()) {
644       Reference reference = Reference.convert(storeFile.getReference());
645       reference.write(fs, outPath);
646     } else {
647       InputStream in;
648       if (linkPath != null) {
649         in = HFileLink.buildFromHFileLinkPattern(conf, linkPath).open(fs);
650       } else {
651         linkPath = new Path(new Path(new Path(snapshotManifest.getSnapshotDir(),
652           regionInfo.getEncodedName()),
653           familyDir.getName()), hfileName);
654         in = fs.open(linkPath);
655       }
656       OutputStream out = fs.create(outPath);
657       IOUtils.copyBytes(in, out, conf);
658     }
659 
660     // Add the daughter region to the map
661     String regionName = Bytes.toString(regionsMap.get(regionInfo.getEncodedNameAsBytes()));
662     if (regionName == null) {
663       regionName = regionInfo.getEncodedName();
664     }
665     LOG.debug("Restore reference " + regionName + " to " + clonedRegionName);
666     synchronized (parentsMap) {
667       Pair<String, String> daughters = parentsMap.get(clonedRegionName);
668       if (daughters == null) {
669         // In case one side of the split is already compacted, regionName is put as both first and
670         // second of Pair
671         daughters = new Pair<String, String>(regionName, regionName);
672         parentsMap.put(clonedRegionName, daughters);
673       } else if (!regionName.equals(daughters.getFirst())) {
674         daughters.setSecond(regionName);
675       }
676     }
677   }
678 
679   /**
680    * Create a new {@link HRegionInfo} from the snapshot region info.
681    * Keep the same startKey, endKey, regionId and split information but change
682    * the table name.
683    *
684    * @param snapshotRegionInfo Info for region to clone.
685    * @return the new HRegion instance
686    */
687   public HRegionInfo cloneRegionInfo(final HRegionInfo snapshotRegionInfo) {
688     return cloneRegionInfo(tableDesc.getTableName(), snapshotRegionInfo);
689   }
690 
691   public static HRegionInfo cloneRegionInfo(TableName tableName, HRegionInfo snapshotRegionInfo) {
692     HRegionInfo regionInfo = new HRegionInfo(tableName,
693                       snapshotRegionInfo.getStartKey(), snapshotRegionInfo.getEndKey(),
694                       snapshotRegionInfo.isSplit(), snapshotRegionInfo.getRegionId());
695     regionInfo.setOffline(snapshotRegionInfo.isOffline());
696     return regionInfo;
697   }
698 
699   /**
700    * @return the set of the regions contained in the table
701    */
702   private List<HRegionInfo> getTableRegions() throws IOException {
703     LOG.debug("get table regions: " + tableDir);
704     FileStatus[] regionDirs = FSUtils.listStatus(fs, tableDir, new FSUtils.RegionDirFilter(fs));
705     if (regionDirs == null) return null;
706 
707     List<HRegionInfo> regions = new ArrayList<HRegionInfo>(regionDirs.length);
708     for (int i = 0; i < regionDirs.length; ++i) {
709       HRegionInfo hri = HRegionFileSystem.loadRegionInfoFileContent(fs, regionDirs[i].getPath());
710       regions.add(hri);
711     }
712     LOG.debug("found " + regions.size() + " regions for table=" +
713         tableDesc.getTableName().getNameAsString());
714     return regions;
715   }
716 
717   /**
718    * Copy the snapshot files for a snapshot scanner, discards meta changes.
719    * @param conf
720    * @param fs
721    * @param rootDir
722    * @param restoreDir
723    * @param snapshotName
724    * @throws IOException
725    */
726   public static RestoreMetaChanges copySnapshotForScanner(Configuration conf, FileSystem fs,
727       Path rootDir, Path restoreDir, String snapshotName) throws IOException {
728     // ensure that restore dir is not under root dir
729     if (!restoreDir.getFileSystem(conf).getUri().equals(rootDir.getFileSystem(conf).getUri())) {
730       throw new IllegalArgumentException("Filesystems for restore directory and HBase root directory " +
731           "should be the same");
732     }
733     if (restoreDir.toUri().getPath().startsWith(rootDir.toUri().getPath() +"/")) {
734       throw new IllegalArgumentException("Restore directory cannot be a sub directory of HBase " +
735           "root directory. RootDir: " + rootDir + ", restoreDir: " + restoreDir);
736     }
737 
738     Path snapshotDir = SnapshotDescriptionUtils.getCompletedSnapshotDir(snapshotName, rootDir);
739     SnapshotDescription snapshotDesc = SnapshotDescriptionUtils.readSnapshotInfo(fs, snapshotDir);
740     SnapshotManifest manifest = SnapshotManifest.open(conf, fs, snapshotDir, snapshotDesc);
741 
742     MonitoredTask status = TaskMonitor.get().createStatus(
743         "Restoring  snapshot '" + snapshotName + "' to directory " + restoreDir);
744     ForeignExceptionDispatcher monitor = new ForeignExceptionDispatcher();
745 
746     // we send createBackRefs=false so that restored hfiles do not create back reference links
747     // in the base hbase root dir.
748     RestoreSnapshotHelper helper = new RestoreSnapshotHelper(conf, fs,
749       manifest, manifest.getTableDescriptor(), restoreDir, monitor, status, false);
750     RestoreMetaChanges metaChanges = helper.restoreHdfsRegions(); // TODO: parallelize.
751 
752     if (LOG.isDebugEnabled()) {
753       LOG.debug("Restored table dir:" + restoreDir);
754       FSUtils.logFileSystemState(fs, restoreDir, LOG);
755     }
756     return metaChanges;
757   }
758 
759   public static void restoreSnapshotACL(SnapshotDescription snapshot, TableName newTableName,
760       Configuration conf) throws IOException {
761     if (snapshot.hasUsersAndPermissions()) {
762       LOG.info("Restore snapshot acl to table. snapshot: " + snapshot + ", table: " + newTableName);
763       ListMultimap<String, TablePermission> perms =
764           ProtobufUtil.toUserTablePermissions(snapshot.getUsersAndPermissions());
765       try {
766         for (Entry<String, TablePermission> e : perms.entries()) {
767           String user = e.getKey();
768           TablePermission perm = e.getValue();
769           perm.setTableName(newTableName);
770           AccessControlClient.grant(conf, perm.getTableName(), user, perm.getFamily(),
771             perm.getQualifier(), perm.getActions());
772         }
773       } catch (Throwable e) {
774         throw new IOException("Grant acl into newly creatd table failed. snapshot: " + snapshot
775             + ", table: " + newTableName, e);
776       }
777     }
778   }
779 }