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