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