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