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}