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