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