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