001/**
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase.snapshot;
019
020import static org.junit.Assert.assertEquals;
021import static org.junit.Assert.assertFalse;
022import static org.junit.Assert.assertTrue;
023
024import com.google.protobuf.ServiceException;
025import java.io.IOException;
026import java.util.ArrayList;
027import java.util.Collections;
028import java.util.HashSet;
029import java.util.List;
030import java.util.Map;
031import java.util.Set;
032import java.util.TreeSet;
033import org.apache.hadoop.conf.Configuration;
034import org.apache.hadoop.fs.FSDataInputStream;
035import org.apache.hadoop.fs.FSDataOutputStream;
036import org.apache.hadoop.fs.FileStatus;
037import org.apache.hadoop.fs.FileSystem;
038import org.apache.hadoop.fs.Path;
039import org.apache.hadoop.fs.PathFilter;
040import org.apache.hadoop.hbase.HBaseTestingUtility;
041import org.apache.hadoop.hbase.HConstants;
042import org.apache.hadoop.hbase.TableName;
043import org.apache.hadoop.hbase.TableNotEnabledException;
044import org.apache.hadoop.hbase.client.Admin;
045import org.apache.hadoop.hbase.client.BufferedMutator;
046import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder;
047import org.apache.hadoop.hbase.client.Durability;
048import org.apache.hadoop.hbase.client.Put;
049import org.apache.hadoop.hbase.client.RegionInfo;
050import org.apache.hadoop.hbase.client.RegionInfoBuilder;
051import org.apache.hadoop.hbase.client.RegionReplicaUtil;
052import org.apache.hadoop.hbase.client.SnapshotDescription;
053import org.apache.hadoop.hbase.client.SnapshotType;
054import org.apache.hadoop.hbase.client.Table;
055import org.apache.hadoop.hbase.client.TableDescriptor;
056import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
057import org.apache.hadoop.hbase.errorhandling.ForeignExceptionDispatcher;
058import org.apache.hadoop.hbase.io.HFileLink;
059import org.apache.hadoop.hbase.master.HMaster;
060import org.apache.hadoop.hbase.master.MasterFileSystem;
061import org.apache.hadoop.hbase.mob.MobUtils;
062import org.apache.hadoop.hbase.regionserver.HRegion;
063import org.apache.hadoop.hbase.regionserver.HRegionFileSystem;
064import org.apache.hadoop.hbase.regionserver.HRegionServer;
065import org.apache.hadoop.hbase.util.Bytes;
066import org.apache.hadoop.hbase.util.FSTableDescriptors;
067import org.apache.hadoop.hbase.util.FSUtils;
068import org.apache.hadoop.hbase.util.FSVisitor;
069import org.apache.hadoop.hbase.util.MD5Hash;
070import org.apache.yetus.audience.InterfaceAudience;
071import org.junit.Assert;
072import org.slf4j.Logger;
073import org.slf4j.LoggerFactory;
074
075import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil;
076import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.IsSnapshotDoneRequest;
077import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.IsSnapshotDoneResponse;
078import org.apache.hadoop.hbase.shaded.protobuf.generated.SnapshotProtos;
079import org.apache.hadoop.hbase.shaded.protobuf.generated.SnapshotProtos.SnapshotRegionManifest;
080
081/**
082 * Utilities class for snapshots
083 */
084@InterfaceAudience.Private
085public final class SnapshotTestingUtils {
086  private static final Logger LOG = LoggerFactory.getLogger(SnapshotTestingUtils.class);
087
088  // default number of regions (and keys) given by getSplitKeys() and createTable()
089  private static byte[] KEYS = Bytes.toBytes("0123456");
090
091  private SnapshotTestingUtils() {
092    // private constructor for utility class
093  }
094
095  /**
096   * Assert that we don't have any snapshots lists
097   *
098   * @throws IOException
099   *           if the admin operation fails
100   */
101  public static void assertNoSnapshots(Admin admin) throws IOException {
102    assertEquals("Have some previous snapshots", 0, admin.listSnapshots()
103        .size());
104  }
105
106  /**
107   * Make sure that there is only one snapshot returned from the master and its
108   * name and table match the passed in parameters.
109   */
110  public static List<SnapshotDescription> assertExistsMatchingSnapshot(
111      Admin admin, String snapshotName, TableName tableName)
112      throws IOException {
113    // list the snapshot
114    List<SnapshotDescription> snapshots = admin.listSnapshots();
115
116    List<SnapshotDescription> returnedSnapshots = new ArrayList<>();
117    for (SnapshotDescription sd : snapshots) {
118      if (snapshotName.equals(sd.getName()) && tableName.equals(sd.getTableName())) {
119        returnedSnapshots.add(sd);
120      }
121    }
122
123    Assert.assertTrue("No matching snapshots found.", returnedSnapshots.size()>0);
124    return returnedSnapshots;
125  }
126
127  /**
128   * Make sure that there is only one snapshot returned from the master
129   */
130  public static void assertOneSnapshotThatMatches(Admin admin,
131      SnapshotProtos.SnapshotDescription snapshot) throws IOException {
132    assertOneSnapshotThatMatches(admin, snapshot.getName(), TableName.valueOf(snapshot.getTable()));
133  }
134
135  /**
136   * Make sure that there is only one snapshot returned from the master and its
137   * name and table match the passed in parameters.
138   */
139  public static List<SnapshotDescription> assertOneSnapshotThatMatches(
140      Admin admin, String snapshotName, TableName tableName)
141      throws IOException {
142    // list the snapshot
143    List<SnapshotDescription> snapshots = admin.listSnapshots();
144
145    assertEquals("Should only have 1 snapshot", 1, snapshots.size());
146    assertEquals(snapshotName, snapshots.get(0).getName());
147    assertEquals(tableName, snapshots.get(0).getTableName());
148
149    return snapshots;
150  }
151
152  /**
153   * Make sure that there is only one snapshot returned from the master and its
154   * name and table match the passed in parameters.
155   */
156  public static List<SnapshotDescription> assertOneSnapshotThatMatches(
157      Admin admin, byte[] snapshot, TableName tableName) throws IOException {
158    return assertOneSnapshotThatMatches(admin, Bytes.toString(snapshot),
159        tableName);
160  }
161
162  public static void confirmSnapshotValid(HBaseTestingUtility testUtil,
163      SnapshotProtos.SnapshotDescription snapshotDescriptor, TableName tableName, byte[] family)
164      throws IOException {
165    MasterFileSystem mfs = testUtil.getHBaseCluster().getMaster().getMasterFileSystem();
166    confirmSnapshotValid(snapshotDescriptor, tableName, family, mfs.getRootDir(),
167      testUtil.getAdmin(), mfs.getFileSystem());
168  }
169
170  /**
171   * Confirm that the snapshot contains references to all the files that should
172   * be in the snapshot.
173   */
174  public static void confirmSnapshotValid(SnapshotProtos.SnapshotDescription snapshotDescriptor,
175      TableName tableName, 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      SnapshotProtos.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      SnapshotProtos.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    SnapshotProtos.SnapshotDescription desc = SnapshotDescriptionUtils.readSnapshotInfo(fs, snapshotDir);
214
215    // Extract regions and families with store files
216    final Set<byte[]> snapshotFamilies = new TreeSet<>(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 RegionInfo regionInfo, final String family,
225              final SnapshotRegionManifest.StoreFile storeFile) throws IOException {
226          snapshotFamilies.add(Bytes.toBytes(family));
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("Expected snapshot to contain family '" + Bytes.toString(familyName) + "', but it does not.", 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("Expected snapshot to skip empty family '" + Bytes.toString(familyName) + "', but it is present.", snapshotFamilies.contains(familyName));
241      }
242    }
243
244    // check the region snapshot for all the regions
245    List<RegionInfo> regions = admin.getRegions(tableName);
246    // remove the non-default regions
247    RegionReplicaUtil.removeNonDefaultRegions(regions);
248    boolean hasMob = regionManifests.containsKey(MobUtils.getMobRegionInfo(tableName)
249        .getEncodedName());
250    if (hasMob) {
251      assertEquals("Wrong number of regions.", regions.size(), regionManifests.size() - 1);
252    } else {
253      // if create snapshot when table splitting, parent region will be included to the snapshot
254      // region manifest. we should exclude the parent regions.
255      int regionCountExclusiveSplitParent = 0;
256      for (SnapshotRegionManifest snapshotRegionManifest : regionManifests.values()) {
257        RegionInfo hri = ProtobufUtil.toRegionInfo(snapshotRegionManifest.getRegionInfo());
258        if (hri.isOffline() && (hri.isSplit() || hri.isSplitParent())) {
259          continue;
260        }
261        regionCountExclusiveSplitParent++;
262      }
263      assertEquals("Wrong number of regions.", regions.size(), regionCountExclusiveSplitParent);
264    }
265
266    // Verify Regions (redundant check, see MasterSnapshotVerifier)
267    for (RegionInfo info : regions) {
268      String regionName = info.getEncodedName();
269      assertTrue("Missing region name: '" + regionName + "'", regionManifests.containsKey(regionName));
270    }
271  }
272
273  /**
274   * Helper method for testing async snapshot operations. Just waits for the
275   * given snapshot to complete on the server by repeatedly checking the master.
276   *
277   * @param master the master running the snapshot
278   * @param snapshot the snapshot to check
279   * @param sleep amount to sleep between checks to see if the snapshot is done
280   * @throws ServiceException if the snapshot fails
281   * @throws org.apache.hbase.thirdparty.com.google.protobuf.ServiceException
282   */
283  public static void waitForSnapshotToComplete(HMaster master,
284      SnapshotProtos.SnapshotDescription snapshot, long sleep)
285          throws org.apache.hbase.thirdparty.com.google.protobuf.ServiceException {
286    final IsSnapshotDoneRequest request = IsSnapshotDoneRequest.newBuilder()
287        .setSnapshot(snapshot).build();
288    IsSnapshotDoneResponse done = IsSnapshotDoneResponse.newBuilder()
289        .buildPartial();
290    while (!done.getDone()) {
291      done = master.getMasterRpcServices().isSnapshotDone(null, request);
292      try {
293        Thread.sleep(sleep);
294      } catch (InterruptedException e) {
295        throw new org.apache.hbase.thirdparty.com.google.protobuf.ServiceException(e);
296      }
297    }
298  }
299
300  /*
301   * Take snapshot with maximum of numTries attempts, ignoring CorruptedSnapshotException
302   * except for the last CorruptedSnapshotException
303   */
304  public static void snapshot(Admin admin, final String snapshotName, final TableName tableName,
305      final SnapshotType type, final int numTries) throws IOException {
306    int tries = 0;
307    CorruptedSnapshotException lastEx = null;
308    while (tries++ < numTries) {
309      try {
310        admin.snapshot(snapshotName, tableName, type);
311        return;
312      } catch (CorruptedSnapshotException cse) {
313        LOG.warn("Got CorruptedSnapshotException", cse);
314        lastEx = cse;
315      }
316    }
317    throw lastEx;
318  }
319
320  public static void cleanupSnapshot(Admin admin, byte[] tableName)
321      throws IOException {
322    SnapshotTestingUtils.cleanupSnapshot(admin, Bytes.toString(tableName));
323  }
324
325  public static void cleanupSnapshot(Admin admin, String snapshotName)
326      throws IOException {
327    // delete the taken snapshot
328    admin.deleteSnapshot(snapshotName);
329    assertNoSnapshots(admin);
330  }
331
332  /**
333   * Expect the snapshot to throw an error when checking if the snapshot is
334   * complete
335   *
336   * @param master master to check
337   * @param snapshot the {@link SnapshotDescription} request to pass to the master
338   * @param clazz expected exception from the master
339   */
340  public static void expectSnapshotDoneException(HMaster master,
341      IsSnapshotDoneRequest snapshot,
342      Class<? extends HBaseSnapshotException> clazz) {
343    try {
344      master.getMasterRpcServices().isSnapshotDone(null, snapshot);
345      Assert.fail("didn't fail to lookup a snapshot");
346    } catch (org.apache.hbase.thirdparty.com.google.protobuf.ServiceException se) {
347      try {
348        throw ProtobufUtil.handleRemoteException(se);
349      } catch (HBaseSnapshotException e) {
350        assertEquals("Threw wrong snapshot exception!", clazz, e.getClass());
351      } catch (Throwable t) {
352        Assert.fail("Threw an unexpected exception:" + t);
353      }
354    }
355  }
356
357  /**
358   * List all the HFiles in the given table
359   *
360   * @param fs FileSystem where the table lives
361   * @param tableDir directory of the table
362   * @return array of the current HFiles in the table (could be a zero-length array)
363   * @throws IOException on unexecpted error reading the FS
364   */
365  public static ArrayList<String> listHFileNames(final FileSystem fs, final Path tableDir)
366      throws IOException {
367    final ArrayList<String> hfiles = new ArrayList<>();
368    FSVisitor.visitTableStoreFiles(fs, tableDir, new FSVisitor.StoreFileVisitor() {
369      @Override
370      public void storeFile(final String region, final String family, final String hfileName)
371          throws IOException {
372        hfiles.add(hfileName);
373      }
374    });
375    Collections.sort(hfiles);
376    return hfiles;
377  }
378
379  /**
380   * Take a snapshot of the specified table and verify that the given family is
381   * not empty. Note that this will leave the table disabled
382   * in the case of an offline snapshot.
383   */
384  public static void createSnapshotAndValidate(Admin admin,
385      TableName tableName, String familyName, String snapshotNameString,
386      Path rootDir, FileSystem fs, boolean onlineSnapshot)
387      throws Exception {
388    ArrayList<byte[]> nonEmptyFamilyNames = new ArrayList<>(1);
389    nonEmptyFamilyNames.add(Bytes.toBytes(familyName));
390    createSnapshotAndValidate(admin, tableName, nonEmptyFamilyNames, /* emptyFamilyNames= */ null,
391                              snapshotNameString, rootDir, fs, onlineSnapshot);
392  }
393
394  /**
395   * Take a snapshot of the specified table and verify the given families.
396   * Note that this will leave the table disabled in the case of an offline snapshot.
397   */
398  public static void createSnapshotAndValidate(Admin admin,
399      TableName tableName, List<byte[]> nonEmptyFamilyNames, List<byte[]> emptyFamilyNames,
400      String snapshotNameString, Path rootDir, FileSystem fs, boolean onlineSnapshot)
401        throws Exception {
402    if (!onlineSnapshot) {
403      try {
404        LOG.info("prepping for offline snapshot.");
405        admin.disableTable(tableName);
406      } catch (TableNotEnabledException tne) {
407        LOG.info("In attempting to disable " + tableName + " it turns out that the this table is " +
408            "already disabled.");
409      }
410    }
411    LOG.info("taking snapshot.");
412    admin.snapshot(snapshotNameString, tableName);
413
414    LOG.info("Confirming snapshot exists.");
415    List<SnapshotDescription> snapshots =
416        SnapshotTestingUtils.assertExistsMatchingSnapshot(admin, snapshotNameString, tableName);
417    if (snapshots == null || snapshots.size() != 1) {
418      Assert.fail("Incorrect number of snapshots for table " + tableName);
419    }
420
421    LOG.info("validating snapshot.");
422    SnapshotTestingUtils.confirmSnapshotValid(
423      ProtobufUtil.createHBaseProtosSnapshotDesc(snapshots.get(0)), tableName, nonEmptyFamilyNames,
424      emptyFamilyNames, rootDir, admin, fs);
425  }
426
427  /**
428   * Corrupt the specified snapshot by deleting some files.
429   *
430   * @param util {@link HBaseTestingUtility}
431   * @param snapshotName name of the snapshot to corrupt
432   * @return array of the corrupted HFiles
433   * @throws IOException on unexecpted error reading the FS
434   */
435  public static ArrayList corruptSnapshot(final HBaseTestingUtility util, final String snapshotName)
436      throws IOException {
437    final MasterFileSystem mfs = util.getHBaseCluster().getMaster().getMasterFileSystem();
438    final FileSystem fs = mfs.getFileSystem();
439
440    Path snapshotDir = SnapshotDescriptionUtils.getCompletedSnapshotDir(snapshotName,
441                                                                        mfs.getRootDir());
442    SnapshotProtos.SnapshotDescription snapshotDesc =
443        SnapshotDescriptionUtils.readSnapshotInfo(fs, snapshotDir);
444    final TableName table = TableName.valueOf(snapshotDesc.getTable());
445
446    final ArrayList corruptedFiles = new ArrayList();
447    final Configuration conf = util.getConfiguration();
448    SnapshotReferenceUtil.visitTableStoreFiles(conf, fs, snapshotDir, snapshotDesc,
449        new SnapshotReferenceUtil.StoreFileVisitor() {
450      @Override
451      public void storeFile(final RegionInfo regionInfo, final String family,
452            final SnapshotRegionManifest.StoreFile storeFile) throws IOException {
453        String region = regionInfo.getEncodedName();
454        String hfile = storeFile.getName();
455        HFileLink link = HFileLink.build(conf, table, region, family, hfile);
456        if (corruptedFiles.size() % 2 == 0) {
457          fs.delete(link.getAvailablePath(fs), true);
458          corruptedFiles.add(hfile);
459        }
460      }
461    });
462
463    assertTrue(corruptedFiles.size() > 0);
464    return corruptedFiles;
465  }
466
467  // ==========================================================================
468  //  Snapshot Mock
469  // ==========================================================================
470  public static class SnapshotMock {
471    protected final static String TEST_FAMILY = "cf";
472    public final static int TEST_NUM_REGIONS = 4;
473
474    private final Configuration conf;
475    private final FileSystem fs;
476    private final Path rootDir;
477
478    static class RegionData {
479      public RegionInfo hri;
480      public Path tableDir;
481      public Path[] files;
482
483      public RegionData(final Path tableDir, final RegionInfo hri, final int nfiles) {
484        this.tableDir = tableDir;
485        this.hri = hri;
486        this.files = new Path[nfiles];
487      }
488    }
489
490    public static class SnapshotBuilder {
491      private final RegionData[] tableRegions;
492      private final SnapshotProtos.SnapshotDescription desc;
493      private final TableDescriptor htd;
494      private final Configuration conf;
495      private final FileSystem fs;
496      private final Path rootDir;
497      private Path snapshotDir;
498      private int snapshotted = 0;
499
500      public SnapshotBuilder(final Configuration conf, final FileSystem fs,
501          final Path rootDir, final TableDescriptor htd,
502          final SnapshotProtos.SnapshotDescription desc, final RegionData[] tableRegions)
503          throws IOException {
504        this.fs = fs;
505        this.conf = conf;
506        this.rootDir = rootDir;
507        this.htd = htd;
508        this.desc = desc;
509        this.tableRegions = tableRegions;
510        this.snapshotDir = SnapshotDescriptionUtils.getWorkingSnapshotDir(desc, rootDir, conf);
511        new FSTableDescriptors(conf)
512          .createTableDescriptorForTableDirectory(snapshotDir, htd, false);
513      }
514
515      public TableDescriptor getTableDescriptor() {
516        return this.htd;
517      }
518
519      public SnapshotProtos.SnapshotDescription getSnapshotDescription() {
520        return this.desc;
521      }
522
523      public Path getSnapshotsDir() {
524        return this.snapshotDir;
525      }
526
527      public Path[] addRegion() throws IOException {
528        return addRegion(desc);
529      }
530
531      public Path[] addRegionV1() throws IOException {
532        return addRegion(desc.toBuilder()
533                          .setVersion(SnapshotManifestV1.DESCRIPTOR_VERSION)
534                          .build());
535      }
536
537      public Path[] addRegionV2() throws IOException {
538        return addRegion(desc.toBuilder()
539                          .setVersion(SnapshotManifestV2.DESCRIPTOR_VERSION)
540                          .build());
541      }
542
543      private Path[] addRegion(final SnapshotProtos.SnapshotDescription desc) throws IOException {
544        if (this.snapshotted == tableRegions.length) {
545          throw new UnsupportedOperationException("No more regions in the table");
546        }
547
548        RegionData regionData = tableRegions[this.snapshotted++];
549        ForeignExceptionDispatcher monitor = new ForeignExceptionDispatcher(desc.getName());
550        SnapshotManifest manifest = SnapshotManifest.create(conf, fs, snapshotDir, desc, monitor);
551        manifest.addRegion(regionData.tableDir, regionData.hri);
552        return regionData.files;
553      }
554
555      private void corruptFile(Path p) throws IOException {
556        String manifestName = p.getName();
557
558        // Rename the original region-manifest file
559        Path newP = new Path(p.getParent(), manifestName + "1");
560        fs.rename(p, newP);
561
562        // Create a new region-manifest file
563        FSDataOutputStream out = fs.create(p);
564
565        //Copy the first 25 bytes of the original region-manifest into the new one,
566        //make it a corrupted region-manifest file.
567        FSDataInputStream input = fs.open(newP);
568        byte[] buffer = new byte[25];
569        int len = input.read(0, buffer, 0, 25);
570        if (len > 1) {
571          out.write(buffer, 0, len - 1);
572        }
573        out.close();
574
575        // Delete the original region-manifest
576        fs.delete(newP);
577      }
578
579      /**
580       * Corrupt one region-manifest file
581       *
582       * @throws IOException on unexecpted error from the FS
583       */
584      public void corruptOneRegionManifest() throws IOException {
585        FileStatus[] manifestFiles = FSUtils.listStatus(fs, snapshotDir, new PathFilter() {
586          @Override public boolean accept(Path path) {
587            return path.getName().startsWith(SnapshotManifestV2.SNAPSHOT_MANIFEST_PREFIX);
588          }
589        });
590
591        if (manifestFiles.length == 0) return;
592
593        // Just choose the first one
594        Path p = manifestFiles[0].getPath();
595        corruptFile(p);
596      }
597
598      public void missOneRegionSnapshotFile() throws IOException {
599        FileStatus[] manifestFiles = FSUtils.listStatus(fs, snapshotDir);
600        for (FileStatus fileStatus : manifestFiles) {
601          String fileName = fileStatus.getPath().getName();
602          if (fileName.endsWith(SnapshotDescriptionUtils.SNAPSHOTINFO_FILE)
603            || fileName.endsWith(".tabledesc")
604            || fileName.endsWith(SnapshotDescriptionUtils.SNAPSHOT_TMP_DIR_NAME)) {
605              fs.delete(fileStatus.getPath(), true);
606          }
607        }
608      }
609
610      /**
611       * Corrupt data-manifest file
612       *
613       * @throws IOException on unexecpted error from the FS
614       */
615      public void corruptDataManifest() throws IOException {
616        FileStatus[] manifestFiles = FSUtils.listStatus(fs, snapshotDir, new PathFilter() {
617          @Override
618          public boolean accept(Path path) {
619            return path.getName().startsWith(SnapshotManifest.DATA_MANIFEST_NAME);
620          }
621        });
622
623        if (manifestFiles.length == 0) return;
624
625        // Just choose the first one
626        Path p = manifestFiles[0].getPath();
627        corruptFile(p);
628      }
629
630      public Path commit() throws IOException {
631        ForeignExceptionDispatcher monitor = new ForeignExceptionDispatcher(desc.getName());
632        SnapshotManifest manifest = SnapshotManifest.create(conf, fs, snapshotDir, desc, monitor);
633        manifest.addTableDescriptor(htd);
634        manifest.consolidate();
635        SnapshotDescriptionUtils.completeSnapshot(desc, rootDir, snapshotDir, fs);
636        snapshotDir = SnapshotDescriptionUtils.getCompletedSnapshotDir(desc, rootDir);
637        return snapshotDir;
638      }
639
640      public void consolidate() throws IOException {
641        ForeignExceptionDispatcher monitor = new ForeignExceptionDispatcher(desc.getName());
642        SnapshotManifest manifest = SnapshotManifest.create(conf, fs, snapshotDir, desc, monitor);
643        manifest.addTableDescriptor(htd);
644        manifest.consolidate();
645      }
646    }
647
648    public SnapshotMock(final Configuration conf, final FileSystem fs, final Path rootDir) {
649      this.fs = fs;
650      this.conf = conf;
651      this.rootDir = rootDir;
652    }
653
654    public SnapshotBuilder createSnapshotV1(final String snapshotName, final String tableName)
655        throws IOException {
656      return createSnapshot(snapshotName, tableName, SnapshotManifestV1.DESCRIPTOR_VERSION);
657    }
658
659    public SnapshotBuilder createSnapshotV1(final String snapshotName, final String tableName,
660        final int numRegions) throws IOException {
661      return createSnapshot(snapshotName, tableName, numRegions, SnapshotManifestV1.DESCRIPTOR_VERSION);
662    }
663
664    public SnapshotBuilder createSnapshotV2(final String snapshotName, final String tableName)
665        throws IOException {
666      return createSnapshot(snapshotName, tableName, SnapshotManifestV2.DESCRIPTOR_VERSION);
667    }
668
669    public SnapshotBuilder createSnapshotV2(final String snapshotName, final String tableName,
670        final int numRegions) throws IOException {
671      return createSnapshot(snapshotName, tableName, numRegions, SnapshotManifestV2.DESCRIPTOR_VERSION);
672    }
673
674    private SnapshotBuilder createSnapshot(final String snapshotName, final String tableName,
675        final int version) throws IOException {
676      return createSnapshot(snapshotName, tableName, TEST_NUM_REGIONS, version);
677    }
678
679    private SnapshotBuilder createSnapshot(final String snapshotName, final String tableName,
680        final int numRegions, final int version) throws IOException {
681      TableDescriptor htd = createHtd(tableName);
682      RegionData[] regions = createTable(htd, numRegions);
683
684      SnapshotProtos.SnapshotDescription desc = SnapshotProtos.SnapshotDescription.newBuilder()
685        .setTable(htd.getTableName().getNameAsString())
686        .setName(snapshotName)
687        .setVersion(version)
688        .build();
689
690      Path workingDir = SnapshotDescriptionUtils.getWorkingSnapshotDir(desc, rootDir, conf);
691      SnapshotDescriptionUtils.writeSnapshotInfo(desc, workingDir, fs);
692      return new SnapshotBuilder(conf, fs, rootDir, htd, desc, regions);
693    }
694
695    public TableDescriptor createHtd(final String tableName) {
696      return TableDescriptorBuilder.newBuilder(TableName.valueOf(tableName))
697              .setColumnFamily(ColumnFamilyDescriptorBuilder.of(TEST_FAMILY))
698              .build();
699    }
700
701    private RegionData[] createTable(final TableDescriptor htd, final int nregions)
702        throws IOException {
703      Path tableDir = FSUtils.getTableDir(rootDir, htd.getTableName());
704      new FSTableDescriptors(conf).createTableDescriptorForTableDirectory(tableDir, htd, false);
705
706      assertTrue(nregions % 2 == 0);
707      RegionData[] regions = new RegionData[nregions];
708      for (int i = 0; i < regions.length; i += 2) {
709        byte[] startKey = Bytes.toBytes(0 + i * 2);
710        byte[] endKey = Bytes.toBytes(1 + i * 2);
711
712        // First region, simple with one plain hfile.
713        RegionInfo hri = RegionInfoBuilder.newBuilder(htd.getTableName())
714            .setStartKey(startKey)
715            .setEndKey(endKey)
716            .build();
717        HRegionFileSystem rfs = HRegionFileSystem.createRegionOnFileSystem(conf, fs, tableDir, hri);
718        regions[i] = new RegionData(tableDir, hri, 3);
719        for (int j = 0; j < regions[i].files.length; ++j) {
720          Path storeFile = createStoreFile(rfs.createTempName());
721          regions[i].files[j] = rfs.commitStoreFile(TEST_FAMILY, storeFile);
722        }
723
724        // Second region, used to test the split case.
725        // This region contains a reference to the hfile in the first region.
726        startKey = Bytes.toBytes(2 + i * 2);
727        endKey = Bytes.toBytes(3 + i * 2);
728        hri = RegionInfoBuilder.newBuilder(htd.getTableName()).build();
729        rfs = HRegionFileSystem.createRegionOnFileSystem(conf, fs, tableDir, hri);
730        regions[i+1] = new RegionData(tableDir, hri, regions[i].files.length);
731        for (int j = 0; j < regions[i].files.length; ++j) {
732          String refName = regions[i].files[j].getName() + '.' + regions[i].hri.getEncodedName();
733          Path refFile = createStoreFile(new Path(rootDir, refName));
734          regions[i+1].files[j] = rfs.commitStoreFile(TEST_FAMILY, refFile);
735        }
736      }
737      return regions;
738    }
739
740    private Path createStoreFile(final Path storeFile)
741        throws IOException {
742      FSDataOutputStream out = fs.create(storeFile);
743      try {
744        out.write(Bytes.toBytes(storeFile.toString()));
745      } finally {
746        out.close();
747      }
748      return storeFile;
749    }
750  }
751
752  // ==========================================================================
753  //  Table Helpers
754  // ==========================================================================
755  public static void waitForTableToBeOnline(final HBaseTestingUtility util,
756                                            final TableName tableName)
757      throws IOException, InterruptedException {
758    HRegionServer rs = util.getRSForFirstRegionInTable(tableName);
759    List<HRegion> onlineRegions = rs.getRegions(tableName);
760    for (HRegion region : onlineRegions) {
761      region.waitForFlushesAndCompactions();
762    }
763    // Wait up to 60 seconds for a table to be available.
764    util.waitFor(60000, util.predicateTableAvailable(tableName));
765  }
766
767  public static void createTable(final HBaseTestingUtility util, final TableName tableName,
768      int regionReplication, int nRegions, final byte[]... families)
769      throws IOException, InterruptedException {
770    TableDescriptorBuilder builder
771      = TableDescriptorBuilder
772          .newBuilder(tableName)
773          .setRegionReplication(regionReplication);
774    for (byte[] family : families) {
775      builder.setColumnFamily(ColumnFamilyDescriptorBuilder.of(family));
776    }
777    byte[][] splitKeys = getSplitKeys(nRegions);
778    util.createTable(builder.build(), splitKeys);
779    assertEquals((splitKeys.length + 1) * regionReplication,
780        util.getAdmin().getTableRegions(tableName).size());
781  }
782
783  public static byte[][] getSplitKeys() {
784    return getSplitKeys(KEYS.length);
785  }
786
787  public static byte[][] getSplitKeys(int nRegions) {
788    nRegions = nRegions < KEYS.length ? nRegions : (KEYS.length - 1);
789    final byte[][] splitKeys = new byte[nRegions-1][];
790    final int step = KEYS.length / nRegions;
791    int keyIndex = 1;
792    for (int i = 0; i < splitKeys.length; ++i) {
793      splitKeys[i] = new byte[] { KEYS[keyIndex] };
794      keyIndex += step;
795    }
796    return splitKeys;
797  }
798
799  public static void createTable(final HBaseTestingUtility util, final TableName tableName,
800      final byte[]... families) throws IOException, InterruptedException {
801    createTable(util, tableName, 1, families);
802  }
803
804  public static void createTable(final HBaseTestingUtility util, final TableName tableName,
805      final int regionReplication, final byte[]... families) throws IOException, InterruptedException {
806    createTable(util, tableName, regionReplication, KEYS.length, families);
807  }
808
809  public static void createPreSplitTable(final HBaseTestingUtility util, final TableName tableName,
810      final int nRegions, final byte[]... families) throws IOException, InterruptedException {
811    createTable(util, tableName, 1, nRegions, families);
812  }
813
814  public static void loadData(final HBaseTestingUtility util, final TableName tableName, int rows,
815      byte[]... families) throws IOException, InterruptedException {
816    BufferedMutator mutator = util.getConnection().getBufferedMutator(tableName);
817    loadData(util, mutator, rows, families);
818  }
819
820  public static void loadData(final HBaseTestingUtility util, final BufferedMutator mutator, int rows,
821      byte[]... families) throws IOException, InterruptedException {
822    // Ensure one row per region
823    assertTrue(rows >= KEYS.length);
824    for (byte k0: KEYS) {
825      byte[] k = new byte[] { k0 };
826      byte[] value = Bytes.add(Bytes.toBytes(System.currentTimeMillis()), k);
827      byte[] key = Bytes.add(k, Bytes.toBytes(MD5Hash.getMD5AsHex(value)));
828      final byte[][] families1 = families;
829      final byte[] key1 = key;
830      final byte[] value1 = value;
831      mutator.mutate(createPut(families1, key1, value1));
832      rows--;
833    }
834
835    // Add other extra rows. more rows, more files
836    while (rows-- > 0) {
837      byte[] value = Bytes.add(Bytes.toBytes(System.currentTimeMillis()), Bytes.toBytes(rows));
838      byte[] key = Bytes.toBytes(MD5Hash.getMD5AsHex(value));
839      final byte[][] families1 = families;
840      final byte[] key1 = key;
841      final byte[] value1 = value;
842      mutator.mutate(createPut(families1, key1, value1));
843    }
844    mutator.flush();
845
846    waitForTableToBeOnline(util, mutator.getName());
847  }
848
849  private static Put createPut(final byte[][] families, final byte[] key, final byte[] value) {
850    byte[] q = Bytes.toBytes("q");
851    Put put = new Put(key);
852    put.setDurability(Durability.SKIP_WAL);
853    for (byte[] family: families) {
854      put.addColumn(family, q, value);
855    }
856    return put;
857  }
858
859  public static void deleteAllSnapshots(final Admin admin)
860      throws IOException {
861    // Delete all the snapshots
862    for (SnapshotDescription snapshot: admin.listSnapshots()) {
863      admin.deleteSnapshot(snapshot.getName());
864    }
865    SnapshotTestingUtils.assertNoSnapshots(admin);
866  }
867
868  public static void deleteArchiveDirectory(final HBaseTestingUtility util)
869      throws IOException {
870    // Ensure the archiver to be empty
871    MasterFileSystem mfs = util.getMiniHBaseCluster().getMaster().getMasterFileSystem();
872    Path archiveDir = new Path(mfs.getRootDir(), HConstants.HFILE_ARCHIVE_DIRECTORY);
873    mfs.getFileSystem().delete(archiveDir, true);
874  }
875
876  public static void verifyRowCount(final HBaseTestingUtility util, final TableName tableName,
877      long expectedRows) throws IOException {
878    Table table = util.getConnection().getTable(tableName);
879    try {
880      assertEquals(expectedRows, util.countRows(table));
881    } finally {
882      table.close();
883    }
884  }
885
886  public static void verifyReplicasCameOnline(TableName tableName, Admin admin,
887      int regionReplication) throws IOException {
888    List<RegionInfo> regions = admin.getRegions(tableName);
889    HashSet<RegionInfo> set = new HashSet<>();
890    for (RegionInfo hri : regions) {
891      set.add(RegionReplicaUtil.getRegionInfoForDefaultReplica(hri));
892      for (int i = 0; i < regionReplication; i++) {
893        RegionInfo replica = RegionReplicaUtil.getRegionInfoForReplica(hri, i);
894        if (!regions.contains(replica)) {
895          Assert.fail(replica + " is not contained in the list of online regions");
896        }
897      }
898    }
899    assertEquals(getSplitKeys().length + 1, set.size());
900  }
901}