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