View Javadoc

1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  package org.apache.hadoop.hbase.master.cleaner;
19  
20  import static org.junit.Assert.assertEquals;
21  import static org.junit.Assert.assertFalse;
22  import static org.junit.Assert.assertTrue;
23  import static org.junit.Assert.fail;
24  
25  import com.google.common.util.concurrent.Uninterruptibles;
26  import java.io.IOException;
27  import java.util.Collection;
28  import java.util.List;
29  import java.util.Set;
30  import java.util.concurrent.TimeUnit;
31  import java.util.regex.Pattern;
32  
33  import org.apache.commons.logging.Log;
34  import org.apache.commons.logging.LogFactory;
35  import org.apache.hadoop.conf.Configuration;
36  import org.apache.hadoop.fs.FileSystem;
37  import org.apache.hadoop.fs.Path;
38  import org.apache.hadoop.hbase.HBaseTestingUtility;
39  import org.apache.hadoop.hbase.HConstants;
40  import org.apache.hadoop.hbase.HTableDescriptor;
41  import org.apache.hadoop.hbase.Waiter.Predicate;
42  import org.apache.hadoop.hbase.client.Put;
43  import org.apache.hadoop.hbase.client.Table;
44  import org.apache.hadoop.hbase.protobuf.generated.HBaseProtos.SnapshotDescription;
45  import org.apache.hadoop.hbase.testclassification.MediumTests;
46  import org.apache.hadoop.hbase.TableName;
47  import org.apache.hadoop.hbase.client.Admin;
48  import org.apache.hadoop.hbase.master.HMaster;
49  import org.apache.hadoop.hbase.master.snapshot.DisabledTableSnapshotHandler;
50  import org.apache.hadoop.hbase.master.snapshot.SnapshotHFileCleaner;
51  import org.apache.hadoop.hbase.master.snapshot.SnapshotManager;
52  import org.apache.hadoop.hbase.protobuf.generated.MasterProtos.DeleteSnapshotRequest;
53  import org.apache.hadoop.hbase.protobuf.generated.MasterProtos.GetCompletedSnapshotsRequest;
54  import org.apache.hadoop.hbase.protobuf.generated.MasterProtos.GetCompletedSnapshotsResponse;
55  import org.apache.hadoop.hbase.protobuf.generated.MasterProtos.IsSnapshotCleanupEnabledRequest;
56  import org.apache.hadoop.hbase.protobuf.generated.MasterProtos.IsSnapshotCleanupEnabledResponse;
57  import org.apache.hadoop.hbase.protobuf.generated.MasterProtos.IsSnapshotDoneRequest;
58  import org.apache.hadoop.hbase.protobuf.generated.MasterProtos.IsSnapshotDoneResponse;
59  import org.apache.hadoop.hbase.protobuf.generated.MasterProtos.SetSnapshotCleanupRequest;
60  import org.apache.hadoop.hbase.regionserver.CompactedHFilesDischarger;
61  import org.apache.hadoop.hbase.regionserver.ConstantSizeRegionSplitPolicy;
62  import org.apache.hadoop.hbase.regionserver.HRegion;
63  import org.apache.hadoop.hbase.regionserver.HRegionServer;
64  import org.apache.hadoop.hbase.snapshot.SnapshotDescriptionUtils;
65  import org.apache.hadoop.hbase.snapshot.SnapshotReferenceUtil;
66  import org.apache.hadoop.hbase.snapshot.SnapshotTestingUtils;
67  import org.apache.hadoop.hbase.snapshot.UnknownSnapshotException;
68  import org.apache.hadoop.hbase.util.Bytes;
69  import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
70  import org.apache.hadoop.hbase.util.FSUtils;
71  import org.apache.hadoop.hbase.util.JVMClusterUtil.RegionServerThread;
72  import org.junit.After;
73  import org.junit.AfterClass;
74  import org.junit.Assert;
75  import org.junit.Before;
76  import org.junit.BeforeClass;
77  import org.junit.Test;
78  import org.junit.experimental.categories.Category;
79  import org.mockito.Mockito;
80  
81  import com.google.common.collect.Lists;
82  import com.google.protobuf.ServiceException;
83  
84  /**
85   * Test the master-related aspects of a snapshot
86   */
87  @Category(MediumTests.class)
88  public class TestSnapshotFromMaster {
89  
90    private static final Log LOG = LogFactory.getLog(TestSnapshotFromMaster.class);
91    private static final HBaseTestingUtility UTIL = new HBaseTestingUtility();
92    private static final int NUM_RS = 2;
93    private static Path rootDir;
94    private static FileSystem fs;
95    private static HMaster master;
96  
97    // for hfile archiving test.
98    private static Path archiveDir;
99    private static final byte[] TEST_FAM = Bytes.toBytes("fam");
100   private static final TableName TABLE_NAME =
101       TableName.valueOf("test");
102   // refresh the cache every 1/2 second
103   private static final long cacheRefreshPeriod = 500;
104   private static final int blockingStoreFiles = 12;
105 
106   /**
107    * Setup the config for the cluster
108    */
109   @BeforeClass
110   public static void setupCluster() throws Exception {
111     setupConf(UTIL.getConfiguration());
112     UTIL.startMiniCluster(NUM_RS);
113     fs = UTIL.getDFSCluster().getFileSystem();
114     master = UTIL.getMiniHBaseCluster().getMaster();
115     rootDir = master.getMasterFileSystem().getRootDir();
116     archiveDir = new Path(rootDir, HConstants.HFILE_ARCHIVE_DIRECTORY);
117   }
118 
119   private static void setupConf(Configuration conf) {
120     // disable the ui
121     conf.setInt("hbase.regionsever.info.port", -1);
122     // change the flush size to a small amount, regulating number of store files
123     conf.setInt("hbase.hregion.memstore.flush.size", 25000);
124     // so make sure we get a compaction when doing a load, but keep around some
125     // files in the store
126     conf.setInt("hbase.hstore.compaction.min", 2);
127     conf.setInt("hbase.hstore.compactionThreshold", 5);
128     // block writes if we get to 12 store files
129     conf.setInt("hbase.hstore.blockingStoreFiles", blockingStoreFiles);
130     // Ensure no extra cleaners on by default (e.g. TimeToLiveHFileCleaner)
131     conf.set(HFileCleaner.MASTER_HFILE_CLEANER_PLUGINS, "");
132     conf.set(HConstants.HBASE_MASTER_LOGCLEANER_PLUGINS, "");
133     // Enable snapshot
134     conf.setBoolean(SnapshotManager.HBASE_SNAPSHOT_ENABLED, true);
135     conf.setLong(SnapshotManager.HBASE_SNAPSHOT_SENTINELS_CLEANUP_TIMEOUT_MILLIS, 3 * 1000L);
136     conf.setLong(SnapshotHFileCleaner.HFILE_CACHE_REFRESH_PERIOD_CONF_KEY, cacheRefreshPeriod);
137     conf.set(HConstants.HBASE_REGION_SPLIT_POLICY_KEY,
138       ConstantSizeRegionSplitPolicy.class.getName());
139     conf.setInt("hbase.hfile.compactions.cleaner.interval", 20 * 1000);
140     conf.setInt("hbase.master.cleaner.snapshot.interval", 500);
141   }
142 
143   @Before
144   public void setup() throws Exception {
145     UTIL.createTable(TABLE_NAME, TEST_FAM);
146     master.getSnapshotManager().setSnapshotHandlerForTesting(TABLE_NAME, null);
147   }
148 
149   @After
150   public void tearDown() throws Exception {
151     UTIL.deleteTable(TABLE_NAME);
152     SnapshotTestingUtils.deleteAllSnapshots(UTIL.getHBaseAdmin());
153     SnapshotTestingUtils.deleteArchiveDirectory(UTIL);
154   }
155 
156   @AfterClass
157   public static void cleanupTest() throws Exception {
158     try {
159       UTIL.shutdownMiniCluster();
160     } catch (Exception e) {
161       // NOOP;
162     }
163   }
164 
165   /**
166    * Test that the contract from the master for checking on a snapshot are valid.
167    * <p>
168    * <ol>
169    * <li>If a snapshot fails with an error, we expect to get the source error.</li>
170    * <li>If there is no snapshot name supplied, we should get an error.</li>
171    * <li>If asking about a snapshot has hasn't occurred, you should get an error.</li>
172    * </ol>
173    */
174   @Test(timeout = 300000)
175   public void testIsDoneContract() throws Exception {
176 
177     IsSnapshotDoneRequest.Builder builder = IsSnapshotDoneRequest.newBuilder();
178 
179     String snapshotName = "asyncExpectedFailureTest";
180 
181     // check that we get an exception when looking up snapshot where one hasn't happened
182     SnapshotTestingUtils.expectSnapshotDoneException(master, builder.build(),
183       UnknownSnapshotException.class);
184 
185     // and that we get the same issue, even if we specify a name
186     SnapshotDescription desc = SnapshotDescription.newBuilder()
187       .setName(snapshotName).setTable(TABLE_NAME.getNameAsString()).build();
188     builder.setSnapshot(desc);
189     SnapshotTestingUtils.expectSnapshotDoneException(master, builder.build(),
190       UnknownSnapshotException.class);
191 
192     // set a mock handler to simulate a snapshot
193     DisabledTableSnapshotHandler mockHandler = Mockito.mock(DisabledTableSnapshotHandler.class);
194     Mockito.when(mockHandler.getException()).thenReturn(null);
195     Mockito.when(mockHandler.getSnapshot()).thenReturn(desc);
196     Mockito.when(mockHandler.isFinished()).thenReturn(new Boolean(true));
197     Mockito.when(mockHandler.getCompletionTimestamp())
198       .thenReturn(EnvironmentEdgeManager.currentTime());
199 
200     master.getSnapshotManager()
201         .setSnapshotHandlerForTesting(TABLE_NAME, mockHandler);
202 
203     // if we do a lookup without a snapshot name, we should fail - you should always know your name
204     builder = IsSnapshotDoneRequest.newBuilder();
205     SnapshotTestingUtils.expectSnapshotDoneException(master, builder.build(),
206       UnknownSnapshotException.class);
207 
208     // then do the lookup for the snapshot that it is done
209     builder.setSnapshot(desc);
210     IsSnapshotDoneResponse response =
211       master.getMasterRpcServices().isSnapshotDone(null, builder.build());
212     assertTrue("Snapshot didn't complete when it should have.", response.getDone());
213 
214     // now try the case where we are looking for a snapshot we didn't take
215     builder.setSnapshot(SnapshotDescription.newBuilder().setName("Not A Snapshot").build());
216     SnapshotTestingUtils.expectSnapshotDoneException(master, builder.build(),
217       UnknownSnapshotException.class);
218 
219     // then create a snapshot to the fs and make sure that we can find it when checking done
220     snapshotName = "completed";
221     Path snapshotDir = SnapshotDescriptionUtils.getCompletedSnapshotDir(snapshotName, rootDir);
222     desc = desc.toBuilder().setName(snapshotName).build();
223     SnapshotDescriptionUtils.writeSnapshotInfo(desc, snapshotDir, fs);
224 
225     builder.setSnapshot(desc);
226     response = master.getMasterRpcServices().isSnapshotDone(null, builder.build());
227     assertTrue("Completed, on-disk snapshot not found", response.getDone());
228   }
229 
230   @Test(timeout = 300000)
231   public void testGetCompletedSnapshots() throws Exception {
232     // first check when there are no snapshots
233     GetCompletedSnapshotsRequest request = GetCompletedSnapshotsRequest.newBuilder().build();
234     GetCompletedSnapshotsResponse response =
235       master.getMasterRpcServices().getCompletedSnapshots(null, request);
236     assertEquals("Found unexpected number of snapshots", 0, response.getSnapshotsCount());
237 
238     // write one snapshot to the fs
239     String snapshotName = "completed";
240     Path snapshotDir = SnapshotDescriptionUtils.getCompletedSnapshotDir(snapshotName, rootDir);
241     SnapshotDescription snapshot = SnapshotDescription.newBuilder().setName(snapshotName).build();
242     SnapshotDescriptionUtils.writeSnapshotInfo(snapshot, snapshotDir, fs);
243 
244     // check that we get one snapshot
245     response = master.getMasterRpcServices().getCompletedSnapshots(null, request);
246     assertEquals("Found unexpected number of snapshots", 1, response.getSnapshotsCount());
247     List<SnapshotDescription> snapshots = response.getSnapshotsList();
248     List<SnapshotDescription> expected = Lists.newArrayList(snapshot);
249     assertEquals("Returned snapshots don't match created snapshots", expected, snapshots);
250 
251     // write a second snapshot
252     snapshotName = "completed_two";
253     snapshotDir = SnapshotDescriptionUtils.getCompletedSnapshotDir(snapshotName, rootDir);
254     snapshot = SnapshotDescription.newBuilder().setName(snapshotName).build();
255     SnapshotDescriptionUtils.writeSnapshotInfo(snapshot, snapshotDir, fs);
256     expected.add(snapshot);
257 
258     // check that we get one snapshot
259     response = master.getMasterRpcServices().getCompletedSnapshots(null, request);
260     assertEquals("Found unexpected number of snapshots", 2, response.getSnapshotsCount());
261     snapshots = response.getSnapshotsList();
262     assertEquals("Returned snapshots don't match created snapshots", expected, snapshots);
263   }
264 
265   @Test(timeout = 300000)
266   public void testDeleteSnapshot() throws Exception {
267 
268     String snapshotName = "completed";
269     SnapshotDescription snapshot = SnapshotDescription.newBuilder().setName(snapshotName).build();
270 
271     DeleteSnapshotRequest request = DeleteSnapshotRequest.newBuilder().setSnapshot(snapshot)
272         .build();
273     try {
274       master.getMasterRpcServices().deleteSnapshot(null, request);
275       fail("Master didn't throw exception when attempting to delete snapshot that doesn't exist");
276     } catch (ServiceException e) {
277       LOG.debug("Correctly failed delete of non-existant snapshot:" + e.getMessage());
278     }
279 
280     // write one snapshot to the fs
281     Path snapshotDir = SnapshotDescriptionUtils.getCompletedSnapshotDir(snapshotName, rootDir);
282     SnapshotDescriptionUtils.writeSnapshotInfo(snapshot, snapshotDir, fs);
283 
284     // then delete the existing snapshot,which shouldn't cause an exception to be thrown
285     master.getMasterRpcServices().deleteSnapshot(null, request);
286   }
287 
288   @Test
289   public void testGetCompletedSnapshotsWithCleanup() throws Exception {
290     // Enable auto snapshot cleanup for the cluster
291     SetSnapshotCleanupRequest setSnapshotCleanupRequest =
292         SetSnapshotCleanupRequest.newBuilder().setEnabled(true).build();
293     master.getMasterRpcServices().switchSnapshotCleanup(null, setSnapshotCleanupRequest);
294 
295     // first check when there are no snapshots
296     GetCompletedSnapshotsRequest request = GetCompletedSnapshotsRequest.newBuilder().build();
297     GetCompletedSnapshotsResponse response =
298         master.getMasterRpcServices().getCompletedSnapshots(null, request);
299     assertEquals("Found unexpected number of snapshots", 0, response.getSnapshotsCount());
300 
301     // write one snapshot to the fs
302     createSnapshotWithTtl("snapshot_01", 1L);
303     createSnapshotWithTtl("snapshot_02", 10L);
304 
305     // check that we get one snapshot
306     response = master.getMasterRpcServices().getCompletedSnapshots(null, request);
307     assertEquals("Found unexpected number of snapshots", 2, response.getSnapshotsCount());
308 
309     // check that 1 snapshot is auto cleaned after 1 sec of TTL expiration
310     Uninterruptibles.sleepUninterruptibly(2, TimeUnit.SECONDS);
311     response = master.getMasterRpcServices().getCompletedSnapshots(null, request);
312     assertEquals("Found unexpected number of snapshots", 1, response.getSnapshotsCount());
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("Found unexpected number of snapshots", 0, response.getSnapshotsCount());
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("Found unexpected number of snapshots", 2, response.getSnapshotsCount());
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("Found unexpected number of snapshots", 2, response.getSnapshotsCount());
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,
354             isSnapshotCleanupEnabledRequest);
355     Assert.assertTrue(isSnapshotCleanupEnabledResponse.getEnabled());
356 
357     // Disable auto snapshot cleanup for the cluster
358     setSnapshotCleanupRequest = SetSnapshotCleanupRequest.newBuilder()
359         .setEnabled(false).build();
360     master.getMasterRpcServices().switchSnapshotCleanup(null, setSnapshotCleanupRequest);
361 
362     // Check if auto snapshot cleanup is disabled
363     isSnapshotCleanupEnabledRequest = IsSnapshotCleanupEnabledRequest
364         .newBuilder().build();
365     isSnapshotCleanupEnabledResponse =
366         master.getMasterRpcServices().isSnapshotCleanupEnabled(null,
367             isSnapshotCleanupEnabledRequest);
368     assertFalse(isSnapshotCleanupEnabledResponse.getEnabled());
369   }
370 
371   /**
372    * Test that the snapshot hfile archive cleaner works correctly. HFiles that are in snapshots
373    * should be retained, while those that are not in a snapshot should be deleted.
374    * @throws Exception on failure
375    */
376   @Test(timeout = 300000)
377   public void testSnapshotHFileArchiving() throws Exception {
378     Admin admin = UTIL.getHBaseAdmin();
379     // make sure we don't fail on listing snapshots
380     SnapshotTestingUtils.assertNoSnapshots(admin);
381 
382     // recreate test table with disabled compactions; otherwise compaction may happen before
383     // snapshot, the call after snapshot will be a no-op and checks will fail
384     UTIL.deleteTable(TABLE_NAME);
385     HTableDescriptor htd = new HTableDescriptor(TABLE_NAME);
386     htd.setCompactionEnabled(false);
387 
388     UTIL.createTable(htd, new byte[][] { TEST_FAM }, null);
389 
390     // load the table
391     for (int i = 0; i < blockingStoreFiles / 2; i ++) {
392       UTIL.loadTable(UTIL.getConnection().getTable(TABLE_NAME), TEST_FAM);
393       UTIL.flush(TABLE_NAME);
394     }
395 
396     // disable the table so we can take a snapshot
397     admin.disableTable(TABLE_NAME);
398     htd.setCompactionEnabled(true);
399 
400     // take a snapshot of the table
401     String snapshotName = "snapshot";
402     byte[] snapshotNameBytes = Bytes.toBytes(snapshotName);
403     admin.snapshot(snapshotNameBytes, TABLE_NAME);
404 
405     LOG.info("After snapshot File-System state");
406     FSUtils.logFileSystemState(fs, rootDir, LOG);
407 
408     // ensure we only have one snapshot
409     SnapshotTestingUtils.assertOneSnapshotThatMatches(admin, snapshotNameBytes, TABLE_NAME);
410 
411     // enable compactions now
412     admin.modifyTable(TABLE_NAME, htd);
413 
414     // renable the table so we can compact the regions
415     admin.enableTable(TABLE_NAME);
416 
417     // compact the files so we get some archived files for the table we just snapshotted
418     List<HRegion> regions = UTIL.getHBaseCluster().getRegions(TABLE_NAME);
419     for (HRegion region : regions) {
420       region.waitForFlushesAndCompactions(); // enable can trigger a compaction, wait for it.
421       region.compactStores(); // min is 2 so will compact and archive
422     }
423     List<RegionServerThread> regionServerThreads = UTIL.getMiniHBaseCluster()
424         .getRegionServerThreads();
425     HRegionServer hrs = null;
426     for (RegionServerThread rs : regionServerThreads) {
427       if (!rs.getRegionServer().getOnlineRegions(TABLE_NAME).isEmpty()) {
428         hrs = rs.getRegionServer();
429         break;
430       }
431     }
432     CompactedHFilesDischarger cleaner = new CompactedHFilesDischarger(100, null, hrs, false);
433     cleaner.chore();
434     LOG.info("After compaction File-System state");
435     FSUtils.logFileSystemState(fs, rootDir, LOG);
436 
437     // make sure the cleaner has run
438     LOG.debug("Running hfile cleaners");
439     ensureHFileCleanersRun();
440     LOG.info("After cleaners File-System state: " + rootDir);
441     FSUtils.logFileSystemState(fs, rootDir, LOG);
442 
443     // get the snapshot files for the table
444     Path snapshotTable = SnapshotDescriptionUtils.getCompletedSnapshotDir(snapshotName, rootDir);
445     Set<String> snapshotHFiles = SnapshotReferenceUtil.getHFileNames(
446         UTIL.getConfiguration(), fs, snapshotTable);
447     // check that the files in the archive contain the ones that we need for the snapshot
448     LOG.debug("Have snapshot hfiles:");
449     for (String fileName : snapshotHFiles) {
450       LOG.debug(fileName);
451     }
452     // get the archived files for the table
453     Collection<String> archives = getHFiles(archiveDir, fs, TABLE_NAME);
454 
455     // get the hfiles for the table
456     Collection<String> hfiles = getHFiles(rootDir, fs, TABLE_NAME);
457 
458     // and make sure that there is a proper subset
459     for (String fileName : snapshotHFiles) {
460       boolean exist = archives.contains(fileName) || hfiles.contains(fileName);
461       assertTrue("Archived hfiles " + archives
462         + " and table hfiles " + hfiles + " is missing snapshot file:" + fileName, exist);
463     }
464 
465     // delete the existing snapshot
466     admin.deleteSnapshot(snapshotNameBytes);
467     SnapshotTestingUtils.assertNoSnapshots(admin);
468 
469     // make sure that we don't keep around the hfiles that aren't in a snapshot
470     // make sure we wait long enough to refresh the snapshot hfile
471     List<BaseHFileCleanerDelegate> delegates = UTIL.getMiniHBaseCluster().getMaster()
472         .getHFileCleaner().cleanersChain;
473     for (BaseHFileCleanerDelegate delegate: delegates) {
474       if (delegate instanceof SnapshotHFileCleaner) {
475         ((SnapshotHFileCleaner)delegate).getFileCacheForTesting().triggerCacheRefreshForTesting();
476       }
477     }
478     // run the cleaner again
479     LOG.debug("Running hfile cleaners");
480     ensureHFileCleanersRun();
481     LOG.info("After delete snapshot cleaners run File-System state");
482     FSUtils.logFileSystemState(fs, rootDir, LOG);
483 
484     archives = getHFiles(archiveDir, fs, TABLE_NAME);
485     assertEquals("Still have some hfiles in the archive, when their snapshot has been deleted.", 0,
486       archives.size());
487   }
488 
489   /**
490    * @return all the HFiles for a given table in the specified dir
491    * @throws IOException on expected failure
492    */
493   private final Collection<String> getHFiles(Path dir, FileSystem fs, TableName tableName) throws IOException {
494     Path tableDir = FSUtils.getTableDir(dir, tableName);
495     return SnapshotTestingUtils.listHFileNames(fs, tableDir);
496   }
497 
498   /**
499    * Make sure the {@link HFileCleaner HFileCleaners} run at least once
500    */
501   private static void ensureHFileCleanersRun() {
502     UTIL.getHBaseCluster().getMaster().getHFileCleaner().chore();
503   }
504 
505   private SnapshotDescription createSnapshotWithTtl(final String snapshotName, final long ttl)
506       throws IOException {
507     SnapshotTestingUtils.SnapshotMock snapshotMock =
508         new SnapshotTestingUtils.SnapshotMock(UTIL.getConfiguration(), fs, rootDir);
509     SnapshotTestingUtils.SnapshotMock.SnapshotBuilder builder =
510         snapshotMock.createSnapshotV2(snapshotName, "test", 0, ttl);
511     builder.commit();
512     return builder.getSnapshotDescription();
513   }
514 
515   @Test
516   public void testAsyncSnapshotWillNotBlockSnapshotHFileCleaner() throws Exception {
517     // Write some data
518     Table table = UTIL.getConnection().getTable(TABLE_NAME);
519     for (int i = 0; i < 10; i++) {
520       Put put = new Put(Bytes.toBytes(i)).addColumn(TEST_FAM, Bytes.toBytes("q"), Bytes.toBytes(i));
521       table.put(put);
522     }
523     final String snapshotName = "testAsyncSnapshotWillNotBlockSnapshotHFileCleaner01";
524     SnapshotDescription snapshot = SnapshotDescription.newBuilder().setName(snapshotName)
525         .setTable(TABLE_NAME.getNameAsString()).setType(SnapshotDescription.Type.FLUSH).build();
526     UTIL.getHBaseAdmin().takeSnapshotAsync(snapshot);
527     UTIL.waitFor(10 * 1000L, 200L, new Predicate<Exception>() {
528       @Override
529       public boolean evaluate() throws Exception {
530         return UTIL.getHBaseAdmin().listSnapshots(Pattern.compile(snapshotName)).size() == 1;
531       }
532     });
533     UTIL.waitFor(30000, new Predicate<Exception>() {
534       @Override
535       public boolean evaluate() throws Exception {
536         return !master.getSnapshotManager().isTakingAnySnapshot();
537       }
538     });
539   }
540 }