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