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.client; 019 020import static org.apache.hadoop.hbase.client.metrics.ScanMetrics.REGIONS_SCANNED_METRIC_NAME; 021import static org.apache.hadoop.hbase.client.metrics.ServerSideScanMetrics.COUNT_OF_ROWS_SCANNED_KEY_METRIC_NAME; 022import static org.junit.jupiter.api.Assertions.assertArrayEquals; 023import static org.junit.jupiter.api.Assertions.assertEquals; 024import static org.junit.jupiter.api.Assertions.assertNotNull; 025import static org.junit.jupiter.api.Assertions.assertNull; 026import static org.junit.jupiter.api.Assertions.fail; 027 028import java.io.IOException; 029import java.util.Arrays; 030import java.util.List; 031import java.util.Map; 032import java.util.stream.Collectors; 033import org.apache.hadoop.conf.Configuration; 034import org.apache.hadoop.fs.FileStatus; 035import org.apache.hadoop.fs.FileSystem; 036import org.apache.hadoop.fs.Path; 037import org.apache.hadoop.hbase.Cell; 038import org.apache.hadoop.hbase.CellScanner; 039import org.apache.hadoop.hbase.HBaseTestingUtil; 040import org.apache.hadoop.hbase.StartTestingClusterOption; 041import org.apache.hadoop.hbase.TableName; 042import org.apache.hadoop.hbase.client.metrics.ScanMetrics; 043import org.apache.hadoop.hbase.client.metrics.ScanMetricsRegionInfo; 044import org.apache.hadoop.hbase.master.cleaner.TimeToLiveHFileCleaner; 045import org.apache.hadoop.hbase.master.snapshot.SnapshotManager; 046import org.apache.hadoop.hbase.regionserver.HRegion; 047import org.apache.hadoop.hbase.regionserver.HRegionFileSystem; 048import org.apache.hadoop.hbase.regionserver.HRegionServer; 049import org.apache.hadoop.hbase.regionserver.StoreContext; 050import org.apache.hadoop.hbase.regionserver.storefiletracker.StoreFileTracker; 051import org.apache.hadoop.hbase.regionserver.storefiletracker.StoreFileTrackerFactory; 052import org.apache.hadoop.hbase.snapshot.MobSnapshotTestingUtils; 053import org.apache.hadoop.hbase.snapshot.RestoreSnapshotHelper; 054import org.apache.hadoop.hbase.snapshot.SnapshotTestingUtils; 055import org.apache.hadoop.hbase.testclassification.ClientTests; 056import org.apache.hadoop.hbase.testclassification.LargeTests; 057import org.apache.hadoop.hbase.util.Bytes; 058import org.apache.hadoop.hbase.util.CommonFSUtils; 059import org.apache.hadoop.hbase.util.EnvironmentEdgeManager; 060import org.apache.hadoop.hbase.util.FSUtils; 061import org.apache.hadoop.hbase.util.HFileArchiveUtil; 062import org.apache.hadoop.hbase.util.JVMClusterUtil.RegionServerThread; 063import org.junit.jupiter.api.AfterEach; 064import org.junit.jupiter.api.BeforeEach; 065import org.junit.jupiter.api.Tag; 066import org.junit.jupiter.api.Test; 067import org.junit.jupiter.api.TestInfo; 068import org.slf4j.Logger; 069import org.slf4j.LoggerFactory; 070 071@Tag(LargeTests.TAG) 072@Tag(ClientTests.TAG) 073public class TestTableSnapshotScanner { 074 075 private static final Logger LOG = LoggerFactory.getLogger(TestTableSnapshotScanner.class); 076 private final HBaseTestingUtil UTIL = new HBaseTestingUtil(); 077 private static final int NUM_REGION_SERVERS = 2; 078 private static final byte[][] FAMILIES = { Bytes.toBytes("f1"), Bytes.toBytes("f2") }; 079 public static byte[] bbb = Bytes.toBytes("bbb"); 080 public static byte[] yyy = Bytes.toBytes("yyy"); 081 082 private FileSystem fs; 083 private Path rootDir; 084 private boolean clusterUp; 085 086 private String methodName; 087 088 public static void blockUntilSplitFinished(HBaseTestingUtil util, TableName tableName, 089 int expectedRegionSize) throws Exception { 090 for (int i = 0; i < 100; i++) { 091 List<RegionInfo> hRegionInfoList = util.getAdmin().getRegions(tableName); 092 if (hRegionInfoList.size() >= expectedRegionSize) { 093 break; 094 } 095 Thread.sleep(1000); 096 } 097 } 098 099 @BeforeEach 100 public void setupCluster(TestInfo testInfo) throws Exception { 101 methodName = testInfo.getTestMethod().get().getName(); 102 setupConf(UTIL.getConfiguration()); 103 StartTestingClusterOption option = 104 StartTestingClusterOption.builder().numRegionServers(NUM_REGION_SERVERS) 105 .numDataNodes(NUM_REGION_SERVERS).createRootDir(true).build(); 106 UTIL.startMiniCluster(option); 107 clusterUp = true; 108 rootDir = UTIL.getHBaseCluster().getMaster().getMasterFileSystem().getRootDir(); 109 fs = rootDir.getFileSystem(UTIL.getConfiguration()); 110 } 111 112 @AfterEach 113 public void tearDownCluster() throws Exception { 114 if (clusterUp) { 115 UTIL.shutdownMiniCluster(); 116 } 117 } 118 119 protected void setupConf(Configuration conf) { 120 // Enable snapshot 121 conf.setBoolean(SnapshotManager.HBASE_SNAPSHOT_ENABLED, true); 122 } 123 124 public static void createTableAndSnapshot(HBaseTestingUtil util, TableName tableName, 125 String snapshotName, int numRegions) throws Exception { 126 try { 127 util.deleteTable(tableName); 128 } catch (Exception ex) { 129 // ignore 130 } 131 132 if (numRegions > 1) { 133 util.createTable(tableName, FAMILIES, 1, bbb, yyy, numRegions); 134 } else { 135 util.createTable(tableName, FAMILIES); 136 } 137 Admin admin = util.getAdmin(); 138 139 // put some stuff in the table 140 Table table = util.getConnection().getTable(tableName); 141 util.loadTable(table, FAMILIES); 142 143 Path rootDir = CommonFSUtils.getRootDir(util.getConfiguration()); 144 FileSystem fs = rootDir.getFileSystem(util.getConfiguration()); 145 146 SnapshotTestingUtils.createSnapshotAndValidate(admin, tableName, Arrays.asList(FAMILIES), null, 147 snapshotName, rootDir, fs, true); 148 149 // load different values 150 byte[] value = Bytes.toBytes("after_snapshot_value"); 151 util.loadTable(table, FAMILIES, value); 152 153 // cause flush to create new files in the region 154 admin.flush(tableName); 155 table.close(); 156 } 157 158 @Test 159 public void testNoDuplicateResultsWhenSplitting() throws Exception { 160 TableName tableName = TableName.valueOf("testNoDuplicateResultsWhenSplitting"); 161 String snapshotName = "testSnapshotBug"; 162 try { 163 if (UTIL.getAdmin().tableExists(tableName)) { 164 UTIL.deleteTable(tableName); 165 } 166 167 UTIL.createTable(tableName, FAMILIES); 168 Admin admin = UTIL.getAdmin(); 169 170 // put some stuff in the table 171 Table table = UTIL.getConnection().getTable(tableName); 172 UTIL.loadTable(table, FAMILIES); 173 174 // split to 2 regions 175 admin.split(tableName, Bytes.toBytes("eee")); 176 blockUntilSplitFinished(UTIL, tableName, 2); 177 178 Path rootDir = CommonFSUtils.getRootDir(UTIL.getConfiguration()); 179 FileSystem fs = rootDir.getFileSystem(UTIL.getConfiguration()); 180 181 SnapshotTestingUtils.createSnapshotAndValidate(admin, tableName, Arrays.asList(FAMILIES), 182 null, snapshotName, rootDir, fs, true); 183 184 // load different values 185 byte[] value = Bytes.toBytes("after_snapshot_value"); 186 UTIL.loadTable(table, FAMILIES, value); 187 188 // cause flush to create new files in the region 189 admin.flush(tableName); 190 table.close(); 191 192 Path restoreDir = UTIL.getDataTestDirOnTestFS(snapshotName); 193 Scan scan = new Scan().withStartRow(bbb).withStopRow(yyy); // limit the scan 194 195 TableSnapshotScanner scanner = 196 new TableSnapshotScanner(UTIL.getConfiguration(), restoreDir, snapshotName, scan); 197 198 verifyScanner(scanner, bbb, yyy); 199 scanner.close(); 200 } catch (Exception e) { 201 e.printStackTrace(); 202 } finally { 203 UTIL.getAdmin().deleteSnapshot(snapshotName); 204 UTIL.deleteTable(tableName); 205 } 206 } 207 208 @Test 209 public void testScanLimit() throws Exception { 210 final TableName tableName = TableName.valueOf(methodName); 211 final String snapshotName = tableName + "Snapshot"; 212 TableSnapshotScanner scanner = null; 213 try { 214 createTableAndSnapshot(UTIL, tableName, snapshotName, 50); 215 Path restoreDir = UTIL.getDataTestDirOnTestFS(snapshotName); 216 Scan scan = new Scan().withStartRow(bbb).setLimit(100); // limit the scan 217 218 scanner = new TableSnapshotScanner(UTIL.getConfiguration(), restoreDir, snapshotName, scan); 219 int count = 0; 220 while (true) { 221 Result result = scanner.next(); 222 if (result == null) { 223 break; 224 } 225 count++; 226 } 227 assertEquals(100, count); 228 } finally { 229 if (scanner != null) { 230 scanner.close(); 231 } 232 UTIL.getAdmin().deleteSnapshot(snapshotName); 233 UTIL.deleteTable(tableName); 234 } 235 } 236 237 @Test 238 public void testWithSingleRegion() throws Exception { 239 testScanner(UTIL, "testWithSingleRegion", 1, false); 240 } 241 242 @Test 243 public void testWithMultiRegion() throws Exception { 244 testScanner(UTIL, "testWithMultiRegion", 10, false); 245 } 246 247 @Test 248 public void testWithOfflineHBaseMultiRegion() throws Exception { 249 testScanner(UTIL, "testWithMultiRegion", 20, true); 250 } 251 252 private ScanMetrics createTableSnapshotScannerAndGetScanMetrics(boolean enableScanMetrics, 253 boolean enableScanMetricsByRegion, byte[] endKey) throws Exception { 254 TableName tableName = TableName.valueOf(methodName + "_TABLE"); 255 String snapshotName = methodName + "_SNAPSHOT"; 256 try { 257 createTableAndSnapshot(UTIL, tableName, snapshotName, 50); 258 Path restoreDir = UTIL.getDataTestDirOnTestFS(snapshotName); 259 Scan scan = new Scan().withStartRow(bbb).withStopRow(endKey); 260 scan.setScanMetricsEnabled(enableScanMetrics); 261 scan.setEnableScanMetricsByRegion(enableScanMetricsByRegion); 262 Configuration conf = UTIL.getConfiguration(); 263 264 TableSnapshotScanner snapshotScanner = 265 new TableSnapshotScanner(conf, restoreDir, snapshotName, scan); 266 verifyScanner(snapshotScanner, bbb, endKey); 267 return snapshotScanner.getScanMetrics(); 268 } finally { 269 UTIL.getAdmin().deleteSnapshot(snapshotName); 270 UTIL.deleteTable(tableName); 271 } 272 } 273 274 @Test 275 public void testScanMetricsDisabled() throws Exception { 276 ScanMetrics scanMetrics = createTableSnapshotScannerAndGetScanMetrics(false, false, yyy); 277 assertNull(scanMetrics); 278 } 279 280 @Test 281 public void testScanMetricsWithScanMetricsByRegionDisabled() throws Exception { 282 ScanMetrics scanMetrics = createTableSnapshotScannerAndGetScanMetrics(true, false, yyy); 283 assertNotNull(scanMetrics); 284 int rowsScanned = 0; 285 for (byte[] row : HBaseTestingUtil.ROWS) { 286 if (Bytes.compareTo(row, bbb) >= 0 && Bytes.compareTo(row, yyy) < 0) { 287 rowsScanned++; 288 } 289 } 290 Map<String, Long> metricsMap = scanMetrics.getMetricsMap(); 291 assertEquals(rowsScanned, (long) metricsMap.get(COUNT_OF_ROWS_SCANNED_KEY_METRIC_NAME)); 292 } 293 294 @Test 295 public void testScanMetricsByRegionForSingleRegion() throws Exception { 296 // Scan single row with row key bbb 297 byte[] bbc = Bytes.toBytes("bbc"); 298 ScanMetrics scanMetrics = createTableSnapshotScannerAndGetScanMetrics(true, true, bbc); 299 assertNotNull(scanMetrics); 300 Map<ScanMetricsRegionInfo, Map<String, Long>> scanMetricsByRegion = 301 scanMetrics.collectMetricsByRegion(); 302 assertEquals(1, scanMetricsByRegion.size()); 303 for (Map.Entry<ScanMetricsRegionInfo, Map<String, Long>> entry : scanMetricsByRegion 304 .entrySet()) { 305 ScanMetricsRegionInfo scanMetricsRegionInfo = entry.getKey(); 306 Map<String, Long> metricsMap = entry.getValue(); 307 assertNull(scanMetricsRegionInfo.getServerName()); 308 assertNotNull(scanMetricsRegionInfo.getEncodedRegionName()); 309 assertEquals(1, (long) metricsMap.get(REGIONS_SCANNED_METRIC_NAME)); 310 assertEquals(1, (long) metricsMap.get(COUNT_OF_ROWS_SCANNED_KEY_METRIC_NAME)); 311 } 312 } 313 314 @Test 315 public void testScanMetricsByRegionForMultiRegion() throws Exception { 316 ScanMetrics scanMetrics = createTableSnapshotScannerAndGetScanMetrics(true, true, yyy); 317 assertNotNull(scanMetrics); 318 Map<ScanMetricsRegionInfo, Map<String, Long>> scanMetricsByRegion = 319 scanMetrics.collectMetricsByRegion(); 320 for (Map.Entry<ScanMetricsRegionInfo, Map<String, Long>> entry : scanMetricsByRegion 321 .entrySet()) { 322 ScanMetricsRegionInfo scanMetricsRegionInfo = entry.getKey(); 323 Map<String, Long> metricsMap = entry.getValue(); 324 assertNull(scanMetricsRegionInfo.getServerName()); 325 assertNotNull(scanMetricsRegionInfo.getEncodedRegionName()); 326 assertEquals(1, (long) metricsMap.get(REGIONS_SCANNED_METRIC_NAME)); 327 } 328 } 329 330 @Test 331 public void testScannerWithRestoreScanner() throws Exception { 332 TableName tableName = TableName.valueOf("testScanner"); 333 String snapshotName = "testScannerWithRestoreScanner"; 334 try { 335 createTableAndSnapshot(UTIL, tableName, snapshotName, 50); 336 Path restoreDir = UTIL.getDataTestDirOnTestFS(snapshotName); 337 Scan scan = new Scan().withStartRow(bbb).withStopRow(yyy); // limit the scan 338 339 Configuration conf = UTIL.getConfiguration(); 340 Path rootDir = CommonFSUtils.getRootDir(conf); 341 342 TableSnapshotScanner scanner0 = 343 new TableSnapshotScanner(conf, restoreDir, snapshotName, scan); 344 verifyScanner(scanner0, bbb, yyy); 345 scanner0.close(); 346 347 // restore snapshot. 348 RestoreSnapshotHelper.copySnapshotForScanner(conf, fs, rootDir, restoreDir, snapshotName); 349 350 // scan the snapshot without restoring snapshot 351 TableSnapshotScanner scanner = 352 new TableSnapshotScanner(conf, rootDir, restoreDir, snapshotName, scan, true); 353 verifyScanner(scanner, bbb, yyy); 354 scanner.close(); 355 356 // check whether the snapshot has been deleted by the close of scanner. 357 scanner = new TableSnapshotScanner(conf, rootDir, restoreDir, snapshotName, scan, true); 358 verifyScanner(scanner, bbb, yyy); 359 scanner.close(); 360 361 // restore snapshot again. 362 RestoreSnapshotHelper.copySnapshotForScanner(conf, fs, rootDir, restoreDir, snapshotName); 363 364 // check whether the snapshot has been deleted by the close of scanner. 365 scanner = new TableSnapshotScanner(conf, rootDir, restoreDir, snapshotName, scan, true); 366 verifyScanner(scanner, bbb, yyy); 367 scanner.close(); 368 } finally { 369 UTIL.getAdmin().deleteSnapshot(snapshotName); 370 UTIL.deleteTable(tableName); 371 } 372 } 373 374 @Test 375 public void testScannerWithRestoredMobSnapshot() throws Exception { 376 TableName tableName = TableName.valueOf(methodName); 377 String snapshotName = methodName + "Snapshot"; 378 Path restoreDir = UTIL.getDataTestDirOnTestFS(snapshotName); 379 try { 380 MobSnapshotTestingUtils.createMobTable(UTIL, tableName, new byte[0][], 1, FAMILIES); 381 try (Table table = UTIL.getConnection().getTable(tableName)) { 382 UTIL.loadTable(table, FAMILIES); 383 } 384 UTIL.getAdmin().snapshot(snapshotName, tableName); 385 386 Configuration conf = UTIL.getConfiguration(); 387 RestoreSnapshotHelper.copySnapshotForScanner(conf, fs, rootDir, restoreDir, snapshotName); 388 try (TableSnapshotScanner scanner = new TableSnapshotScanner(conf, rootDir, restoreDir, 389 snapshotName, new Scan().withStartRow(Bytes.toBytes("zzzz")), true)) { 390 assertNull(scanner.next()); 391 } 392 } finally { 393 fs.delete(restoreDir, true); 394 UTIL.getAdmin().deleteSnapshot(snapshotName); 395 UTIL.deleteTable(tableName); 396 } 397 } 398 399 private void testScanner(HBaseTestingUtil util, String snapshotName, int numRegions, 400 boolean shutdownCluster) throws Exception { 401 TableName tableName = TableName.valueOf("testScanner"); 402 try { 403 createTableAndSnapshot(util, tableName, snapshotName, numRegions); 404 405 if (shutdownCluster) { 406 util.shutdownMiniHBaseCluster(); 407 clusterUp = false; 408 } 409 410 Path restoreDir = util.getDataTestDirOnTestFS(snapshotName); 411 Scan scan = new Scan().withStartRow(bbb).withStopRow(yyy); // limit the scan 412 413 TableSnapshotScanner scanner = 414 new TableSnapshotScanner(UTIL.getConfiguration(), restoreDir, snapshotName, scan); 415 416 verifyScanner(scanner, bbb, yyy); 417 scanner.close(); 418 } finally { 419 if (clusterUp) { 420 util.getAdmin().deleteSnapshot(snapshotName); 421 util.deleteTable(tableName); 422 } 423 } 424 } 425 426 private void verifyScanner(ResultScanner scanner, byte[] startRow, byte[] stopRow) 427 throws IOException, InterruptedException { 428 429 HBaseTestingUtil.SeenRowTracker rowTracker = 430 new HBaseTestingUtil.SeenRowTracker(startRow, stopRow); 431 432 while (true) { 433 Result result = scanner.next(); 434 if (result == null) { 435 break; 436 } 437 verifyRow(result); 438 rowTracker.addRow(result.getRow()); 439 } 440 441 // validate all rows are seen 442 rowTracker.validate(); 443 } 444 445 private static void verifyRow(Result result) throws IOException { 446 byte[] row = result.getRow(); 447 CellScanner scanner = result.cellScanner(); 448 while (scanner.advance()) { 449 Cell cell = scanner.current(); 450 451 // assert that all Cells in the Result have the same key 452 assertEquals(0, Bytes.compareTo(row, 0, row.length, cell.getRowArray(), cell.getRowOffset(), 453 cell.getRowLength())); 454 } 455 456 for (int j = 0; j < FAMILIES.length; j++) { 457 byte[] actual = result.getValue(FAMILIES[j], FAMILIES[j]); 458 assertArrayEquals(row, actual, "Row in snapshot does not match, expected:" 459 + Bytes.toString(row) + " ,actual:" + Bytes.toString(actual)); 460 } 461 } 462 463 @Test 464 public void testMergeRegion() throws Exception { 465 TableName tableName = TableName.valueOf("testMergeRegion"); 466 String snapshotName = tableName.getNameAsString() + "_snapshot"; 467 Configuration conf = UTIL.getConfiguration(); 468 Path rootDir = UTIL.getHBaseCluster().getMaster().getMasterFileSystem().getRootDir(); 469 long timeout = 20000; // 20s 470 try (Admin admin = UTIL.getAdmin()) { 471 List<String> serverList = admin.getRegionServers().stream().map(sn -> sn.getServerName()) 472 .collect(Collectors.toList()); 473 // create table with 3 regions 474 Table table = UTIL.createTable(tableName, FAMILIES, 1, bbb, yyy, 3); 475 List<RegionInfo> regions = admin.getRegions(tableName); 476 assertEquals(3, regions.size()); 477 RegionInfo region0 = regions.get(0); 478 RegionInfo region1 = regions.get(1); 479 RegionInfo region2 = regions.get(2); 480 // put some data in the table 481 UTIL.loadTable(table, FAMILIES); 482 admin.flush(tableName); 483 // wait flush is finished 484 UTIL.waitFor(timeout, () -> { 485 try { 486 Path tableDir = CommonFSUtils.getTableDir(rootDir, tableName); 487 for (RegionInfo region : regions) { 488 Path regionDir = new Path(tableDir, region.getEncodedName()); 489 for (Path familyDir : FSUtils.getFamilyDirs(fs, regionDir)) { 490 for (FileStatus fs : fs.listStatus(familyDir)) { 491 if (!fs.getPath().getName().equals(".filelist")) { 492 return true; 493 } 494 } 495 return false; 496 } 497 } 498 return true; 499 } catch (IOException e) { 500 LOG.warn("Failed check if flush is finished", e); 501 return false; 502 } 503 }); 504 // merge 2 regions 505 admin.compactionSwitch(false, serverList); 506 admin.mergeRegionsAsync(region0.getEncodedNameAsBytes(), region1.getEncodedNameAsBytes(), 507 true); 508 UTIL.waitFor(timeout, () -> admin.getRegions(tableName).size() == 2); 509 List<RegionInfo> mergedRegions = admin.getRegions(tableName); 510 RegionInfo mergedRegion = 511 mergedRegions.get(0).getEncodedName().equals(region2.getEncodedName()) 512 ? mergedRegions.get(1) 513 : mergedRegions.get(0); 514 // snapshot 515 admin.snapshot(snapshotName, tableName); 516 assertEquals(1, admin.listSnapshots().size()); 517 // major compact 518 admin.compactionSwitch(true, serverList); 519 admin.majorCompactRegion(mergedRegion.getRegionName()); 520 // wait until merged region has no reference 521 UTIL.waitFor(timeout, () -> { 522 try { 523 for (RegionServerThread regionServerThread : UTIL.getMiniHBaseCluster() 524 .getRegionServerThreads()) { 525 HRegionServer regionServer = regionServerThread.getRegionServer(); 526 for (HRegion subRegion : regionServer.getRegions(tableName)) { 527 if ( 528 subRegion.getRegionInfo().getEncodedName().equals(mergedRegion.getEncodedName()) 529 ) { 530 regionServer.getCompactedHFilesDischarger().chore(); 531 } 532 } 533 } 534 Path tableDir = CommonFSUtils.getTableDir(rootDir, tableName); 535 HRegionFileSystem regionFs = HRegionFileSystem 536 .openRegionFromFileSystem(UTIL.getConfiguration(), fs, tableDir, mergedRegion, true); 537 boolean references = false; 538 Path regionDir = new Path(tableDir, mergedRegion.getEncodedName()); 539 for (Path familyDir : FSUtils.getFamilyDirs(fs, regionDir)) { 540 StoreContext storeContext = StoreContext.getBuilder() 541 .withColumnFamilyDescriptor(ColumnFamilyDescriptorBuilder.of(familyDir.getName())) 542 .withRegionFileSystem(regionFs).withFamilyStoreDirectoryPath(familyDir).build(); 543 StoreFileTracker sft = 544 StoreFileTrackerFactory.create(UTIL.getConfiguration(), false, storeContext); 545 references = references || sft.hasReferences(); 546 if (references) { 547 break; 548 } 549 } 550 return !references; 551 } catch (IOException e) { 552 LOG.warn("Failed check merged region has no reference", e); 553 return false; 554 } 555 }); 556 // run catalog janitor to clean and wait for parent regions are archived 557 UTIL.getMiniHBaseCluster().getMaster().getCatalogJanitor().choreForTesting(); 558 UTIL.waitFor(timeout, () -> { 559 try { 560 Path tableDir = CommonFSUtils.getTableDir(rootDir, tableName); 561 for (FileStatus fileStatus : fs.listStatus(tableDir)) { 562 String name = fileStatus.getPath().getName(); 563 if (name.equals(region0.getEncodedName()) || name.equals(region1.getEncodedName())) { 564 return false; 565 } 566 } 567 return true; 568 } catch (IOException e) { 569 LOG.warn("Check if parent regions are archived error", e); 570 return false; 571 } 572 }); 573 // set file modify time and then run cleaner 574 long time = EnvironmentEdgeManager.currentTime() - TimeToLiveHFileCleaner.DEFAULT_TTL * 1000; 575 traverseAndSetFileTime(HFileArchiveUtil.getArchivePath(conf), time); 576 UTIL.getMiniHBaseCluster().getMaster().getHFileCleaner().triggerCleanerNow().get(); 577 // scan snapshot 578 try (TableSnapshotScanner scanner = 579 new TableSnapshotScanner(conf, UTIL.getDataTestDirOnTestFS(snapshotName), snapshotName, 580 new Scan().withStartRow(bbb).withStopRow(yyy))) { 581 verifyScanner(scanner, bbb, yyy); 582 } 583 } catch (Exception e) { 584 LOG.error("scan snapshot error", e); 585 fail("Should not throw Exception: " + e.getMessage()); 586 } 587 } 588 589 @Test 590 public void testDeleteTableWithMergedRegions() throws Exception { 591 final TableName tableName = TableName.valueOf(this.methodName); 592 String snapshotName = tableName.getNameAsString() + "_snapshot"; 593 Configuration conf = UTIL.getConfiguration(); 594 try (Admin admin = UTIL.getConnection().getAdmin()) { 595 // disable compaction 596 admin.compactionSwitch(false, 597 admin.getRegionServers().stream().map(s -> s.getServerName()).collect(Collectors.toList())); 598 // create table 599 Table table = UTIL.createTable(tableName, FAMILIES, 1, bbb, yyy, 3); 600 List<RegionInfo> regions = admin.getRegions(tableName); 601 assertEquals(3, regions.size()); 602 // write some data 603 UTIL.loadTable(table, FAMILIES); 604 // merge region 605 admin.mergeRegionsAsync(new byte[][] { regions.get(0).getEncodedNameAsBytes(), 606 regions.get(1).getEncodedNameAsBytes() }, false).get(); 607 regions = admin.getRegions(tableName); 608 assertEquals(2, regions.size()); 609 // snapshot 610 admin.snapshot(snapshotName, tableName); 611 // verify snapshot 612 try (TableSnapshotScanner scanner = 613 new TableSnapshotScanner(conf, UTIL.getDataTestDirOnTestFS(snapshotName), snapshotName, 614 new Scan().withStartRow(bbb).withStopRow(yyy))) { 615 verifyScanner(scanner, bbb, yyy); 616 } 617 // drop table 618 admin.disableTable(tableName); 619 admin.deleteTable(tableName); 620 // verify snapshot 621 try (TableSnapshotScanner scanner = 622 new TableSnapshotScanner(conf, UTIL.getDataTestDirOnTestFS(snapshotName), snapshotName, 623 new Scan().withStartRow(bbb).withStopRow(yyy))) { 624 verifyScanner(scanner, bbb, yyy); 625 } 626 } 627 } 628 629 private void traverseAndSetFileTime(Path path, long time) throws IOException { 630 fs.setTimes(path, time, -1); 631 if (fs.isDirectory(path)) { 632 List<FileStatus> allPaths = Arrays.asList(fs.listStatus(path)); 633 List<FileStatus> subDirs = 634 allPaths.stream().filter(FileStatus::isDirectory).collect(Collectors.toList()); 635 List<FileStatus> files = 636 allPaths.stream().filter(FileStatus::isFile).collect(Collectors.toList()); 637 for (FileStatus subDir : subDirs) { 638 traverseAndSetFileTime(subDir.getPath(), time); 639 } 640 for (FileStatus file : files) { 641 fs.setTimes(file.getPath(), time, -1); 642 } 643 } 644 } 645}