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