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