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.backup; 019 020import static org.junit.Assert.assertEquals; 021import static org.junit.Assert.assertFalse; 022import static org.junit.Assert.assertTrue; 023import static org.mockito.ArgumentMatchers.any; 024import static org.mockito.Mockito.mock; 025import static org.mockito.Mockito.times; 026import static org.mockito.Mockito.verify; 027import static org.mockito.Mockito.when; 028 029import java.io.IOException; 030import java.security.PrivilegedExceptionAction; 031import java.util.ArrayList; 032import java.util.Collection; 033import java.util.Collections; 034import java.util.List; 035import java.util.stream.Collectors; 036import org.apache.hadoop.conf.Configuration; 037import org.apache.hadoop.fs.FileStatus; 038import org.apache.hadoop.fs.FileSystem; 039import org.apache.hadoop.fs.Path; 040import org.apache.hadoop.fs.PathFilter; 041import org.apache.hadoop.hbase.ChoreService; 042import org.apache.hadoop.hbase.HBaseClassTestRule; 043import org.apache.hadoop.hbase.HBaseTestingUtility; 044import org.apache.hadoop.hbase.HConstants; 045import org.apache.hadoop.hbase.Stoppable; 046import org.apache.hadoop.hbase.TableName; 047import org.apache.hadoop.hbase.client.Admin; 048import org.apache.hadoop.hbase.client.RegionInfo; 049import org.apache.hadoop.hbase.client.Table; 050import org.apache.hadoop.hbase.master.cleaner.DirScanPool; 051import org.apache.hadoop.hbase.master.cleaner.HFileCleaner; 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.regionserver.HStoreFile; 056import org.apache.hadoop.hbase.testclassification.LargeTests; 057import org.apache.hadoop.hbase.testclassification.MiscTests; 058import org.apache.hadoop.hbase.util.Bytes; 059import org.apache.hadoop.hbase.util.CommonFSUtils; 060import org.apache.hadoop.hbase.util.FSUtils; 061import org.apache.hadoop.hbase.util.HFileArchiveTestingUtil; 062import org.apache.hadoop.hbase.util.HFileArchiveUtil; 063import org.apache.hadoop.hbase.util.StoppableImplementation; 064import org.apache.hadoop.security.UserGroupInformation; 065import org.junit.After; 066import org.junit.AfterClass; 067import org.junit.Assert; 068import org.junit.BeforeClass; 069import org.junit.ClassRule; 070import org.junit.Rule; 071import org.junit.Test; 072import org.junit.experimental.categories.Category; 073import org.junit.rules.TestName; 074import org.mockito.ArgumentCaptor; 075import org.slf4j.Logger; 076import org.slf4j.LoggerFactory; 077 078/** 079 * Test that the {@link HFileArchiver} correctly removes all the parts of a region when cleaning up 080 * a region 081 */ 082@Category({LargeTests.class, MiscTests.class}) 083public class TestHFileArchiving { 084 085 @ClassRule 086 public static final HBaseClassTestRule CLASS_RULE = 087 HBaseClassTestRule.forClass(TestHFileArchiving.class); 088 089 private static final Logger LOG = LoggerFactory.getLogger(TestHFileArchiving.class); 090 private static final HBaseTestingUtility UTIL = new HBaseTestingUtility(); 091 private static final byte[] TEST_FAM = Bytes.toBytes("fam"); 092 093 private static DirScanPool POOL; 094 @Rule 095 public TestName name = new TestName(); 096 097 /** 098 * Setup the config for the cluster 099 */ 100 @BeforeClass 101 public static void setupCluster() throws Exception { 102 setupConf(UTIL.getConfiguration()); 103 UTIL.startMiniCluster(); 104 105 // We don't want the cleaner to remove files. The tests do that. 106 UTIL.getMiniHBaseCluster().getMaster().getHFileCleaner().cancel(true); 107 108 POOL = new DirScanPool(UTIL.getConfiguration()); 109 } 110 111 private static void setupConf(Configuration conf) { 112 // disable the ui 113 conf.setInt("hbase.regionsever.info.port", -1); 114 // drop the memstore size so we get flushes 115 conf.setInt("hbase.hregion.memstore.flush.size", 25000); 116 // disable major compactions 117 conf.setInt(HConstants.MAJOR_COMPACTION_PERIOD, 0); 118 119 // prevent aggressive region split 120 conf.set(HConstants.HBASE_REGION_SPLIT_POLICY_KEY, 121 ConstantSizeRegionSplitPolicy.class.getName()); 122 } 123 124 @After 125 public void tearDown() throws Exception { 126 // cleanup the archive directory 127 clearArchiveDirectory(); 128 } 129 130 @AfterClass 131 public static void cleanupTest() throws Exception { 132 UTIL.shutdownMiniCluster(); 133 POOL.shutdownNow(); 134 } 135 136 @Test 137 public void testArchiveStoreFilesDifferentFileSystemsWallWithSchemaPlainRoot() throws Exception { 138 String walDir = "mockFS://mockFSAuthority:9876/mockDir/wals/"; 139 String baseDir = CommonFSUtils.getRootDir(UTIL.getConfiguration()).toString() + "/"; 140 testArchiveStoreFilesDifferentFileSystems(walDir, baseDir, 141 HFileArchiver::archiveStoreFiles); 142 } 143 144 @Test 145 public void testArchiveStoreFilesDifferentFileSystemsWallNullPlainRoot() throws Exception { 146 String baseDir = CommonFSUtils.getRootDir(UTIL.getConfiguration()).toString() + "/"; 147 testArchiveStoreFilesDifferentFileSystems(null, baseDir, 148 HFileArchiver::archiveStoreFiles); 149 } 150 151 @Test 152 public void testArchiveStoreFilesDifferentFileSystemsWallAndRootSame() throws Exception { 153 String baseDir = CommonFSUtils.getRootDir(UTIL.getConfiguration()).toString() + "/"; 154 testArchiveStoreFilesDifferentFileSystems("/hbase/wals/", baseDir, 155 HFileArchiver::archiveStoreFiles); 156 } 157 158 private void testArchiveStoreFilesDifferentFileSystems(String walDir, String expectedBase, 159 ArchivingFunction<Configuration, FileSystem, RegionInfo, Path, byte[], 160 Collection<HStoreFile>> archivingFunction) throws IOException { 161 FileSystem mockedFileSystem = mock(FileSystem.class); 162 Configuration conf = new Configuration(UTIL.getConfiguration()); 163 if(walDir != null) { 164 conf.set(CommonFSUtils.HBASE_WAL_DIR, walDir); 165 } 166 Path filePath = new Path("/mockDir/wals/mockFile"); 167 when(mockedFileSystem.getScheme()).thenReturn("mockFS"); 168 when(mockedFileSystem.mkdirs(any())).thenReturn(true); 169 when(mockedFileSystem.exists(any())).thenReturn(true); 170 RegionInfo mockedRegion = mock(RegionInfo.class); 171 TableName tableName = TableName.valueOf("mockTable"); 172 when(mockedRegion.getTable()).thenReturn(tableName); 173 when(mockedRegion.getEncodedName()).thenReturn("mocked-region-encoded-name"); 174 Path tableDir = new Path("mockFS://mockDir/tabledir"); 175 byte[] family = Bytes.toBytes("testfamily"); 176 HStoreFile mockedFile = mock(HStoreFile.class); 177 List<HStoreFile> list = new ArrayList<>(); 178 list.add(mockedFile); 179 when(mockedFile.getPath()).thenReturn(filePath); 180 when(mockedFileSystem.rename(any(),any())).thenReturn(true); 181 archivingFunction.apply(conf, mockedFileSystem, mockedRegion, tableDir, family, list); 182 ArgumentCaptor<Path> pathCaptor = ArgumentCaptor.forClass(Path.class); 183 verify(mockedFileSystem, times(2)).rename(pathCaptor.capture(), any()); 184 String expectedDir = expectedBase + 185 "archive/data/default/mockTable/mocked-region-encoded-name/testfamily/mockFile"; 186 assertTrue(pathCaptor.getAllValues().get(0).toString().equals(expectedDir)); 187 } 188 189 @FunctionalInterface 190 private interface ArchivingFunction<Configuration, FS, Region, Dir, Family, Files> { 191 void apply(Configuration config, FS fs, Region region, Dir dir, Family family, Files files) 192 throws IOException; 193 } 194 195 @Test 196 public void testArchiveRecoveredEditsWalDirNull() throws Exception { 197 testArchiveRecoveredEditsWalDirNullOrSame(null); 198 } 199 200 @Test 201 public void testArchiveRecoveredEditsWalDirSameFsStoreFiles() throws Exception { 202 testArchiveRecoveredEditsWalDirNullOrSame("/wal-dir"); 203 } 204 205 private void testArchiveRecoveredEditsWalDirNullOrSame(String walDir) throws Exception { 206 String originalRootDir = UTIL.getConfiguration().get(HConstants.HBASE_DIR); 207 try { 208 String baseDir = "mockFS://mockFSAuthority:9876/hbase/"; 209 UTIL.getConfiguration().set(HConstants.HBASE_DIR, baseDir); 210 testArchiveStoreFilesDifferentFileSystems(walDir, baseDir, 211 (conf, fs, region, dir, family, list) -> HFileArchiver 212 .archiveRecoveredEdits(conf, fs, region, family, list)); 213 } finally { 214 UTIL.getConfiguration().set(HConstants.HBASE_DIR, originalRootDir); 215 } 216 } 217 218 @Test(expected = IOException.class) 219 public void testArchiveRecoveredEditsWrongFS() throws Exception { 220 String baseDir = CommonFSUtils.getRootDir(UTIL.getConfiguration()).toString() + "/"; 221 //Internally, testArchiveStoreFilesDifferentFileSystems will pass a "mockedFS" 222 // to HFileArchiver.archiveRecoveredEdits, but since wal-dir is supposedly on same FS 223 // as root dir it would lead to conflicting FSes and an IOException is expected. 224 testArchiveStoreFilesDifferentFileSystems("/wal-dir", baseDir, 225 (conf, fs, region, dir, family, list) -> HFileArchiver 226 .archiveRecoveredEdits(conf, fs, region, family, list)); 227 } 228 229 @Test 230 public void testArchiveRecoveredEditsWalDirDifferentFS() throws Exception { 231 String walDir = "mockFS://mockFSAuthority:9876/mockDir/wals/"; 232 testArchiveStoreFilesDifferentFileSystems(walDir, walDir, 233 (conf, fs, region, dir, family, list) -> 234 HFileArchiver.archiveRecoveredEdits(conf, fs, region, family, list)); 235 } 236 237 @Test 238 public void testRemoveRegionDirOnArchive() throws Exception { 239 final TableName tableName = TableName.valueOf(name.getMethodName()); 240 UTIL.createTable(tableName, TEST_FAM); 241 242 final Admin admin = UTIL.getAdmin(); 243 244 // get the current store files for the region 245 List<HRegion> servingRegions = UTIL.getHBaseCluster().getRegions(tableName); 246 // make sure we only have 1 region serving this table 247 assertEquals(1, servingRegions.size()); 248 HRegion region = servingRegions.get(0); 249 250 // and load the table 251 UTIL.loadRegion(region, TEST_FAM); 252 253 // shutdown the table so we can manipulate the files 254 admin.disableTable(tableName); 255 256 FileSystem fs = UTIL.getTestFileSystem(); 257 258 // now attempt to depose the region 259 Path rootDir = region.getRegionFileSystem().getTableDir().getParent(); 260 Path regionDir = FSUtils.getRegionDirFromRootDir(rootDir, region.getRegionInfo()); 261 262 HFileArchiver.archiveRegion(UTIL.getConfiguration(), fs, region.getRegionInfo()); 263 264 // check for the existence of the archive directory and some files in it 265 Path archiveDir = HFileArchiveTestingUtil.getRegionArchiveDir(UTIL.getConfiguration(), region); 266 assertTrue(fs.exists(archiveDir)); 267 268 // check to make sure the store directory was copied 269 FileStatus[] stores = fs.listStatus(archiveDir, new PathFilter() { 270 @Override 271 public boolean accept(Path p) { 272 if (p.getName().contains(HConstants.RECOVERED_EDITS_DIR)) { 273 return false; 274 } 275 return true; 276 } 277 }); 278 assertTrue(stores.length == 1); 279 280 // make sure we archived the store files 281 FileStatus[] storeFiles = fs.listStatus(stores[0].getPath()); 282 assertTrue(storeFiles.length > 0); 283 284 // then ensure the region's directory isn't present 285 assertFalse(fs.exists(regionDir)); 286 287 UTIL.deleteTable(tableName); 288 } 289 290 /** 291 * Test that the region directory is removed when we archive a region without store files, but 292 * still has hidden files. 293 * @throws Exception 294 */ 295 @Test 296 public void testDeleteRegionWithNoStoreFiles() throws Exception { 297 final TableName tableName = TableName.valueOf(name.getMethodName()); 298 UTIL.createTable(tableName, TEST_FAM); 299 300 // get the current store files for the region 301 List<HRegion> servingRegions = UTIL.getHBaseCluster().getRegions(tableName); 302 // make sure we only have 1 region serving this table 303 assertEquals(1, servingRegions.size()); 304 HRegion region = servingRegions.get(0); 305 306 FileSystem fs = region.getRegionFileSystem().getFileSystem(); 307 308 // make sure there are some files in the regiondir 309 Path rootDir = CommonFSUtils.getRootDir(fs.getConf()); 310 Path regionDir = FSUtils.getRegionDirFromRootDir(rootDir, region.getRegionInfo()); 311 FileStatus[] regionFiles = CommonFSUtils.listStatus(fs, regionDir, null); 312 Assert.assertNotNull("No files in the region directory", regionFiles); 313 if (LOG.isDebugEnabled()) { 314 List<Path> files = new ArrayList<>(); 315 for (FileStatus file : regionFiles) { 316 files.add(file.getPath()); 317 } 318 LOG.debug("Current files:" + files); 319 } 320 // delete the visible folders so we just have hidden files/folders 321 final PathFilter dirFilter = new FSUtils.DirFilter(fs); 322 PathFilter nonHidden = new PathFilter() { 323 @Override 324 public boolean accept(Path file) { 325 return dirFilter.accept(file) && !file.getName().toString().startsWith("."); 326 } 327 }; 328 FileStatus[] storeDirs = CommonFSUtils.listStatus(fs, regionDir, nonHidden); 329 for (FileStatus store : storeDirs) { 330 LOG.debug("Deleting store for test"); 331 fs.delete(store.getPath(), true); 332 } 333 334 // then archive the region 335 HFileArchiver.archiveRegion(UTIL.getConfiguration(), fs, region.getRegionInfo()); 336 337 // and check to make sure the region directoy got deleted 338 assertFalse("Region directory (" + regionDir + "), still exists.", fs.exists(regionDir)); 339 340 UTIL.deleteTable(tableName); 341 } 342 343 private List<HRegion> initTableForArchivingRegions(TableName tableName) throws IOException { 344 final byte[][] splitKeys = new byte[][] { 345 Bytes.toBytes("b"), Bytes.toBytes("c"), Bytes.toBytes("d") 346 }; 347 348 UTIL.createTable(tableName, TEST_FAM, splitKeys); 349 350 // get the current store files for the regions 351 List<HRegion> regions = UTIL.getHBaseCluster().getRegions(tableName); 352 // make sure we have 4 regions serving this table 353 assertEquals(4, regions.size()); 354 355 // and load the table 356 try (Table table = UTIL.getConnection().getTable(tableName)) { 357 UTIL.loadTable(table, TEST_FAM); 358 } 359 360 // disable the table so that we can manipulate the files 361 UTIL.getAdmin().disableTable(tableName); 362 363 return regions; 364 } 365 366 @Test 367 public void testArchiveRegions() throws Exception { 368 final TableName tableName = TableName.valueOf(name.getMethodName()); 369 List<HRegion> regions = initTableForArchivingRegions(tableName); 370 371 FileSystem fs = UTIL.getTestFileSystem(); 372 373 // now attempt to depose the regions 374 Path rootDir = CommonFSUtils.getRootDir(UTIL.getConfiguration()); 375 Path tableDir = CommonFSUtils.getTableDir(rootDir, regions.get(0).getRegionInfo().getTable()); 376 List<Path> regionDirList = regions.stream() 377 .map(region -> FSUtils.getRegionDirFromTableDir(tableDir, region.getRegionInfo())) 378 .collect(Collectors.toList()); 379 380 HFileArchiver.archiveRegions(UTIL.getConfiguration(), fs, rootDir, tableDir, regionDirList); 381 382 // check for the existence of the archive directory and some files in it 383 for (HRegion region : regions) { 384 Path archiveDir = HFileArchiveTestingUtil.getRegionArchiveDir(UTIL.getConfiguration(), 385 region); 386 assertTrue(fs.exists(archiveDir)); 387 388 // check to make sure the store directory was copied 389 FileStatus[] stores = fs.listStatus(archiveDir, 390 p -> !p.getName().contains(HConstants.RECOVERED_EDITS_DIR)); 391 assertTrue(stores.length == 1); 392 393 // make sure we archived the store files 394 FileStatus[] storeFiles = fs.listStatus(stores[0].getPath()); 395 assertTrue(storeFiles.length > 0); 396 } 397 398 // then ensure the region's directories aren't present 399 for (Path regionDir: regionDirList) { 400 assertFalse(fs.exists(regionDir)); 401 } 402 403 UTIL.deleteTable(tableName); 404 } 405 406 @Test(expected=IOException.class) 407 public void testArchiveRegionsWhenPermissionDenied() throws Exception { 408 final TableName tableName = TableName.valueOf(name.getMethodName()); 409 List<HRegion> regions = initTableForArchivingRegions(tableName); 410 411 // now attempt to depose the regions 412 Path rootDir = CommonFSUtils.getRootDir(UTIL.getConfiguration()); 413 Path tableDir = CommonFSUtils.getTableDir(rootDir, regions.get(0).getRegionInfo().getTable()); 414 List<Path> regionDirList = regions.stream() 415 .map(region -> FSUtils.getRegionDirFromTableDir(tableDir, region.getRegionInfo())) 416 .collect(Collectors.toList()); 417 418 // To create a permission denied error, we do archive regions as a non-current user 419 UserGroupInformation 420 ugi = UserGroupInformation.createUserForTesting("foo1234", new String[]{"group1"}); 421 422 try { 423 ugi.doAs((PrivilegedExceptionAction<Void>) () -> { 424 FileSystem fs = UTIL.getTestFileSystem(); 425 HFileArchiver.archiveRegions(UTIL.getConfiguration(), fs, rootDir, tableDir, 426 regionDirList); 427 return null; 428 }); 429 } catch (IOException e) { 430 assertTrue(e.getCause().getMessage().contains("Permission denied")); 431 throw e; 432 } finally { 433 UTIL.deleteTable(tableName); 434 } 435 } 436 437 @Test 438 public void testArchiveOnTableDelete() throws Exception { 439 final TableName tableName = TableName.valueOf(name.getMethodName()); 440 UTIL.createTable(tableName, TEST_FAM); 441 442 List<HRegion> servingRegions = UTIL.getHBaseCluster().getRegions(tableName); 443 // make sure we only have 1 region serving this table 444 assertEquals(1, servingRegions.size()); 445 HRegion region = servingRegions.get(0); 446 447 // get the parent RS and monitor 448 HRegionServer hrs = UTIL.getRSForFirstRegionInTable(tableName); 449 FileSystem fs = hrs.getFileSystem(); 450 451 // put some data on the region 452 LOG.debug("-------Loading table"); 453 UTIL.loadRegion(region, TEST_FAM); 454 455 // get the hfiles in the region 456 List<HRegion> regions = hrs.getRegions(tableName); 457 assertEquals("More that 1 region for test table.", 1, regions.size()); 458 459 region = regions.get(0); 460 // wait for all the compactions to complete 461 region.waitForFlushesAndCompactions(); 462 463 // disable table to prevent new updates 464 UTIL.getAdmin().disableTable(tableName); 465 LOG.debug("Disabled table"); 466 467 // remove all the files from the archive to get a fair comparison 468 clearArchiveDirectory(); 469 470 // then get the current store files 471 byte[][]columns = region.getTableDescriptor().getColumnFamilyNames().toArray(new byte[0][]); 472 List<String> storeFiles = region.getStoreFileList(columns); 473 474 // then delete the table so the hfiles get archived 475 UTIL.deleteTable(tableName); 476 LOG.debug("Deleted table"); 477 478 assertArchiveFiles(fs, storeFiles, 30000); 479 } 480 481 private void assertArchiveFiles(FileSystem fs, List<String> storeFiles, long timeout) throws IOException { 482 long end = System.currentTimeMillis() + timeout; 483 Path archiveDir = HFileArchiveUtil.getArchivePath(UTIL.getConfiguration()); 484 List<String> archivedFiles = new ArrayList<>(); 485 486 // We have to ensure that the DeleteTableHandler is finished. HBaseAdmin.deleteXXX() can return before all files 487 // are archived. We should fix HBASE-5487 and fix synchronous operations from admin. 488 while (System.currentTimeMillis() < end) { 489 archivedFiles = getAllFileNames(fs, archiveDir); 490 if (archivedFiles.size() >= storeFiles.size()) { 491 break; 492 } 493 } 494 495 Collections.sort(storeFiles); 496 Collections.sort(archivedFiles); 497 498 LOG.debug("Store files:"); 499 for (int i = 0; i < storeFiles.size(); i++) { 500 LOG.debug(i + " - " + storeFiles.get(i)); 501 } 502 LOG.debug("Archive files:"); 503 for (int i = 0; i < archivedFiles.size(); i++) { 504 LOG.debug(i + " - " + archivedFiles.get(i)); 505 } 506 507 assertTrue("Archived files are missing some of the store files!", 508 archivedFiles.containsAll(storeFiles)); 509 } 510 511 512 /** 513 * Test that the store files are archived when a column family is removed. 514 * @throws Exception 515 */ 516 @Test 517 public void testArchiveOnTableFamilyDelete() throws Exception { 518 final TableName tableName = TableName.valueOf(name.getMethodName()); 519 UTIL.createTable(tableName, new byte[][] {TEST_FAM, Bytes.toBytes("fam2")}); 520 521 List<HRegion> servingRegions = UTIL.getHBaseCluster().getRegions(tableName); 522 // make sure we only have 1 region serving this table 523 assertEquals(1, servingRegions.size()); 524 HRegion region = servingRegions.get(0); 525 526 // get the parent RS and monitor 527 HRegionServer hrs = UTIL.getRSForFirstRegionInTable(tableName); 528 FileSystem fs = hrs.getFileSystem(); 529 530 // put some data on the region 531 LOG.debug("-------Loading table"); 532 UTIL.loadRegion(region, TEST_FAM); 533 534 // get the hfiles in the region 535 List<HRegion> regions = hrs.getRegions(tableName); 536 assertEquals("More that 1 region for test table.", 1, regions.size()); 537 538 region = regions.get(0); 539 // wait for all the compactions to complete 540 region.waitForFlushesAndCompactions(); 541 542 // disable table to prevent new updates 543 UTIL.getAdmin().disableTable(tableName); 544 LOG.debug("Disabled table"); 545 546 // remove all the files from the archive to get a fair comparison 547 clearArchiveDirectory(); 548 549 // then get the current store files 550 byte[][]columns = region.getTableDescriptor().getColumnFamilyNames().toArray(new byte[0][]); 551 List<String> storeFiles = region.getStoreFileList(columns); 552 553 // then delete the table so the hfiles get archived 554 UTIL.getAdmin().deleteColumnFamily(tableName, TEST_FAM); 555 556 assertArchiveFiles(fs, storeFiles, 30000); 557 558 UTIL.deleteTable(tableName); 559 } 560 561 /** 562 * Test HFileArchiver.resolveAndArchive() race condition HBASE-7643 563 */ 564 @Test 565 public void testCleaningRace() throws Exception { 566 final long TEST_TIME = 20 * 1000; 567 final ChoreService choreService = new ChoreService("TEST_SERVER_NAME"); 568 569 Configuration conf = UTIL.getMiniHBaseCluster().getMaster().getConfiguration(); 570 Path rootDir = UTIL.getDataTestDirOnTestFS("testCleaningRace"); 571 FileSystem fs = UTIL.getTestFileSystem(); 572 573 Path archiveDir = new Path(rootDir, HConstants.HFILE_ARCHIVE_DIRECTORY); 574 Path regionDir = new Path(CommonFSUtils.getTableDir(new Path("./"), 575 TableName.valueOf(name.getMethodName())), "abcdef"); 576 Path familyDir = new Path(regionDir, "cf"); 577 578 Path sourceRegionDir = new Path(rootDir, regionDir); 579 fs.mkdirs(sourceRegionDir); 580 581 Stoppable stoppable = new StoppableImplementation(); 582 583 // The cleaner should be looping without long pauses to reproduce the race condition. 584 HFileCleaner cleaner = new HFileCleaner(1, stoppable, conf, fs, archiveDir, POOL); 585 try { 586 choreService.scheduleChore(cleaner); 587 588 // Keep creating/archiving new files while the cleaner is running in the other thread 589 long startTime = System.currentTimeMillis(); 590 for (long fid = 0; (System.currentTimeMillis() - startTime) < TEST_TIME; ++fid) { 591 Path file = new Path(familyDir, String.valueOf(fid)); 592 Path sourceFile = new Path(rootDir, file); 593 Path archiveFile = new Path(archiveDir, file); 594 595 fs.createNewFile(sourceFile); 596 597 try { 598 // Try to archive the file 599 HFileArchiver.archiveRegion(fs, rootDir, 600 sourceRegionDir.getParent(), sourceRegionDir); 601 602 // The archiver succeded, the file is no longer in the original location 603 // but it's in the archive location. 604 LOG.debug("hfile=" + fid + " should be in the archive"); 605 assertTrue(fs.exists(archiveFile)); 606 assertFalse(fs.exists(sourceFile)); 607 } catch (IOException e) { 608 // The archiver is unable to archive the file. Probably HBASE-7643 race condition. 609 // in this case, the file should not be archived, and we should have the file 610 // in the original location. 611 LOG.debug("hfile=" + fid + " should be in the source location"); 612 assertFalse(fs.exists(archiveFile)); 613 assertTrue(fs.exists(sourceFile)); 614 615 // Avoid to have this file in the next run 616 fs.delete(sourceFile, false); 617 } 618 } 619 } finally { 620 stoppable.stop("test end"); 621 cleaner.cancel(true); 622 choreService.shutdown(); 623 fs.delete(rootDir, true); 624 } 625 } 626 627 @Test 628 public void testArchiveRegionTableAndRegionDirsNull() throws IOException { 629 Path rootDir = UTIL.getDataTestDirOnTestFS("testCleaningRace"); 630 FileSystem fileSystem = UTIL.getTestFileSystem(); 631 // Try to archive the file but with null regionDir, can't delete sourceFile 632 assertFalse(HFileArchiver.archiveRegion(fileSystem, rootDir, null, null)); 633 } 634 635 @Test 636 public void testArchiveRegionWithTableDirNull() throws IOException { 637 Path regionDir = new Path(CommonFSUtils.getTableDir(new Path("./"), 638 TableName.valueOf(name.getMethodName())), "xyzabc"); 639 Path familyDir = new Path(regionDir, "rd"); 640 Path rootDir = UTIL.getDataTestDirOnTestFS("testCleaningRace"); 641 Path file = new Path(familyDir, "1"); 642 Path sourceFile = new Path(rootDir, file); 643 FileSystem fileSystem = UTIL.getTestFileSystem(); 644 fileSystem.createNewFile(sourceFile); 645 Path sourceRegionDir = new Path(rootDir, regionDir); 646 fileSystem.mkdirs(sourceRegionDir); 647 // Try to archive the file 648 assertFalse(HFileArchiver.archiveRegion(fileSystem, rootDir, null, sourceRegionDir)); 649 assertFalse(fileSystem.exists(sourceRegionDir)); 650 } 651 652 @Test 653 public void testArchiveRegionWithRegionDirNull() throws IOException { 654 Path regionDir = new Path(CommonFSUtils.getTableDir(new Path("./"), 655 TableName.valueOf(name.getMethodName())), "elgn4nf"); 656 Path familyDir = new Path(regionDir, "rdar"); 657 Path rootDir = UTIL.getDataTestDirOnTestFS("testCleaningRace"); 658 Path file = new Path(familyDir, "2"); 659 Path sourceFile = new Path(rootDir, file); 660 FileSystem fileSystem = UTIL.getTestFileSystem(); 661 fileSystem.createNewFile(sourceFile); 662 Path sourceRegionDir = new Path(rootDir, regionDir); 663 fileSystem.mkdirs(sourceRegionDir); 664 // Try to archive the file but with null regionDir, can't delete sourceFile 665 assertFalse(HFileArchiver.archiveRegion(fileSystem, rootDir, sourceRegionDir.getParent(), 666 null)); 667 assertTrue(fileSystem.exists(sourceRegionDir)); 668 fileSystem.delete(sourceRegionDir, true); 669 } 670 671 private void clearArchiveDirectory() throws IOException { 672 UTIL.getTestFileSystem().delete( 673 new Path(UTIL.getDefaultRootDirPath(), HConstants.HFILE_ARCHIVE_DIRECTORY), true); 674 } 675 676 /** 677 * Get the names of all the files below the given directory 678 * @param fs the file system to inspect 679 * @param archiveDir the directory in which to look 680 * @return a list of all files in the directory and sub-directories 681 * @throws IOException 682 */ 683 private List<String> getAllFileNames(final FileSystem fs, Path archiveDir) throws IOException { 684 FileStatus[] files = CommonFSUtils.listStatus(fs, archiveDir, new PathFilter() { 685 @Override 686 public boolean accept(Path p) { 687 if (p.getName().contains(HConstants.RECOVERED_EDITS_DIR)) { 688 return false; 689 } 690 return true; 691 } 692 }); 693 return recurseOnFiles(fs, files, new ArrayList<>()); 694 } 695 696 /** Recursively lookup all the file names under the file[] array **/ 697 private List<String> recurseOnFiles(FileSystem fs, FileStatus[] files, List<String> fileNames) 698 throws IOException { 699 if (files == null || files.length == 0) return fileNames; 700 701 for (FileStatus file : files) { 702 if (file.isDirectory()) { 703 recurseOnFiles(fs, CommonFSUtils.listStatus(fs, file.getPath(), null), fileNames); 704 } else { 705 fileNames.add(file.getPath().getName()); 706 } 707 } 708 return fileNames; 709 } 710}