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  package org.apache.hadoop.hbase.snapshot;
19  
20  import static org.junit.Assert.assertEquals;
21  import static org.junit.Assert.assertFalse;
22  import static org.junit.Assert.assertTrue;
23  
24  import java.io.IOException;
25  import java.util.Collections;
26  import java.util.ArrayList;
27  import java.util.HashSet;
28  import java.util.List;
29  import java.util.Map;
30  import java.util.Set;
31  import java.util.TreeSet;
32  
33  import org.apache.commons.logging.Log;
34  import org.apache.commons.logging.LogFactory;
35  import org.apache.hadoop.conf.Configuration;
36  import org.apache.hadoop.fs.FSDataOutputStream;
37  import org.apache.hadoop.fs.FileSystem;
38  import org.apache.hadoop.fs.FileStatus;
39  import org.apache.hadoop.fs.FSDataInputStream;
40  import org.apache.hadoop.fs.Path;
41  import org.apache.hadoop.fs.PathFilter;
42  import org.apache.hadoop.hbase.HBaseTestingUtility;
43  import org.apache.hadoop.hbase.HColumnDescriptor;
44  import org.apache.hadoop.hbase.HConstants;
45  import org.apache.hadoop.hbase.HRegionInfo;
46  import org.apache.hadoop.hbase.HTableDescriptor;
47  import org.apache.hadoop.hbase.TableName;
48  import org.apache.hadoop.hbase.TableNotEnabledException;
49  import org.apache.hadoop.hbase.classification.InterfaceAudience;
50  import org.apache.hadoop.hbase.client.Admin;
51  import org.apache.hadoop.hbase.client.BufferedMutator;
52  import org.apache.hadoop.hbase.client.Durability;
53  import org.apache.hadoop.hbase.client.HTable;
54  import org.apache.hadoop.hbase.client.Put;
55  import org.apache.hadoop.hbase.client.Table;
56  import org.apache.hadoop.hbase.errorhandling.ForeignExceptionDispatcher;
57  import org.apache.hadoop.hbase.client.RegionReplicaUtil;
58  import org.apache.hadoop.hbase.io.HFileLink;
59  import org.apache.hadoop.hbase.master.HMaster;
60  import org.apache.hadoop.hbase.master.MasterFileSystem;
61  import org.apache.hadoop.hbase.protobuf.ProtobufUtil;
62  import org.apache.hadoop.hbase.protobuf.generated.HBaseProtos.SnapshotDescription;
63  import org.apache.hadoop.hbase.protobuf.generated.SnapshotProtos.SnapshotRegionManifest;
64  import org.apache.hadoop.hbase.protobuf.generated.MasterProtos.IsSnapshotDoneRequest;
65  import org.apache.hadoop.hbase.protobuf.generated.MasterProtos.IsSnapshotDoneResponse;
66  import org.apache.hadoop.hbase.regionserver.HRegionFileSystem;
67  import org.apache.hadoop.hbase.regionserver.HRegionServer;
68  import org.apache.hadoop.hbase.regionserver.Region;
69  import org.apache.hadoop.hbase.util.Bytes;
70  import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
71  import org.apache.hadoop.hbase.util.FSTableDescriptors;
72  import org.apache.hadoop.hbase.util.FSVisitor;
73  import org.apache.hadoop.hbase.util.FSUtils;
74  import org.apache.hadoop.hbase.util.MD5Hash;
75  import org.junit.Assert;
76  
77  import com.google.protobuf.ServiceException;
78  
79  /**
80   * Utilities class for snapshots
81   */
82  @InterfaceAudience.Private
83  public final class SnapshotTestingUtils {
84  
85    private static final Log LOG = LogFactory.getLog(SnapshotTestingUtils.class);
86    private static byte[] KEYS = Bytes.toBytes("0123456789");
87  
88    private SnapshotTestingUtils() {
89      // private constructor for utility class
90    }
91  
92    /**
93     * Assert that we don't have any snapshots lists
94     *
95     * @throws IOException
96     *           if the admin operation fails
97     */
98    public static void assertNoSnapshots(Admin admin) throws IOException {
99      assertEquals("Have some previous snapshots", 0, admin.listSnapshots()
100         .size());
101   }
102 
103   /**
104    * Make sure that there is only one snapshot returned from the master and its
105    * name and table match the passed in parameters.
106    */
107   public static List<SnapshotDescription> assertExistsMatchingSnapshot(
108       Admin admin, String snapshotName, TableName tableName)
109       throws IOException {
110     // list the snapshot
111     List<SnapshotDescription> snapshots = admin.listSnapshots();
112 
113     List<SnapshotDescription> returnedSnapshots = new ArrayList<SnapshotDescription>();
114     for (SnapshotDescription sd : snapshots) {
115       if (snapshotName.equals(sd.getName()) &&
116           tableName.equals(TableName.valueOf(sd.getTable()))) {
117         returnedSnapshots.add(sd);
118       }
119     }
120 
121     Assert.assertTrue("No matching snapshots found.", returnedSnapshots.size()>0);
122     return returnedSnapshots;
123   }
124 
125   /**
126    * Make sure that there is only one snapshot returned from the master
127    */
128   public static void assertOneSnapshotThatMatches(Admin admin,
129       SnapshotDescription snapshot) throws IOException {
130     assertOneSnapshotThatMatches(admin, snapshot.getName(),
131         TableName.valueOf(snapshot.getTable()));
132   }
133 
134   /**
135    * Make sure that there is only one snapshot returned from the master and its
136    * name and table match the passed in parameters.
137    */
138   public static List<SnapshotDescription> assertOneSnapshotThatMatches(
139       Admin admin, String snapshotName, TableName tableName)
140       throws IOException {
141     // list the snapshot
142     List<SnapshotDescription> snapshots = admin.listSnapshots();
143 
144     assertEquals("Should only have 1 snapshot", 1, snapshots.size());
145     assertEquals(snapshotName, snapshots.get(0).getName());
146     assertEquals(tableName, TableName.valueOf(snapshots.get(0).getTable()));
147 
148     return snapshots;
149   }
150 
151   /**
152    * Make sure that there is only one snapshot returned from the master and its
153    * name and table match the passed in parameters.
154    */
155   public static List<SnapshotDescription> assertOneSnapshotThatMatches(
156       Admin admin, byte[] snapshot, TableName tableName) throws IOException {
157     return assertOneSnapshotThatMatches(admin, Bytes.toString(snapshot),
158         tableName);
159   }
160 
161   public static void confirmSnapshotValid(HBaseTestingUtility testUtil,
162       SnapshotDescription snapshotDescriptor, TableName tableName, byte[] family)
163       throws IOException {
164     MasterFileSystem mfs = testUtil.getHBaseCluster().getMaster().getMasterFileSystem();
165     confirmSnapshotValid(snapshotDescriptor, tableName, family,
166         mfs.getRootDir(), testUtil.getHBaseAdmin(), mfs.getFileSystem());
167   }
168 
169   /**
170    * Confirm that the snapshot contains references to all the files that should
171    * be in the snapshot.
172    */
173   public static void confirmSnapshotValid(
174       SnapshotDescription snapshotDescriptor, TableName tableName,
175       byte[] testFamily, Path rootDir, Admin admin, FileSystem fs)
176       throws IOException {
177     ArrayList nonEmptyTestFamilies = new ArrayList(1);
178     nonEmptyTestFamilies.add(testFamily);
179     confirmSnapshotValid(snapshotDescriptor, tableName,
180       nonEmptyTestFamilies, null, rootDir, admin, fs);
181   }
182 
183   /**
184    * Confirm that the snapshot has no references files but only metadata.
185    */
186   public static void confirmEmptySnapshotValid(
187       SnapshotDescription snapshotDescriptor, TableName tableName,
188       byte[] testFamily, Path rootDir, Admin admin, FileSystem fs)
189       throws IOException {
190     ArrayList emptyTestFamilies = new ArrayList(1);
191     emptyTestFamilies.add(testFamily);
192     confirmSnapshotValid(snapshotDescriptor, tableName,
193       null, emptyTestFamilies, rootDir, admin, fs);
194   }
195 
196   /**
197    * Confirm that the snapshot contains references to all the files that should
198    * be in the snapshot. This method also perform some redundant check like
199    * the existence of the snapshotinfo or the regioninfo which are done always
200    * by the MasterSnapshotVerifier, at the end of the snapshot operation.
201    */
202   public static void confirmSnapshotValid(
203       SnapshotDescription snapshotDescriptor, TableName tableName,
204       List<byte[]> nonEmptyTestFamilies, List<byte[]> emptyTestFamilies,
205       Path rootDir, Admin admin, FileSystem fs) throws IOException {
206     final Configuration conf = admin.getConfiguration();
207 
208     // check snapshot dir
209     Path snapshotDir = SnapshotDescriptionUtils.getCompletedSnapshotDir(
210         snapshotDescriptor, rootDir);
211     assertTrue("target snapshot directory, '"+ snapshotDir +"', doesn't exist.", fs.exists(snapshotDir));
212 
213     SnapshotDescription desc = SnapshotDescriptionUtils.readSnapshotInfo(fs, snapshotDir);
214 
215     // Extract regions and families with store files
216     final Set<byte[]> snapshotFamilies = new TreeSet<byte[]>(Bytes.BYTES_COMPARATOR);
217 
218     SnapshotManifest manifest = SnapshotManifest.open(conf, fs, snapshotDir, desc);
219     Map<String, SnapshotRegionManifest> regionManifests = manifest.getRegionManifestsMap();
220     for (SnapshotRegionManifest regionManifest: regionManifests.values()) {
221       SnapshotReferenceUtil.visitRegionStoreFiles(regionManifest,
222           new SnapshotReferenceUtil.StoreFileVisitor() {
223         @Override
224         public void storeFile(final HRegionInfo regionInfo, final String family,
225               final SnapshotRegionManifest.StoreFile storeFile) throws IOException {
226           snapshotFamilies.add(Bytes.toBytes(family));
227         }
228       });
229     }
230 
231     // Verify that there are store files in the specified families
232     if (nonEmptyTestFamilies != null) {
233       for (final byte[] familyName: nonEmptyTestFamilies) {
234         assertTrue("Expected snapshot to contain family '" + Bytes.toString(familyName) + "', but it does not.", snapshotFamilies.contains(familyName));
235       }
236     }
237 
238     // Verify that there are no store files in the specified families
239     if (emptyTestFamilies != null) {
240       for (final byte[] familyName: emptyTestFamilies) {
241         assertFalse("Expected snapshot to skip empty family '" + Bytes.toString(familyName) + "', but it is present.", snapshotFamilies.contains(familyName));
242       }
243     }
244 
245     // check the region snapshot for all the regions
246     List<HRegionInfo> regions = admin.getTableRegions(tableName);
247     // remove the non-default regions
248     RegionReplicaUtil.removeNonDefaultRegions(regions);
249 
250     // if create snapshot when table splitting, parent region will be included to the snapshot
251     // region manifest. we should exclude the parent regions.
252     int regionCountExclusiveSplitParent = 0;
253     for (SnapshotRegionManifest snapshotRegionManifest : regionManifests.values()) {
254       HRegionInfo hri = HRegionInfo.convert(snapshotRegionManifest.getRegionInfo());
255       if (hri.isOffline() && (hri.isSplit() || hri.isSplitParent())) {
256         continue;
257       }
258       regionCountExclusiveSplitParent++;
259     }
260     assertEquals("Wrong number of regions.", regions.size(), regionCountExclusiveSplitParent);
261 
262     // Verify Regions (redundant check, see MasterSnapshotVerifier)
263     for (HRegionInfo info : regions) {
264       String regionName = info.getEncodedName();
265       assertTrue("Missing region name: '" + regionName + "'", regionManifests.containsKey(regionName));
266     }
267   }
268 
269   /**
270    * Helper method for testing async snapshot operations. Just waits for the
271    * given snapshot to complete on the server by repeatedly checking the master.
272    *
273    * @param master: the master running the snapshot
274    * @param snapshot: the snapshot to check
275    * @param sleep: amount to sleep between checks to see if the snapshot is done
276    * @throws ServiceException if the snapshot fails
277    */
278   public static void waitForSnapshotToComplete(HMaster master, SnapshotDescription snapshot,
279       long sleep) throws ServiceException {
280     final IsSnapshotDoneRequest request = IsSnapshotDoneRequest.newBuilder()
281         .setSnapshot(snapshot).build();
282     IsSnapshotDoneResponse done = IsSnapshotDoneResponse.newBuilder()
283         .buildPartial();
284     while (!done.getDone()) {
285       done = master.getMasterRpcServices().isSnapshotDone(null, request);
286       try {
287         Thread.sleep(sleep);
288       } catch (InterruptedException e) {
289         throw new ServiceException(e);
290       }
291     }
292   }
293 
294   /*
295    * Take snapshot with maximum of numTries attempts, ignoring CorruptedSnapshotException
296    * except for the last CorruptedSnapshotException
297    */
298   public static void snapshot(Admin admin,
299       final String snapshotName, final String tableName,
300       SnapshotDescription.Type type, int numTries) throws IOException {
301     int tries = 0;
302     CorruptedSnapshotException lastEx = null;
303     while (tries++ < numTries) {
304       try {
305         admin.snapshot(snapshotName, TableName.valueOf(tableName), type);
306         return;
307       } catch (CorruptedSnapshotException cse) {
308         LOG.warn("Got CorruptedSnapshotException", cse);
309         lastEx = cse;
310       }
311     }
312     throw lastEx;
313   }
314 
315   public static void cleanupSnapshot(Admin admin, byte[] tableName)
316       throws IOException {
317     SnapshotTestingUtils.cleanupSnapshot(admin, Bytes.toString(tableName));
318   }
319 
320   public static void cleanupSnapshot(Admin admin, String snapshotName)
321       throws IOException {
322     // delete the taken snapshot
323     admin.deleteSnapshot(snapshotName);
324     assertNoSnapshots(admin);
325   }
326 
327   /**
328    * Expect the snapshot to throw an error when checking if the snapshot is
329    * complete
330    *
331    * @param master master to check
332    * @param snapshot the {@link SnapshotDescription} request to pass to the master
333    * @param clazz expected exception from the master
334    */
335   public static void expectSnapshotDoneException(HMaster master,
336       IsSnapshotDoneRequest snapshot,
337       Class<? extends HBaseSnapshotException> clazz) {
338     try {
339       master.getMasterRpcServices().isSnapshotDone(null, snapshot);
340       Assert.fail("didn't fail to lookup a snapshot");
341     } catch (ServiceException se) {
342       try {
343         throw ProtobufUtil.getRemoteException(se);
344       } catch (HBaseSnapshotException e) {
345         assertEquals("Threw wrong snapshot exception!", clazz, e.getClass());
346       } catch (Throwable t) {
347         Assert.fail("Threw an unexpected exception:" + t);
348       }
349     }
350   }
351 
352   /**
353    * List all the HFiles in the given table
354    *
355    * @param fs: FileSystem where the table lives
356    * @param tableDir directory of the table
357    * @return array of the current HFiles in the table (could be a zero-length array)
358    * @throws IOException on unexecpted error reading the FS
359    */
360   public static ArrayList<String> listHFileNames(final FileSystem fs, final Path tableDir)
361       throws IOException {
362     final ArrayList<String> hfiles = new ArrayList<String>();
363     FSVisitor.visitTableStoreFiles(fs, tableDir, new FSVisitor.StoreFileVisitor() {
364       @Override
365       public void storeFile(final String region, final String family, final String hfileName)
366           throws IOException {
367         hfiles.add(hfileName);
368       }
369     });
370     Collections.sort(hfiles);
371     return hfiles;
372   }
373 
374   /**
375    * Take a snapshot of the specified table and verify that the given family is
376    * not empty. Note that this will leave the table disabled
377    * in the case of an offline snapshot.
378    */
379   public static void createSnapshotAndValidate(Admin admin,
380       TableName tableName, String familyName, String snapshotNameString,
381       Path rootDir, FileSystem fs, boolean onlineSnapshot)
382       throws Exception {
383     ArrayList<byte[]> nonEmptyFamilyNames = new ArrayList<byte[]>(1);
384     nonEmptyFamilyNames.add(Bytes.toBytes(familyName));
385     createSnapshotAndValidate(admin, tableName, nonEmptyFamilyNames, /* emptyFamilyNames= */ null,
386                               snapshotNameString, rootDir, fs, onlineSnapshot);
387   }
388 
389   /**
390    * Take a snapshot of the specified table and verify the given families.
391    * Note that this will leave the table disabled in the case of an offline snapshot.
392    */
393   public static void createSnapshotAndValidate(Admin admin,
394       TableName tableName, List<byte[]> nonEmptyFamilyNames, List<byte[]> emptyFamilyNames,
395       String snapshotNameString, Path rootDir, FileSystem fs, boolean onlineSnapshot)
396         throws Exception {
397     if (!onlineSnapshot) {
398       try {
399         LOG.info("prepping for offline snapshot.");
400         admin.disableTable(tableName);
401       } catch (TableNotEnabledException tne) {
402         LOG.info("In attempting to disable " + tableName + " it turns out that the this table is " +
403             "already disabled.");
404       }
405     }
406     LOG.info("taking snapshot.");
407     admin.snapshot(snapshotNameString, tableName);
408 
409     LOG.info("Confirming snapshot exists.");
410     List<SnapshotDescription> snapshots = SnapshotTestingUtils.assertExistsMatchingSnapshot(admin,
411       snapshotNameString, tableName);
412     if (snapshots == null || snapshots.size() != 1) {
413       Assert.fail("Incorrect number of snapshots for table " + tableName);
414     }
415 
416     LOG.info("validating snapshot.");
417     SnapshotTestingUtils.confirmSnapshotValid(snapshots.get(0), tableName, nonEmptyFamilyNames,
418       emptyFamilyNames, rootDir, admin, fs);
419   }
420 
421   /**
422    * Corrupt the specified snapshot by deleting some files.
423    *
424    * @param util {@link HBaseTestingUtility}
425    * @param snapshotName name of the snapshot to corrupt
426    * @return array of the corrupted HFiles
427    * @throws IOException on unexecpted error reading the FS
428    */
429   public static ArrayList corruptSnapshot(final HBaseTestingUtility util, final String snapshotName)
430       throws IOException {
431     final MasterFileSystem mfs = util.getHBaseCluster().getMaster().getMasterFileSystem();
432     final FileSystem fs = mfs.getFileSystem();
433 
434     Path snapshotDir = SnapshotDescriptionUtils.getCompletedSnapshotDir(snapshotName,
435                                                                         mfs.getRootDir());
436     SnapshotDescription snapshotDesc = SnapshotDescriptionUtils.readSnapshotInfo(fs, snapshotDir);
437     final TableName table = TableName.valueOf(snapshotDesc.getTable());
438 
439     final ArrayList corruptedFiles = new ArrayList();
440     final Configuration conf = util.getConfiguration();
441     SnapshotReferenceUtil.visitTableStoreFiles(conf, fs, snapshotDir, snapshotDesc,
442         new SnapshotReferenceUtil.StoreFileVisitor() {
443       @Override
444       public void storeFile(final HRegionInfo regionInfo, final String family,
445             final SnapshotRegionManifest.StoreFile storeFile) throws IOException {
446         String region = regionInfo.getEncodedName();
447         String hfile = storeFile.getName();
448         HFileLink link = HFileLink.build(conf, table, region, family, hfile);
449         if (corruptedFiles.size() % 2 == 0) {
450           fs.delete(link.getAvailablePath(fs), true);
451           corruptedFiles.add(hfile);
452         }
453       }
454     });
455 
456     assertTrue(corruptedFiles.size() > 0);
457     return corruptedFiles;
458   }
459 
460   // ==========================================================================
461   //  Snapshot Mock
462   // ==========================================================================
463   public static class SnapshotMock {
464     private final static String TEST_FAMILY = "cf";
465     public final static int TEST_NUM_REGIONS = 4;
466 
467     private final Configuration conf;
468     private final FileSystem fs;
469     private final Path rootDir;
470 
471     static class RegionData {
472       public HRegionInfo hri;
473       public Path tableDir;
474       public Path[] files;
475 
476       public RegionData(final Path tableDir, final HRegionInfo hri, final int nfiles) {
477         this.tableDir = tableDir;
478         this.hri = hri;
479         this.files = new Path[nfiles];
480       }
481     }
482 
483     public static class SnapshotBuilder {
484       private final RegionData[] tableRegions;
485       private final SnapshotDescription desc;
486       private final HTableDescriptor htd;
487       private final Configuration conf;
488       private final FileSystem fs;
489       private final Path rootDir;
490       private Path snapshotDir;
491       private int snapshotted = 0;
492 
493       public SnapshotBuilder(final Configuration conf, final FileSystem fs,
494           final Path rootDir, final HTableDescriptor htd,
495           final SnapshotDescription desc, final RegionData[] tableRegions)
496           throws IOException {
497         this.fs = fs;
498         this.conf = conf;
499         this.rootDir = rootDir;
500         this.htd = htd;
501         this.desc = desc;
502         this.tableRegions = tableRegions;
503         this.snapshotDir = SnapshotDescriptionUtils.getWorkingSnapshotDir(desc, rootDir, conf);
504         new FSTableDescriptors(conf, snapshotDir.getFileSystem(conf), rootDir)
505           .createTableDescriptorForTableDirectory(snapshotDir, htd, false);
506       }
507 
508       public HTableDescriptor getTableDescriptor() {
509         return this.htd;
510       }
511 
512       public SnapshotDescription getSnapshotDescription() {
513         return this.desc;
514       }
515 
516       public Path getSnapshotsDir() {
517         return this.snapshotDir;
518       }
519 
520       public Path[] addRegion() throws IOException {
521         return addRegion(desc);
522       }
523 
524       public Path[] addRegionV1() throws IOException {
525         return addRegion(desc.toBuilder()
526                           .setVersion(SnapshotManifestV1.DESCRIPTOR_VERSION)
527                           .build());
528       }
529 
530       public Path[] addRegionV2() throws IOException {
531         return addRegion(desc.toBuilder()
532                           .setVersion(SnapshotManifestV2.DESCRIPTOR_VERSION)
533                           .build());
534       }
535 
536       private Path[] addRegion(final SnapshotDescription desc) throws IOException {
537         if (this.snapshotted == tableRegions.length) {
538           throw new UnsupportedOperationException("No more regions in the table");
539         }
540 
541         RegionData regionData = tableRegions[this.snapshotted++];
542         ForeignExceptionDispatcher monitor = new ForeignExceptionDispatcher(desc.getName());
543         SnapshotManifest manifest = SnapshotManifest.create(conf, fs, snapshotDir, desc, monitor);
544         manifest.addRegion(regionData.tableDir, regionData.hri);
545         return regionData.files;
546       }
547 
548       private void corruptFile(Path p) throws IOException {
549         String manifestName = p.getName();
550 
551         // Rename the original region-manifest file
552         Path newP = new Path(p.getParent(), manifestName + "1");
553         fs.rename(p, newP);
554 
555         // Create a new region-manifest file
556         FSDataOutputStream out = fs.create(p);
557 
558         //Copy the first 25 bytes of the original region-manifest into the new one,
559         //make it a corrupted region-manifest file.
560         FSDataInputStream input = fs.open(newP);
561         byte[] buffer = new byte[25];
562         int len = input.read(0, buffer, 0, 25);
563         if (len > 1) {
564           out.write(buffer, 0, len - 1);
565         }
566         out.close();
567 
568         // Delete the original region-manifest
569         fs.delete(newP);
570       }
571 
572       /**
573        * Corrupt one region-manifest file
574        *
575        * @throws IOException on unexecpted error from the FS
576        */
577       public void corruptOneRegionManifest() throws IOException {
578         FileStatus[] manifestFiles = FSUtils.listStatus(fs, snapshotDir, new PathFilter() {
579           @Override public boolean accept(Path path) {
580             return path.getName().startsWith(SnapshotManifestV2.SNAPSHOT_MANIFEST_PREFIX);
581           }
582         });
583 
584         if (manifestFiles.length == 0) return;
585 
586         // Just choose the first one
587         Path p = manifestFiles[0].getPath();
588         corruptFile(p);
589       }
590 
591       public void missOneRegionSnapshotFile() throws IOException {
592         FileStatus[] manifestFiles = FSUtils.listStatus(fs, snapshotDir);
593         for (FileStatus fileStatus : manifestFiles) {
594           String fileName = fileStatus.getPath().getName();
595           if (fileName.endsWith(SnapshotDescriptionUtils.SNAPSHOTINFO_FILE)
596             || fileName.endsWith(".tabledesc")
597             || fileName.endsWith(SnapshotDescriptionUtils.SNAPSHOT_TMP_DIR_NAME)) {
598               fs.delete(fileStatus.getPath(), true);
599           }
600         }
601       }
602 
603       /**
604        * Corrupt data-manifest file
605        *
606        * @throws IOException on unexecpted error from the FS
607        */
608       public void corruptDataManifest() throws IOException {
609         FileStatus[] manifestFiles = FSUtils.listStatus(fs, snapshotDir, new PathFilter() {
610           @Override
611           public boolean accept(Path path) {
612             return path.getName().startsWith(SnapshotManifest.DATA_MANIFEST_NAME);
613           }
614         });
615 
616         if (manifestFiles.length == 0) return;
617 
618         // Just choose the first one
619         Path p = manifestFiles[0].getPath();
620         corruptFile(p);
621       }
622 
623       public Path commit() throws IOException {
624         ForeignExceptionDispatcher monitor = new ForeignExceptionDispatcher(desc.getName());
625         SnapshotManifest manifest = SnapshotManifest.create(conf, fs, snapshotDir, desc, monitor);
626         manifest.addTableDescriptor(htd);
627         manifest.consolidate();
628         Path workingDir = snapshotDir;
629         Path completedSnapshotDir = SnapshotDescriptionUtils.getCompletedSnapshotDir(desc, rootDir);
630         SnapshotDescriptionUtils.completeSnapshot(completedSnapshotDir, workingDir, fs,
631             workingDir.getFileSystem(conf), conf);
632         snapshotDir = completedSnapshotDir;
633         return snapshotDir;
634       }
635 
636       public void consolidate() throws IOException {
637         ForeignExceptionDispatcher monitor = new ForeignExceptionDispatcher(desc.getName());
638         SnapshotManifest manifest = SnapshotManifest.create(conf, fs, snapshotDir, desc, monitor);
639         manifest.addTableDescriptor(htd);
640         manifest.consolidate();
641       }
642     }
643 
644     public SnapshotMock(final Configuration conf, final FileSystem fs, final Path rootDir) {
645       this.fs = fs;
646       this.conf = conf;
647       this.rootDir = rootDir;
648     }
649 
650     public SnapshotBuilder createSnapshotV1(final String snapshotName, final String tableName)
651         throws IOException {
652       return createSnapshot(snapshotName, tableName, SnapshotManifestV1.DESCRIPTOR_VERSION);
653     }
654 
655     public SnapshotBuilder createSnapshotV1(final String snapshotName, final String tableName,
656         final int numRegions) throws IOException {
657       return createSnapshot(snapshotName, tableName, numRegions, SnapshotManifestV1.DESCRIPTOR_VERSION);
658     }
659 
660     public SnapshotBuilder createSnapshotV2(final String snapshotName, final String tableName)
661         throws IOException {
662       return createSnapshot(snapshotName, tableName, SnapshotManifestV2.DESCRIPTOR_VERSION);
663     }
664 
665     public SnapshotBuilder createSnapshotV2(final String snapshotName, final String tableName,
666         final int numRegions) throws IOException {
667       return createSnapshot(snapshotName, tableName, numRegions, SnapshotManifestV2.DESCRIPTOR_VERSION);
668     }
669 
670     public SnapshotBuilder createSnapshotV2(final String snapshotName, final String tableName,
671         final int numRegions, final long ttl) throws IOException {
672       return createSnapshot(snapshotName, tableName, numRegions,
673           SnapshotManifestV2.DESCRIPTOR_VERSION, ttl);
674     }
675 
676     private SnapshotBuilder createSnapshot(final String snapshotName, final String tableName,
677         final int version) throws IOException {
678       return createSnapshot(snapshotName, tableName, TEST_NUM_REGIONS, version);
679     }
680 
681     private SnapshotBuilder createSnapshot(final String snapshotName, final String tableName,
682         final int numRegions, final int version) throws IOException {
683       HTableDescriptor htd = createHtd(tableName);
684       RegionData[] regions = createTable(htd, numRegions);
685 
686       SnapshotDescription desc = SnapshotDescription.newBuilder()
687         .setTable(htd.getNameAsString())
688         .setName(snapshotName)
689         .setVersion(version)
690         .build();
691 
692       Path workingDir = SnapshotDescriptionUtils.getWorkingSnapshotDir(desc, rootDir, conf);
693       SnapshotDescriptionUtils.writeSnapshotInfo(desc, workingDir, workingDir.getFileSystem(conf));
694       return new SnapshotBuilder(conf, fs, rootDir, htd, desc, regions);
695     }
696 
697     private SnapshotBuilder createSnapshot(final String snapshotName, final String tableName,
698       final int numRegions, final int version, final long ttl) throws IOException {
699       HTableDescriptor htd = createHtd(tableName);
700       RegionData[] regions = createTable(htd, numRegions);
701       SnapshotDescription desc = SnapshotDescription.newBuilder()
702         .setTable(htd.getTableName().getNameAsString())
703         .setName(snapshotName)
704         .setVersion(version)
705         .setCreationTime(EnvironmentEdgeManager.currentTime())
706         .setTtl(ttl)
707         .build();
708       Path workingDir = SnapshotDescriptionUtils.getWorkingSnapshotDir(desc, rootDir, conf);
709       SnapshotDescriptionUtils.writeSnapshotInfo(desc, workingDir, fs);
710       return new SnapshotBuilder(conf, fs, rootDir, htd, desc, regions);
711     }
712 
713     public HTableDescriptor createHtd(final String tableName) {
714       HTableDescriptor htd = new HTableDescriptor(tableName);
715       htd.addFamily(new HColumnDescriptor(TEST_FAMILY));
716       return htd;
717     }
718 
719     private RegionData[] createTable(final HTableDescriptor htd, final int nregions)
720         throws IOException {
721       Path tableDir = FSUtils.getTableDir(rootDir, htd.getTableName());
722       new FSTableDescriptors(conf).createTableDescriptorForTableDirectory(tableDir, htd, false);
723 
724       assertTrue(nregions % 2 == 0);
725       RegionData[] regions = new RegionData[nregions];
726       for (int i = 0; i < regions.length; i += 2) {
727         byte[] startKey = Bytes.toBytes(0 + i * 2);
728         byte[] endKey = Bytes.toBytes(1 + i * 2);
729 
730         // First region, simple with one plain hfile.
731         HRegionInfo hri = new HRegionInfo(htd.getTableName(), startKey, endKey);
732         HRegionFileSystem rfs = HRegionFileSystem.createRegionOnFileSystem(conf, fs, tableDir, hri);
733         regions[i] = new RegionData(tableDir, hri, 3);
734         for (int j = 0; j < regions[i].files.length; ++j) {
735           Path storeFile = createStoreFile(rfs.createTempName());
736           regions[i].files[j] = rfs.commitStoreFile(TEST_FAMILY, storeFile);
737         }
738 
739         // Second region, used to test the split case.
740         // This region contains a reference to the hfile in the first region.
741         startKey = Bytes.toBytes(2 + i * 2);
742         endKey = Bytes.toBytes(3 + i * 2);
743         hri = new HRegionInfo(htd.getTableName());
744         rfs = HRegionFileSystem.createRegionOnFileSystem(conf, fs, tableDir, hri);
745         regions[i+1] = new RegionData(tableDir, hri, regions[i].files.length);
746         for (int j = 0; j < regions[i].files.length; ++j) {
747           String refName = regions[i].files[j].getName() + '.' + regions[i].hri.getEncodedName();
748           Path refFile = createStoreFile(new Path(rootDir, refName));
749           regions[i+1].files[j] = rfs.commitStoreFile(TEST_FAMILY, refFile);
750         }
751       }
752       return regions;
753     }
754 
755     private Path createStoreFile(final Path storeFile)
756         throws IOException {
757       FSDataOutputStream out = fs.create(storeFile);
758       try {
759         out.write(Bytes.toBytes(storeFile.toString()));
760       } finally {
761         out.close();
762       }
763       return storeFile;
764     }
765   }
766 
767   // ==========================================================================
768   //  Table Helpers
769   // ==========================================================================
770   public static void waitForTableToBeOnline(final HBaseTestingUtility util,
771                                             final TableName tableName)
772       throws IOException, InterruptedException {
773     HRegionServer rs = util.getRSForFirstRegionInTable(tableName);
774     List<Region> onlineRegions = rs.getOnlineRegions(tableName);
775     for (Region region : onlineRegions) {
776       region.waitForFlushesAndCompactions();
777     }
778     // Wait up to 60 seconds for a table to be available.
779     util.waitFor(60000, util.predicateTableAvailable(tableName));
780   }
781 
782   public static void createTable(final HBaseTestingUtility util, final TableName tableName,
783       int regionReplication, final byte[]... families) throws IOException, InterruptedException {
784     HTableDescriptor htd = new HTableDescriptor(tableName);
785     htd.setRegionReplication(regionReplication);
786     for (byte[] family : families) {
787       HColumnDescriptor hcd = new HColumnDescriptor(family);
788       htd.addFamily(hcd);
789     }
790     byte[][] splitKeys = getSplitKeys();
791     util.createTable(htd, splitKeys);
792     assertEquals((splitKeys.length + 1) * regionReplication,
793         util.getHBaseAdmin().getTableRegions(tableName).size());
794   }
795 
796   public static byte[][] getSplitKeys() {
797     byte[][] splitKeys = new byte[KEYS.length-2][];
798     for (int i = 0; i < splitKeys.length; ++i) {
799       splitKeys[i] = new byte[] { KEYS[i+1] };
800     }
801     return splitKeys;
802   }
803 
804   public static void createTable(final HBaseTestingUtility util, final TableName tableName,
805       final byte[]... families) throws IOException, InterruptedException {
806     createTable(util, tableName, 1, families);
807   }
808 
809   public static void loadData(final HBaseTestingUtility util, final TableName tableName, int rows,
810       byte[]... families) throws IOException, InterruptedException {
811     try (BufferedMutator mutator = util.getConnection().getBufferedMutator(tableName)) {
812       loadData(util, mutator, rows, families);
813     }
814   }
815 
816   public static void loadData(final HBaseTestingUtility util, final BufferedMutator mutator, int rows,
817       byte[]... families) throws IOException, InterruptedException {
818     // Ensure one row per region
819     assertTrue(rows >= KEYS.length);
820     for (byte k0: KEYS) {
821       byte[] k = new byte[] { k0 };
822       byte[] value = Bytes.add(Bytes.toBytes(System.currentTimeMillis()), k);
823       byte[] key = Bytes.add(k, Bytes.toBytes(MD5Hash.getMD5AsHex(value)));
824       final byte[][] families1 = families;
825       final byte[] key1 = key;
826       final byte[] value1 = value;
827       mutator.mutate(createPut(families1, key1, value1));
828       rows--;
829     }
830 
831     // Add other extra rows. more rows, more files
832     while (rows-- > 0) {
833       byte[] value = Bytes.add(Bytes.toBytes(System.currentTimeMillis()), Bytes.toBytes(rows));
834       byte[] key = Bytes.toBytes(MD5Hash.getMD5AsHex(value));
835       final byte[][] families1 = families;
836       final byte[] key1 = key;
837       final byte[] value1 = value;
838       mutator.mutate(createPut(families1, key1, value1));
839     }
840     mutator.flush();
841 
842     waitForTableToBeOnline(util, mutator.getName());
843   }
844 
845   private static Put createPut(final byte[][] families, final byte[] key, final byte[] value) {
846     byte[] q = Bytes.toBytes("q");
847     Put put = new Put(key);
848     put.setDurability(Durability.SKIP_WAL);
849     for (byte[] family: families) {
850       put.add(family, q, value);
851     }
852     return put;
853   }
854 
855   public static void deleteAllSnapshots(final Admin admin)
856       throws IOException {
857     // Delete all the snapshots
858     for (SnapshotDescription snapshot: admin.listSnapshots()) {
859       admin.deleteSnapshot(snapshot.getName());
860     }
861     SnapshotTestingUtils.assertNoSnapshots(admin);
862   }
863 
864   public static void deleteArchiveDirectory(final HBaseTestingUtility util)
865       throws IOException {
866     // Ensure the archiver to be empty
867     MasterFileSystem mfs = util.getMiniHBaseCluster().getMaster().getMasterFileSystem();
868     Path archiveDir = new Path(mfs.getRootDir(), HConstants.HFILE_ARCHIVE_DIRECTORY);
869     mfs.getFileSystem().delete(archiveDir, true);
870   }
871 
872   public static void verifyRowCount(final HBaseTestingUtility util, final TableName tableName,
873       long expectedRows) throws IOException {
874     Table table = new HTable(util.getConfiguration(), tableName);
875     try {
876       assertEquals(expectedRows, util.countRows(table));
877     } finally {
878       table.close();
879     }
880   }
881 
882   public static void verifyReplicasCameOnline(TableName tableName, Admin admin,
883       int regionReplication) throws IOException {
884     List<HRegionInfo> regions = admin.getTableRegions(tableName);
885     HashSet<HRegionInfo> set = new HashSet<HRegionInfo>();
886     for (HRegionInfo hri : regions) {
887       set.add(RegionReplicaUtil.getRegionInfoForDefaultReplica(hri));
888       for (int i = 0; i < regionReplication; i++) {
889         HRegionInfo replica = RegionReplicaUtil.getRegionInfoForReplica(hri, i);
890         if (!regions.contains(replica)) {
891           Assert.fail(replica + " is not contained in the list of online regions");
892         }
893       }
894     }
895     assert(set.size() == getSplitKeys().length + 1);
896   }
897 }