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