View Javadoc

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