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