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