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