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