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