diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/backup/HFileArchiver.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/backup/HFileArchiver.java index f5496eae0e3..0cac6296ad2 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/backup/HFileArchiver.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/backup/HFileArchiver.java @@ -19,11 +19,18 @@ package org.apache.hadoop.hbase.backup; import java.io.FileNotFoundException; import java.io.IOException; +import java.io.InterruptedIOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.FileStatus; @@ -37,6 +44,7 @@ import org.apache.hadoop.hbase.util.Bytes; import org.apache.hadoop.hbase.util.EnvironmentEdgeManager; import org.apache.hadoop.hbase.util.FSUtils; import org.apache.hadoop.hbase.util.HFileArchiveUtil; +import org.apache.hadoop.hbase.util.Threads; import org.apache.hadoop.io.MultipleIOException; import org.apache.yetus.audience.InterfaceAudience; import org.slf4j.Logger; @@ -67,6 +75,8 @@ public class HFileArchiver { } }; + private static ThreadPoolExecutor archiveExecutor; + private HFileArchiver() { // hidden ctor since this is just a util } @@ -103,14 +113,12 @@ public class HFileArchiver { * the archive path) * @param tableDir {@link Path} to where the table is being stored (for building the archive path) * @param regionDir {@link Path} to where a region is being stored (for building the archive path) - * @return true if the region was sucessfully deleted. false if the filesystem + * @return true if the region was successfully deleted. false if the filesystem * operations could not complete. * @throws IOException if the request cannot be completed */ public static boolean archiveRegion(FileSystem fs, Path rootdir, Path tableDir, Path regionDir) throws IOException { - LOG.debug("ARCHIVING {}", rootdir.toString()); - // otherwise, we archive the files // make sure we can archive if (tableDir == null || regionDir == null) { @@ -122,6 +130,8 @@ public class HFileArchiver { return false; } + LOG.debug("ARCHIVING {}", regionDir); + // make sure the regiondir lives under the tabledir Preconditions.checkArgument(regionDir.toString().startsWith(tableDir.toString())); Path regionArchiveDir = HFileArchiveUtil.getRegionArchiveDir(rootdir, @@ -137,7 +147,7 @@ public class HFileArchiver { PathFilter nonHidden = new PathFilter() { @Override public boolean accept(Path file) { - return dirFilter.accept(file) && !file.getName().toString().startsWith("."); + return dirFilter.accept(file) && !file.getName().startsWith("."); } }; FileStatus[] storeDirs = FSUtils.listStatus(fs, regionDir, nonHidden); @@ -162,6 +172,67 @@ public class HFileArchiver { return deleteRegionWithoutArchiving(fs, regionDir); } + /** + * Archive the specified regions in parallel. + * @param conf the configuration to use + * @param fs {@link FileSystem} from which to remove the region + * @param rootDir {@link Path} to the root directory where hbase files are stored (for building + * the archive path) + * @param tableDir {@link Path} to where the table is being stored (for building the archive + * path) + * @param regionDirList {@link Path} to where regions are being stored (for building the archive + * path) + * @throws IOException if the request cannot be completed + */ + public static void archiveRegions(Configuration conf, FileSystem fs, Path rootDir, Path tableDir, + List regionDirList) throws IOException { + List> futures = new ArrayList<>(regionDirList.size()); + for (Path regionDir: regionDirList) { + Future future = getArchiveExecutor(conf).submit(() -> { + archiveRegion(fs, rootDir, tableDir, regionDir); + return null; + }); + futures.add(future); + } + try { + for (Future future: futures) { + future.get(); + } + } catch (InterruptedException e) { + throw new InterruptedIOException(e.getMessage()); + } catch (ExecutionException e) { + throw new IOException(e.getCause()); + } + } + + private static synchronized ThreadPoolExecutor getArchiveExecutor(final Configuration conf) { + if (archiveExecutor == null) { + int maxThreads = conf.getInt("hbase.hfilearchiver.thread.pool.max", 8); + archiveExecutor = Threads.getBoundedCachedThreadPool(maxThreads, 30L, TimeUnit.SECONDS, + getThreadFactory()); + + // Shutdown this ThreadPool in a shutdown hook + Runtime.getRuntime().addShutdownHook(new Thread(() -> archiveExecutor.shutdown())); + } + return archiveExecutor; + } + + // We need this method instead of Threads.getNamedThreadFactory() to pass some tests. + // The difference from Threads.getNamedThreadFactory() is that it doesn't fix ThreadGroup for + // new threads. If we use Threads.getNamedThreadFactory(), we will face ThreadGroup related + // issues in some tests. + private static ThreadFactory getThreadFactory() { + return new ThreadFactory() { + final AtomicInteger threadNumber = new AtomicInteger(1); + + @Override + public Thread newThread(Runnable r) { + final String name = "HFileArchiver-" + threadNumber.getAndIncrement(); + return new Thread(r, name); + } + }; + } + /** * Remove from the specified region the store files of the specified column family, * either by archiving them or outright deletion diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/MasterFileSystem.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/MasterFileSystem.java index 864be029008..54c596e92bb 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/MasterFileSystem.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/MasterFileSystem.java @@ -49,6 +49,8 @@ import org.apache.yetus.audience.InterfaceAudience; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.apache.hbase.thirdparty.com.google.common.annotations.VisibleForTesting; + /** * This class abstracts a bunch of operations the HMaster needs to interact with * the underlying file system like creating the initial layout, checking file @@ -307,16 +309,16 @@ public class MasterFileSystem { * Make sure the hbase temp directory exists and is empty. * NOTE that this method is only executed once just after the master becomes the active one. */ - private void checkTempDir(final Path tmpdir, final Configuration c, final FileSystem fs) + @VisibleForTesting + void checkTempDir(final Path tmpdir, final Configuration c, final FileSystem fs) throws IOException { // If the temp directory exists, clear the content (left over, from the previous run) if (fs.exists(tmpdir)) { // Archive table in temp, maybe left over from failed deletion, // if not the cleaner will take care of them. - for (Path tabledir: FSUtils.getTableDirs(fs, tmpdir)) { - for (Path regiondir: FSUtils.getRegionDirs(fs, tabledir)) { - HFileArchiver.archiveRegion(fs, this.rootdir, tabledir, regiondir); - } + for (Path tableDir: FSUtils.getTableDirs(fs, tmpdir)) { + HFileArchiver.archiveRegions(c, fs, this.rootdir, tableDir, + FSUtils.getRegionDirs(fs, tableDir)); } if (!fs.delete(tmpdir, true)) { throw new IOException("Unable to clean the temp directory: " + tmpdir); diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/procedure/DeleteTableProcedure.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/procedure/DeleteTableProcedure.java index 46dca208657..4325d19569b 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/procedure/DeleteTableProcedure.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/procedure/DeleteTableProcedure.java @@ -20,7 +20,9 @@ package org.apache.hadoop.hbase.master.procedure; import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; +import java.util.stream.Collectors; import org.apache.hadoop.fs.FileStatus; import org.apache.hadoop.fs.FileSystem; @@ -33,6 +35,7 @@ import org.apache.hadoop.hbase.backup.HFileArchiver; import org.apache.hadoop.hbase.client.Connection; import org.apache.hadoop.hbase.client.Delete; import org.apache.hadoop.hbase.client.RegionInfo; +import org.apache.hadoop.hbase.client.RegionReplicaUtil; import org.apache.hadoop.hbase.client.Result; import org.apache.hadoop.hbase.client.ResultScanner; import org.apache.hadoop.hbase.client.Scan; @@ -43,7 +46,6 @@ import org.apache.hadoop.hbase.master.MasterFileSystem; import org.apache.hadoop.hbase.mob.MobConstants; import org.apache.hadoop.hbase.mob.MobUtils; import org.apache.hadoop.hbase.procedure2.ProcedureStateSerializer; -import org.apache.hadoop.hbase.regionserver.HRegion; import org.apache.hadoop.hbase.util.FSUtils; import org.apache.yetus.audience.InterfaceAudience; import org.slf4j.Logger; @@ -293,35 +295,37 @@ public class DeleteTableProcedure throw new IOException("HBase temp directory '" + tempdir + "' creation failure."); } + if (fs.exists(tempTableDir)) { + // TODO + // what's in this dir? something old? probably something manual from the user... + // let's get rid of this stuff... + FileStatus[] files = fs.listStatus(tempTableDir); + if (files != null && files.length > 0) { + List regionDirList = Arrays.stream(files) + .filter(FileStatus::isDirectory) + .map(FileStatus::getPath) + .collect(Collectors.toList()); + HFileArchiver.archiveRegions(env.getMasterConfiguration(), fs, mfs.getRootDir(), + tempTableDir, regionDirList); + } + fs.delete(tempTableDir, true); + } + // Move the table in /hbase/.tmp if (!fs.rename(tableDir, tempTableDir)) { - if (fs.exists(tempTableDir)) { - // TODO - // what's in this dir? something old? probably something manual from the user... - // let's get rid of this stuff... - FileStatus[] files = fs.listStatus(tempdir); - if (files != null && files.length > 0) { - for (int i = 0; i < files.length; ++i) { - if (!files[i].isDirectory()) { - continue; - } - HFileArchiver.archiveRegion(fs, mfs.getRootDir(), tempTableDir, files[i].getPath()); - } - } - fs.delete(tempdir, true); - } throw new IOException("Unable to move '" + tableDir + "' to temp '" + tempTableDir + "'"); } } // Archive regions from FS (temp directory) if (archive) { - for (RegionInfo hri : regions) { - LOG.debug("Archiving region " + hri.getRegionNameAsString() + " from FS"); - HFileArchiver.archiveRegion(fs, mfs.getRootDir(), - tempTableDir, HRegion.getRegionDir(tempTableDir, hri.getEncodedName())); - } - LOG.debug("Table '" + tableName + "' archived!"); + List regionDirList = regions.stream() + .filter(RegionReplicaUtil::isDefaultReplica) + .map(region -> FSUtils.getRegionDir(tempTableDir, region)) + .collect(Collectors.toList()); + HFileArchiver.archiveRegions(env.getMasterConfiguration(), fs, mfs.getRootDir(), + tempTableDir, regionDirList); + LOG.debug("Table '{}' archived!", tableName); } // Archive mob data diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/backup/TestHFileArchiving.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/backup/TestHFileArchiving.java index 05abbd1da56..ed2b466b928 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/backup/TestHFileArchiving.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/backup/TestHFileArchiving.java @@ -22,9 +22,12 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import java.io.IOException; +import java.security.PrivilegedExceptionAction; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.stream.Collectors; + import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.FileStatus; import org.apache.hadoop.fs.FileSystem; @@ -33,12 +36,11 @@ import org.apache.hadoop.fs.PathFilter; import org.apache.hadoop.hbase.*; import org.apache.hadoop.hbase.HBaseClassTestRule; import org.apache.hadoop.hbase.client.Admin; +import org.apache.hadoop.hbase.client.Table; import org.apache.hadoop.hbase.master.cleaner.HFileCleaner; -import org.apache.hadoop.hbase.regionserver.CompactingMemStore; import org.apache.hadoop.hbase.regionserver.ConstantSizeRegionSplitPolicy; import org.apache.hadoop.hbase.regionserver.HRegion; import org.apache.hadoop.hbase.regionserver.HRegionServer; -import org.apache.hadoop.hbase.regionserver.Region; import org.apache.hadoop.hbase.testclassification.MediumTests; import org.apache.hadoop.hbase.testclassification.MiscTests; import org.apache.hadoop.hbase.util.Bytes; @@ -46,6 +48,7 @@ import org.apache.hadoop.hbase.util.FSUtils; import org.apache.hadoop.hbase.util.HFileArchiveTestingUtil; import org.apache.hadoop.hbase.util.HFileArchiveUtil; import org.apache.hadoop.hbase.util.StoppableImplementation; +import org.apache.hadoop.security.UserGroupInformation; import org.junit.After; import org.junit.AfterClass; import org.junit.Assert; @@ -121,7 +124,7 @@ public class TestHFileArchiving { } @Test - public void testRemovesRegionDirOnArchive() throws Exception { + public void testRemoveRegionDirOnArchive() throws Exception { final TableName tableName = TableName.valueOf(name.getMethodName()); UTIL.createTable(tableName, TEST_FAM); @@ -151,7 +154,6 @@ public class TestHFileArchiving { Path archiveDir = HFileArchiveTestingUtil.getRegionArchiveDir(UTIL.getConfiguration(), region); assertTrue(fs.exists(archiveDir)); - // check to make sure the store directory was copied // check to make sure the store directory was copied FileStatus[] stores = fs.listStatus(archiveDir, new PathFilter() { @Override @@ -227,6 +229,100 @@ public class TestHFileArchiving { UTIL.deleteTable(tableName); } + private List initTableForArchivingRegions(TableName tableName) throws IOException { + final byte[][] splitKeys = new byte[][] { + Bytes.toBytes("b"), Bytes.toBytes("c"), Bytes.toBytes("d") + }; + + UTIL.createTable(tableName, TEST_FAM, splitKeys); + + // get the current store files for the regions + List regions = UTIL.getHBaseCluster().getRegions(tableName); + // make sure we have 4 regions serving this table + assertEquals(4, regions.size()); + + // and load the table + try (Table table = UTIL.getConnection().getTable(tableName)) { + UTIL.loadTable(table, TEST_FAM); + } + + // disable the table so that we can manipulate the files + UTIL.getAdmin().disableTable(tableName); + + return regions; + } + + @Test + public void testArchiveRegions() throws Exception { + final TableName tableName = TableName.valueOf(name.getMethodName()); + List regions = initTableForArchivingRegions(tableName); + + FileSystem fs = UTIL.getTestFileSystem(); + + // now attempt to depose the regions + Path rootDir = FSUtils.getRootDir(UTIL.getConfiguration()); + Path tableDir = FSUtils.getTableDir(rootDir, regions.get(0).getRegionInfo().getTable()); + List regionDirList = regions.stream() + .map(region -> FSUtils.getRegionDir(tableDir, region.getRegionInfo())) + .collect(Collectors.toList()); + + HFileArchiver.archiveRegions(UTIL.getConfiguration(), fs, rootDir, tableDir, regionDirList); + + // check for the existence of the archive directory and some files in it + for (HRegion region : regions) { + Path archiveDir = HFileArchiveTestingUtil.getRegionArchiveDir(UTIL.getConfiguration(), + region); + assertTrue(fs.exists(archiveDir)); + + // check to make sure the store directory was copied + FileStatus[] stores = fs.listStatus(archiveDir, + p -> !p.getName().contains(HConstants.RECOVERED_EDITS_DIR)); + assertTrue(stores.length == 1); + + // make sure we archived the store files + FileStatus[] storeFiles = fs.listStatus(stores[0].getPath()); + assertTrue(storeFiles.length > 0); + } + + // then ensure the region's directories aren't present + for (Path regionDir: regionDirList) { + assertFalse(fs.exists(regionDir)); + } + + UTIL.deleteTable(tableName); + } + + @Test(expected=IOException.class) + public void testArchiveRegionsWhenPermissionDenied() throws Exception { + final TableName tableName = TableName.valueOf(name.getMethodName()); + List regions = initTableForArchivingRegions(tableName); + + // now attempt to depose the regions + Path rootDir = FSUtils.getRootDir(UTIL.getConfiguration()); + Path tableDir = FSUtils.getTableDir(rootDir, regions.get(0).getRegionInfo().getTable()); + List regionDirList = regions.stream() + .map(region -> FSUtils.getRegionDir(tableDir, region.getRegionInfo())) + .collect(Collectors.toList()); + + // To create a permission denied error, we do archive regions as a non-current user + UserGroupInformation + ugi = UserGroupInformation.createUserForTesting("foo1234", new String[]{"group1"}); + + try { + ugi.doAs((PrivilegedExceptionAction) () -> { + FileSystem fs = UTIL.getTestFileSystem(); + HFileArchiver.archiveRegions(UTIL.getConfiguration(), fs, rootDir, tableDir, + regionDirList); + return null; + }); + } catch (IOException e) { + assertTrue(e.getCause().getMessage().contains("Permission denied")); + throw e; + } finally { + UTIL.deleteTable(tableName); + } + } + @Test public void testArchiveOnTableDelete() throws Exception { final TableName tableName = TableName.valueOf(name.getMethodName()); diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/TestMasterFileSystem.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/TestMasterFileSystem.java index 107d0782ff3..47cad32e04d 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/TestMasterFileSystem.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/TestMasterFileSystem.java @@ -18,22 +18,30 @@ package org.apache.hadoop.hbase.master; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.util.List; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; import org.apache.hadoop.hbase.HBaseClassTestRule; import org.apache.hadoop.hbase.HBaseTestingUtility; -import org.apache.hadoop.hbase.HConstants; +import org.apache.hadoop.hbase.TableName; +import org.apache.hadoop.hbase.client.Table; +import org.apache.hadoop.hbase.regionserver.HRegion; import org.apache.hadoop.hbase.testclassification.MasterTests; import org.apache.hadoop.hbase.testclassification.MediumTests; +import org.apache.hadoop.hbase.util.Bytes; import org.apache.hadoop.hbase.util.FSUtils; +import org.apache.hadoop.hbase.util.HFileArchiveTestingUtil; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.ClassRule; +import org.junit.Rule; import org.junit.Test; import org.junit.experimental.categories.Category; +import org.junit.rules.TestName; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -47,6 +55,9 @@ public class TestMasterFileSystem { public static final HBaseClassTestRule CLASS_RULE = HBaseClassTestRule.forClass(TestMasterFileSystem.class); + @Rule + public TestName name = new TestName(); + private static final Logger LOG = LoggerFactory.getLogger(TestMasterFileSystem.class); private static final HBaseTestingUtility UTIL = new HBaseTestingUtility(); @@ -73,4 +84,56 @@ public class TestMasterFileSystem { // make sure the set uri matches by forcing it. assertEquals(masterRoot, rootDir); } + + @Test + public void testCheckTempDir() throws Exception { + final MasterFileSystem masterFileSystem = + UTIL.getMiniHBaseCluster().getMaster().getMasterFileSystem(); + + final TableName tableName = TableName.valueOf(name.getMethodName()); + final byte[] FAM = Bytes.toBytes("fam"); + final byte[][] splitKeys = new byte[][] { + Bytes.toBytes("b"), Bytes.toBytes("c"), Bytes.toBytes("d") + }; + + UTIL.createTable(tableName, FAM, splitKeys); + + // get the current store files for the regions + List regions = UTIL.getHBaseCluster().getRegions(tableName); + // make sure we have 4 regions serving this table + assertEquals(4, regions.size()); + + // load the table + try (Table table = UTIL.getConnection().getTable(tableName)) { + UTIL.loadTable(table, FAM); + } + + // disable the table so that we can manipulate the files + UTIL.getAdmin().disableTable(tableName); + + final Path tableDir = FSUtils.getTableDir(masterFileSystem.getRootDir(), tableName); + final Path tempDir = masterFileSystem.getTempDir(); + final Path tempTableDir = FSUtils.getTableDir(tempDir, tableName); + final FileSystem fs = masterFileSystem.getFileSystem(); + + // move the table to the temporary directory + if (!fs.rename(tableDir, tempTableDir)) { + fail(); + } + + masterFileSystem.checkTempDir(tempDir, UTIL.getConfiguration(), fs); + + // check if the temporary directory exists and is empty + assertTrue(fs.exists(tempDir)); + assertEquals(0, fs.listStatus(tempDir).length); + + // check for the existence of the archive directory + for (HRegion region : regions) { + Path archiveDir = HFileArchiveTestingUtil.getRegionArchiveDir(UTIL.getConfiguration(), + region); + assertTrue(fs.exists(archiveDir)); + } + + UTIL.deleteTable(tableName); + } } diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/procedure/TestDeleteTableProcedure.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/procedure/TestDeleteTableProcedure.java index 86ddf92c432..88159662bf1 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/procedure/TestDeleteTableProcedure.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/procedure/TestDeleteTableProcedure.java @@ -17,19 +17,32 @@ */ package org.apache.hadoop.hbase.master.procedure; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import java.util.List; + +import org.apache.hadoop.fs.FileSystem; +import org.apache.hadoop.fs.FileUtil; +import org.apache.hadoop.fs.Path; import org.apache.hadoop.hbase.HBaseClassTestRule; import org.apache.hadoop.hbase.TableName; import org.apache.hadoop.hbase.TableNotDisabledException; import org.apache.hadoop.hbase.TableNotFoundException; import org.apache.hadoop.hbase.client.RegionInfo; +import org.apache.hadoop.hbase.client.Table; +import org.apache.hadoop.hbase.master.MasterFileSystem; import org.apache.hadoop.hbase.procedure2.Procedure; import org.apache.hadoop.hbase.procedure2.ProcedureExecutor; import org.apache.hadoop.hbase.procedure2.ProcedureTestingUtility; +import org.apache.hadoop.hbase.regionserver.HRegion; import org.apache.hadoop.hbase.testclassification.MasterTests; import org.apache.hadoop.hbase.testclassification.MediumTests; import org.apache.hadoop.hbase.util.Bytes; +import org.apache.hadoop.hbase.util.FSUtils; +import org.apache.hadoop.hbase.util.HFileArchiveTestingUtil; import org.junit.ClassRule; import org.junit.Rule; import org.junit.Test; @@ -38,6 +51,7 @@ import org.junit.rules.TestName; import org.slf4j.Logger; import org.slf4j.LoggerFactory; + @Category({MasterTests.class, MediumTests.class}) public class TestDeleteTableProcedure extends TestTableDDLProcedureBase { @@ -155,4 +169,59 @@ public class TestDeleteTableProcedure extends TestTableDDLProcedureBase { MasterProcedureTestingUtility.validateTableDeletion(getMaster(), tableName); } + + @Test + public void testDeleteWhenTempDirIsNotEmpty() throws Exception { + final TableName tableName = TableName.valueOf(name.getMethodName()); + final String FAM = "fam"; + final byte[][] splitKeys = new byte[][] { + Bytes.toBytes("b"), Bytes.toBytes("c"), Bytes.toBytes("d") + }; + + // create the table + MasterProcedureTestingUtility.createTable( + getMasterProcedureExecutor(), tableName, splitKeys, FAM); + + // get the current store files for the regions + List regions = UTIL.getHBaseCluster().getRegions(tableName); + // make sure we have 4 regions serving this table + assertEquals(4, regions.size()); + + // load the table + try (Table table = UTIL.getConnection().getTable(tableName)) { + UTIL.loadTable(table, Bytes.toBytes(FAM)); + } + + // disable the table so that we can manipulate the files + UTIL.getAdmin().disableTable(tableName); + + final MasterFileSystem masterFileSystem = + UTIL.getMiniHBaseCluster().getMaster().getMasterFileSystem(); + final Path tableDir = FSUtils.getTableDir(masterFileSystem.getRootDir(), tableName); + final Path tempDir = masterFileSystem.getTempDir(); + final Path tempTableDir = FSUtils.getTableDir(tempDir, tableName); + final FileSystem fs = masterFileSystem.getFileSystem(); + + // copy the table to the temporary directory to make sure the temp directory is not empty + if (!FileUtil.copy(fs, tableDir, fs, tempTableDir, false, UTIL.getConfiguration())) { + fail(); + } + + // delete the table + final ProcedureExecutor procExec = getMasterProcedureExecutor(); + long procId = ProcedureTestingUtility.submitAndWait(procExec, + new DeleteTableProcedure(procExec.getEnvironment(), tableName)); + ProcedureTestingUtility.assertProcNotFailed(procExec, procId); + MasterProcedureTestingUtility.validateTableDeletion(getMaster(), tableName); + + // check if the temporary directory is deleted + assertFalse(fs.exists(tempTableDir)); + + // check for the existence of the archive directory + for (HRegion region : regions) { + Path archiveDir = HFileArchiveTestingUtil.getRegionArchiveDir(UTIL.getConfiguration(), + region); + assertTrue(fs.exists(archiveDir)); + } + } }