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.master.cleaner;
019
020import static org.junit.Assert.assertEquals;
021import static org.junit.Assert.assertTrue;
022import static org.junit.Assert.fail;
023
024import java.io.IOException;
025import java.util.Collection;
026import java.util.List;
027import java.util.Set;
028import java.util.concurrent.Future;
029import java.util.concurrent.TimeUnit;
030import java.util.regex.Pattern;
031import org.apache.hadoop.conf.Configuration;
032import org.apache.hadoop.fs.FileSystem;
033import org.apache.hadoop.fs.Path;
034import org.apache.hadoop.hbase.HBaseClassTestRule;
035import org.apache.hadoop.hbase.HBaseTestingUtility;
036import org.apache.hadoop.hbase.HConstants;
037import org.apache.hadoop.hbase.TableName;
038import org.apache.hadoop.hbase.Waiter;
039import org.apache.hadoop.hbase.Waiter.ExplainingPredicate;
040import org.apache.hadoop.hbase.client.Admin;
041import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder;
042import org.apache.hadoop.hbase.client.Put;
043import org.apache.hadoop.hbase.client.SnapshotType;
044import org.apache.hadoop.hbase.client.Table;
045import org.apache.hadoop.hbase.client.TableDescriptor;
046import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
047import org.apache.hadoop.hbase.master.HMaster;
048import org.apache.hadoop.hbase.master.snapshot.DisabledTableSnapshotHandler;
049import org.apache.hadoop.hbase.master.snapshot.SnapshotHFileCleaner;
050import org.apache.hadoop.hbase.master.snapshot.SnapshotManager;
051import org.apache.hadoop.hbase.regionserver.CompactedHFilesDischarger;
052import org.apache.hadoop.hbase.regionserver.ConstantSizeRegionSplitPolicy;
053import org.apache.hadoop.hbase.regionserver.HRegion;
054import org.apache.hadoop.hbase.regionserver.HRegionServer;
055import org.apache.hadoop.hbase.snapshot.SnapshotDescriptionUtils;
056import org.apache.hadoop.hbase.snapshot.SnapshotReferenceUtil;
057import org.apache.hadoop.hbase.snapshot.SnapshotTestingUtils;
058import org.apache.hadoop.hbase.snapshot.UnknownSnapshotException;
059import org.apache.hadoop.hbase.testclassification.MasterTests;
060import org.apache.hadoop.hbase.testclassification.MediumTests;
061import org.apache.hadoop.hbase.util.Bytes;
062import org.apache.hadoop.hbase.util.CommonFSUtils;
063import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
064import org.apache.hadoop.hbase.util.JVMClusterUtil.RegionServerThread;
065import org.junit.After;
066import org.junit.AfterClass;
067import org.junit.Assert;
068import org.junit.Before;
069import org.junit.BeforeClass;
070import org.junit.ClassRule;
071import org.junit.Test;
072import org.junit.experimental.categories.Category;
073import org.mockito.Mockito;
074import org.slf4j.Logger;
075import org.slf4j.LoggerFactory;
076
077import org.apache.hbase.thirdparty.com.google.common.collect.Lists;
078import org.apache.hbase.thirdparty.com.google.common.util.concurrent.Uninterruptibles;
079
080import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.DeleteSnapshotRequest;
081import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.GetCompletedSnapshotsRequest;
082import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.GetCompletedSnapshotsResponse;
083import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.IsSnapshotCleanupEnabledRequest;
084import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.IsSnapshotCleanupEnabledResponse;
085import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.IsSnapshotDoneRequest;
086import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.IsSnapshotDoneResponse;
087import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.SetSnapshotCleanupRequest;
088import org.apache.hadoop.hbase.shaded.protobuf.generated.SnapshotProtos.SnapshotDescription;
089
090/**
091 * Test the master-related aspects of a snapshot
092 */
093@Category({ MasterTests.class, MediumTests.class })
094public class TestSnapshotFromMaster {
095
096  @ClassRule
097  public static final HBaseClassTestRule CLASS_RULE =
098    HBaseClassTestRule.forClass(TestSnapshotFromMaster.class);
099
100  private static final Logger LOG = LoggerFactory.getLogger(TestSnapshotFromMaster.class);
101  private static final HBaseTestingUtility UTIL = new HBaseTestingUtility();
102  private static final int NUM_RS = 2;
103  private static Path rootDir;
104  private static FileSystem fs;
105  private static HMaster master;
106
107  // for hfile archiving test.
108  private static Path archiveDir;
109  private static final byte[] TEST_FAM = Bytes.toBytes("fam");
110  private static final TableName TABLE_NAME = TableName.valueOf("test");
111  // refresh the cache every 1/2 second
112  private static final long cacheRefreshPeriod = 500;
113  private static final int blockingStoreFiles = 12;
114
115  /**
116   * Setup the config for the cluster
117   */
118  @BeforeClass
119  public static void setupCluster() throws Exception {
120    setupConf(UTIL.getConfiguration());
121    UTIL.startMiniCluster(NUM_RS);
122    fs = UTIL.getDFSCluster().getFileSystem();
123    master = UTIL.getMiniHBaseCluster().getMaster();
124    rootDir = master.getMasterFileSystem().getRootDir();
125    archiveDir = new Path(rootDir, HConstants.HFILE_ARCHIVE_DIRECTORY);
126  }
127
128  private static void setupConf(Configuration conf) {
129    // disable the ui
130    conf.setInt("hbase.regionsever.info.port", -1);
131    // change the flush size to a small amount, regulating number of store files
132    conf.setInt("hbase.hregion.memstore.flush.size", 25000);
133    // so make sure we get a compaction when doing a load, but keep around some
134    // files in the store
135    conf.setInt("hbase.hstore.compaction.min", 2);
136    conf.setInt("hbase.hstore.compactionThreshold", 5);
137    // block writes if we get to 12 store files
138    conf.setInt("hbase.hstore.blockingStoreFiles", blockingStoreFiles);
139    // Ensure no extra cleaners on by default (e.g. TimeToLiveHFileCleaner)
140    conf.set(HFileCleaner.MASTER_HFILE_CLEANER_PLUGINS, "");
141    conf.set(HConstants.HBASE_MASTER_LOGCLEANER_PLUGINS, "");
142    // Enable snapshot
143    conf.setBoolean(SnapshotManager.HBASE_SNAPSHOT_ENABLED, true);
144    conf.setLong(SnapshotManager.HBASE_SNAPSHOT_SENTINELS_CLEANUP_TIMEOUT_MILLIS, 3 * 1000L);
145    conf.setLong(SnapshotHFileCleaner.HFILE_CACHE_REFRESH_PERIOD_CONF_KEY, cacheRefreshPeriod);
146    conf.set(HConstants.HBASE_REGION_SPLIT_POLICY_KEY,
147      ConstantSizeRegionSplitPolicy.class.getName());
148    conf.setInt("hbase.hfile.compactions.cleaner.interval", 20 * 1000);
149    conf.setInt("hbase.master.cleaner.snapshot.interval", 500);
150  }
151
152  @Before
153  public void setup() throws Exception {
154    UTIL.createTable(TABLE_NAME, TEST_FAM);
155    master.getSnapshotManager().setSnapshotHandlerForTesting(TABLE_NAME, null);
156  }
157
158  @After
159  public void tearDown() throws Exception {
160    UTIL.deleteTable(TABLE_NAME);
161    SnapshotTestingUtils.deleteAllSnapshots(UTIL.getAdmin());
162    SnapshotTestingUtils.deleteArchiveDirectory(UTIL);
163  }
164
165  @AfterClass
166  public static void cleanupTest() throws Exception {
167    try {
168      UTIL.shutdownMiniCluster();
169    } catch (Exception e) {
170      // NOOP;
171    }
172  }
173
174  /**
175   * Test that the contract from the master for checking on a snapshot are valid.
176   * <p>
177   * <ol>
178   * <li>If a snapshot fails with an error, we expect to get the source error.</li>
179   * <li>If there is no snapshot name supplied, we should get an error.</li>
180   * <li>If asking about a snapshot has hasn't occurred, you should get an error.</li>
181   * </ol>
182   */
183  @Test
184  public void testIsDoneContract() throws Exception {
185
186    IsSnapshotDoneRequest.Builder builder = IsSnapshotDoneRequest.newBuilder();
187
188    String snapshotName = "asyncExpectedFailureTest";
189
190    // check that we get an exception when looking up snapshot where one hasn't happened
191    SnapshotTestingUtils.expectSnapshotDoneException(master, builder.build(),
192      UnknownSnapshotException.class);
193
194    // and that we get the same issue, even if we specify a name
195    SnapshotDescription desc = SnapshotDescription.newBuilder().setName(snapshotName)
196      .setTable(TABLE_NAME.getNameAsString()).build();
197    builder.setSnapshot(desc);
198    SnapshotTestingUtils.expectSnapshotDoneException(master, builder.build(),
199      UnknownSnapshotException.class);
200
201    // set a mock handler to simulate a snapshot
202    DisabledTableSnapshotHandler mockHandler = Mockito.mock(DisabledTableSnapshotHandler.class);
203    Mockito.when(mockHandler.getException()).thenReturn(null);
204    Mockito.when(mockHandler.getSnapshot()).thenReturn(desc);
205    Mockito.when(mockHandler.isFinished()).thenReturn(Boolean.TRUE);
206    Mockito.when(mockHandler.getCompletionTimestamp())
207      .thenReturn(EnvironmentEdgeManager.currentTime());
208
209    master.getSnapshotManager().setSnapshotHandlerForTesting(TABLE_NAME, mockHandler);
210
211    // if we do a lookup without a snapshot name, we should fail - you should always know your name
212    builder = IsSnapshotDoneRequest.newBuilder();
213    SnapshotTestingUtils.expectSnapshotDoneException(master, builder.build(),
214      UnknownSnapshotException.class);
215
216    // then do the lookup for the snapshot that it is done
217    builder.setSnapshot(desc);
218    IsSnapshotDoneResponse response =
219      master.getMasterRpcServices().isSnapshotDone(null, builder.build());
220    assertTrue("Snapshot didn't complete when it should have.", response.getDone());
221
222    // now try the case where we are looking for a snapshot we didn't take
223    builder.setSnapshot(SnapshotDescription.newBuilder().setName("Not A Snapshot").build());
224    SnapshotTestingUtils.expectSnapshotDoneException(master, builder.build(),
225      UnknownSnapshotException.class);
226
227    // then create a snapshot to the fs and make sure that we can find it when checking done
228    snapshotName = "completed";
229    desc = createSnapshot(snapshotName);
230
231    builder.setSnapshot(desc);
232    response = master.getMasterRpcServices().isSnapshotDone(null, builder.build());
233    assertTrue("Completed, on-disk snapshot not found", response.getDone());
234  }
235
236  @Test
237  public void testGetCompletedSnapshots() throws Exception {
238    // first check when there are no snapshots
239    GetCompletedSnapshotsRequest request = GetCompletedSnapshotsRequest.newBuilder().build();
240    GetCompletedSnapshotsResponse response =
241      master.getMasterRpcServices().getCompletedSnapshots(null, request);
242    assertEquals("Found unexpected number of snapshots", 0, response.getSnapshotsCount());
243
244    // write one snapshot to the fs
245    String snapshotName = "completed";
246    SnapshotDescription snapshot = createSnapshot(snapshotName);
247
248    // check that we get one snapshot
249    response = master.getMasterRpcServices().getCompletedSnapshots(null, request);
250    assertEquals("Found unexpected number of snapshots", 1, response.getSnapshotsCount());
251    List<SnapshotDescription> snapshots = response.getSnapshotsList();
252    List<SnapshotDescription> expected = Lists.newArrayList(snapshot);
253    assertEquals("Returned snapshots don't match created snapshots", expected, snapshots);
254
255    // write a second snapshot
256    snapshotName = "completed_two";
257    snapshot = createSnapshot(snapshotName);
258    expected.add(snapshot);
259
260    // check that we get one snapshot
261    response = master.getMasterRpcServices().getCompletedSnapshots(null, request);
262    assertEquals("Found unexpected number of snapshots", 2, response.getSnapshotsCount());
263    snapshots = response.getSnapshotsList();
264    assertEquals("Returned snapshots don't match created snapshots", expected, snapshots);
265  }
266
267  @Test
268  public void testDeleteSnapshot() throws Exception {
269
270    String snapshotName = "completed";
271    SnapshotDescription snapshot = SnapshotDescription.newBuilder().setName(snapshotName).build();
272
273    DeleteSnapshotRequest request =
274      DeleteSnapshotRequest.newBuilder().setSnapshot(snapshot).build();
275    try {
276      master.getMasterRpcServices().deleteSnapshot(null, request);
277      fail("Master didn't throw exception when attempting to delete snapshot that doesn't exist");
278    } catch (org.apache.hbase.thirdparty.com.google.protobuf.ServiceException e) {
279      // Expected
280    }
281
282    // write one snapshot to the fs
283    createSnapshot(snapshotName);
284
285    // then delete the existing snapshot,which shouldn't cause an exception to be thrown
286    master.getMasterRpcServices().deleteSnapshot(null, request);
287  }
288
289  @Test
290  public void testGetCompletedSnapshotsWithCleanup() throws Exception {
291    // Enable auto snapshot cleanup for the cluster
292    SetSnapshotCleanupRequest setSnapshotCleanupRequest =
293      SetSnapshotCleanupRequest.newBuilder().setEnabled(true).build();
294    master.getMasterRpcServices().switchSnapshotCleanup(null, setSnapshotCleanupRequest);
295
296    // first check when there are no snapshots
297    GetCompletedSnapshotsRequest request = GetCompletedSnapshotsRequest.newBuilder().build();
298    GetCompletedSnapshotsResponse response =
299      master.getMasterRpcServices().getCompletedSnapshots(null, request);
300    assertEquals("Found unexpected number of snapshots", 0, response.getSnapshotsCount());
301
302    // NOTE: This is going to be flakey. Its timing based. For now made it more coarse
303    // so more likely to pass though we have to hang around longer.
304
305    // write one snapshot to the fs
306    createSnapshotWithTtl("snapshot_01", 5L);
307    createSnapshotWithTtl("snapshot_02", 100L);
308
309    // check that we get one snapshot
310    response = master.getMasterRpcServices().getCompletedSnapshots(null, request);
311    assertEquals("Found unexpected number of snapshots", 2, response.getSnapshotsCount());
312
313    // Check that 1 snapshot is auto cleaned after 5 sec of TTL expiration. Wait 10 seconds
314    // just in case.
315    Uninterruptibles.sleepUninterruptibly(10, TimeUnit.SECONDS);
316    response = master.getMasterRpcServices().getCompletedSnapshots(null, request);
317    assertEquals("Found unexpected number of snapshots", 1, response.getSnapshotsCount());
318  }
319
320  @Test
321  public void testGetCompletedSnapshotsWithoutCleanup() throws Exception {
322    // Disable auto snapshot cleanup for the cluster
323    SetSnapshotCleanupRequest setSnapshotCleanupRequest =
324      SetSnapshotCleanupRequest.newBuilder().setEnabled(false).build();
325    master.getMasterRpcServices().switchSnapshotCleanup(null, setSnapshotCleanupRequest);
326
327    // first check when there are no snapshots
328    GetCompletedSnapshotsRequest request = GetCompletedSnapshotsRequest.newBuilder().build();
329    GetCompletedSnapshotsResponse response =
330      master.getMasterRpcServices().getCompletedSnapshots(null, request);
331    assertEquals("Found unexpected number of snapshots", 0, response.getSnapshotsCount());
332
333    // write one snapshot to the fs
334    createSnapshotWithTtl("snapshot_02", 1L);
335    createSnapshotWithTtl("snapshot_03", 1L);
336
337    // check that we get one snapshot
338    response = master.getMasterRpcServices().getCompletedSnapshots(null, request);
339    assertEquals("Found unexpected number of snapshots", 2, response.getSnapshotsCount());
340
341    // check that no snapshot is auto cleaned even after 1 sec of TTL expiration
342    Uninterruptibles.sleepUninterruptibly(2, TimeUnit.SECONDS);
343    response = master.getMasterRpcServices().getCompletedSnapshots(null, request);
344    assertEquals("Found unexpected number of snapshots", 2, response.getSnapshotsCount());
345  }
346
347  @Test
348  public void testSnapshotCleanupStatus() throws Exception {
349    // Enable auto snapshot cleanup for the cluster
350    SetSnapshotCleanupRequest setSnapshotCleanupRequest =
351      SetSnapshotCleanupRequest.newBuilder().setEnabled(true).build();
352    master.getMasterRpcServices().switchSnapshotCleanup(null, setSnapshotCleanupRequest);
353
354    // Check if auto snapshot cleanup is enabled
355    IsSnapshotCleanupEnabledRequest isSnapshotCleanupEnabledRequest =
356      IsSnapshotCleanupEnabledRequest.newBuilder().build();
357    IsSnapshotCleanupEnabledResponse isSnapshotCleanupEnabledResponse =
358      master.getMasterRpcServices().isSnapshotCleanupEnabled(null, isSnapshotCleanupEnabledRequest);
359    Assert.assertTrue(isSnapshotCleanupEnabledResponse.getEnabled());
360
361    // Disable auto snapshot cleanup for the cluster
362    setSnapshotCleanupRequest = SetSnapshotCleanupRequest.newBuilder().setEnabled(false).build();
363    master.getMasterRpcServices().switchSnapshotCleanup(null, setSnapshotCleanupRequest);
364
365    // Check if auto snapshot cleanup is disabled
366    isSnapshotCleanupEnabledRequest = IsSnapshotCleanupEnabledRequest.newBuilder().build();
367    isSnapshotCleanupEnabledResponse =
368      master.getMasterRpcServices().isSnapshotCleanupEnabled(null, isSnapshotCleanupEnabledRequest);
369    Assert.assertFalse(isSnapshotCleanupEnabledResponse.getEnabled());
370  }
371
372  /**
373   * Test that the snapshot hfile archive cleaner works correctly. HFiles that are in snapshots
374   * should be retained, while those that are not in a snapshot should be deleted.
375   * @throws Exception on failure
376   */
377  @Test
378  public void testSnapshotHFileArchiving() throws Exception {
379    Admin admin = UTIL.getAdmin();
380    // make sure we don't fail on listing snapshots
381    SnapshotTestingUtils.assertNoSnapshots(admin);
382
383    // recreate test table with disabled compactions; otherwise compaction may happen before
384    // snapshot, the call after snapshot will be a no-op and checks will fail
385    UTIL.deleteTable(TABLE_NAME);
386    TableDescriptor td = TableDescriptorBuilder.newBuilder(TABLE_NAME)
387      .setColumnFamily(ColumnFamilyDescriptorBuilder.of(TEST_FAM)).setCompactionEnabled(false)
388      .build();
389    UTIL.getAdmin().createTable(td);
390
391    // load the table
392    for (int i = 0; i < blockingStoreFiles / 2; i++) {
393      UTIL.loadTable(UTIL.getConnection().getTable(TABLE_NAME), TEST_FAM);
394      UTIL.flush(TABLE_NAME);
395    }
396
397    // disable the table so we can take a snapshot
398    admin.disableTable(TABLE_NAME);
399
400    // take a snapshot of the table
401    String snapshotName = "snapshot";
402    byte[] snapshotNameBytes = Bytes.toBytes(snapshotName);
403    admin.snapshot(snapshotName, TABLE_NAME);
404
405    LOG.info("After snapshot File-System state");
406    CommonFSUtils.logFileSystemState(fs, rootDir, LOG);
407
408    // ensure we only have one snapshot
409    SnapshotTestingUtils.assertOneSnapshotThatMatches(admin, snapshotNameBytes, TABLE_NAME);
410
411    td = TableDescriptorBuilder.newBuilder(td).setCompactionEnabled(true).build();
412    // enable compactions now
413    admin.modifyTable(td);
414
415    // renable the table so we can compact the regions
416    admin.enableTable(TABLE_NAME);
417
418    // compact the files so we get some archived files for the table we just snapshotted
419    List<HRegion> regions = UTIL.getHBaseCluster().getRegions(TABLE_NAME);
420    for (HRegion region : regions) {
421      region.waitForFlushesAndCompactions(); // enable can trigger a compaction, wait for it.
422      region.compactStores(); // min is 2 so will compact and archive
423    }
424    List<RegionServerThread> regionServerThreads =
425      UTIL.getMiniHBaseCluster().getRegionServerThreads();
426    HRegionServer hrs = null;
427    for (RegionServerThread rs : regionServerThreads) {
428      if (!rs.getRegionServer().getRegions(TABLE_NAME).isEmpty()) {
429        hrs = rs.getRegionServer();
430        break;
431      }
432    }
433    CompactedHFilesDischarger cleaner = new CompactedHFilesDischarger(100, null, hrs, false);
434    cleaner.chore();
435    LOG.info("After compaction File-System state");
436    CommonFSUtils.logFileSystemState(fs, rootDir, LOG);
437
438    // make sure the cleaner has run
439    LOG.debug("Running hfile cleaners");
440    ensureHFileCleanersRun();
441    LOG.info("After cleaners File-System state: " + rootDir);
442    CommonFSUtils.logFileSystemState(fs, rootDir, LOG);
443
444    // get the snapshot files for the table
445    Path snapshotTable = SnapshotDescriptionUtils.getCompletedSnapshotDir(snapshotName, rootDir);
446    Set<String> snapshotHFiles =
447      SnapshotReferenceUtil.getHFileNames(UTIL.getConfiguration(), fs, snapshotTable);
448    // check that the files in the archive contain the ones that we need for the snapshot
449    LOG.debug("Have snapshot hfiles:");
450    for (String fileName : snapshotHFiles) {
451      LOG.debug(fileName);
452    }
453    // get the archived files for the table
454    Collection<String> archives = getHFiles(archiveDir, fs, TABLE_NAME);
455
456    // get the hfiles for the table
457    Collection<String> hfiles = getHFiles(rootDir, fs, TABLE_NAME);
458
459    // and make sure that there is a proper subset
460    for (String fileName : snapshotHFiles) {
461      boolean exist = archives.contains(fileName) || hfiles.contains(fileName);
462      assertTrue("Archived hfiles " + archives + " and table hfiles " + hfiles
463        + " is missing snapshot file:" + fileName, exist);
464    }
465
466    // delete the existing snapshot
467    admin.deleteSnapshot(snapshotNameBytes);
468    SnapshotTestingUtils.assertNoSnapshots(admin);
469
470    // make sure that we don't keep around the hfiles that aren't in a snapshot
471    // make sure we wait long enough to refresh the snapshot hfile
472    List<BaseHFileCleanerDelegate> delegates =
473      UTIL.getMiniHBaseCluster().getMaster().getHFileCleaner().cleanersChain;
474    for (BaseHFileCleanerDelegate delegate : delegates) {
475      if (delegate instanceof SnapshotHFileCleaner) {
476        ((SnapshotHFileCleaner) delegate).getFileCacheForTesting().triggerCacheRefreshForTesting();
477      }
478    }
479    // run the cleaner again
480    // In SnapshotFileCache, we use lock.tryLock to avoid being blocked by snapshot operation, if we
481    // fail to get the lock, we will not archive any files. This is not a big deal in real world, so
482    // here we will try to run the cleaner multiple times to make the test more stable.
483    // See HBASE-26861
484    UTIL.waitFor(60000, 1000, new ExplainingPredicate<Exception>() {
485
486      @Override
487      public boolean evaluate() throws Exception {
488        LOG.debug("Running hfile cleaners");
489        ensureHFileCleanersRun();
490        LOG.info("After delete snapshot cleaners run File-System state");
491        CommonFSUtils.logFileSystemState(fs, rootDir, LOG);
492        return getHFiles(archiveDir, fs, TABLE_NAME).isEmpty();
493      }
494
495      @Override
496      public String explainFailure() throws Exception {
497        return "Still have some hfiles in the archive, when their snapshot has been deleted: "
498          + getHFiles(archiveDir, fs, TABLE_NAME);
499      }
500    });
501  }
502
503  /**
504   * @return all the HFiles for a given table in the specified dir
505   * @throws IOException on expected failure
506   */
507  private final Collection<String> getHFiles(Path dir, FileSystem fs, TableName tableName)
508    throws IOException {
509    Path tableDir = CommonFSUtils.getTableDir(dir, tableName);
510    return SnapshotTestingUtils.listHFileNames(fs, tableDir);
511  }
512
513  /**
514   * Make sure the {@link HFileCleaner HFileCleaners} run at least once
515   */
516  private static void ensureHFileCleanersRun() {
517    UTIL.getHBaseCluster().getMaster().getHFileCleaner().chore();
518  }
519
520  private SnapshotDescription createSnapshot(final String snapshotName) throws IOException {
521    SnapshotTestingUtils.SnapshotMock snapshotMock =
522      new SnapshotTestingUtils.SnapshotMock(UTIL.getConfiguration(), fs, rootDir);
523    SnapshotTestingUtils.SnapshotMock.SnapshotBuilder builder =
524      snapshotMock.createSnapshotV2(snapshotName, "test", 0);
525    builder.commit();
526    return builder.getSnapshotDescription();
527  }
528
529  private SnapshotDescription createSnapshotWithTtl(final String snapshotName, final long ttl)
530    throws IOException {
531    SnapshotTestingUtils.SnapshotMock snapshotMock =
532      new SnapshotTestingUtils.SnapshotMock(UTIL.getConfiguration(), fs, rootDir);
533    SnapshotTestingUtils.SnapshotMock.SnapshotBuilder builder =
534      snapshotMock.createSnapshotV2(snapshotName, "test", 0, ttl);
535    builder.commit();
536    return builder.getSnapshotDescription();
537  }
538
539  @Test
540  public void testAsyncSnapshotWillNotBlockSnapshotHFileCleaner() throws Exception {
541    // Write some data
542    Table table = UTIL.getConnection().getTable(TABLE_NAME);
543    for (int i = 0; i < 10; i++) {
544      Put put = new Put(Bytes.toBytes(i)).addColumn(TEST_FAM, Bytes.toBytes("q"), Bytes.toBytes(i));
545      table.put(put);
546    }
547    String snapshotName = "testAsyncSnapshotWillNotBlockSnapshotHFileCleaner01";
548    Future<Void> future =
549      UTIL.getAdmin().snapshotAsync(new org.apache.hadoop.hbase.client.SnapshotDescription(
550        snapshotName, TABLE_NAME, SnapshotType.FLUSH));
551    Waiter.waitFor(UTIL.getConfiguration(), 10 * 1000L, 200L,
552      () -> UTIL.getAdmin().listSnapshots(Pattern.compile(snapshotName)).size() == 1);
553    UTIL.waitFor(30000, () -> !master.getSnapshotManager().isTakingAnySnapshot());
554  }
555}