diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AsyncAdmin.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AsyncAdmin.java index 9538a4878b4..7d904b39fc8 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AsyncAdmin.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AsyncAdmin.java @@ -21,6 +21,7 @@ import java.util.List; import java.util.Collection; import java.util.Map; import java.util.Optional; +import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.regex.Pattern; @@ -32,9 +33,11 @@ import org.apache.hadoop.hbase.ServerName; import org.apache.hadoop.hbase.NamespaceDescriptor; import org.apache.hadoop.hbase.TableName; import org.apache.hadoop.hbase.classification.InterfaceAudience; +import org.apache.hadoop.hbase.procedure2.LockInfo; import org.apache.hadoop.hbase.quotas.QuotaFilter; import org.apache.hadoop.hbase.quotas.QuotaSettings; import org.apache.hadoop.hbase.client.replication.TableCFs; +import org.apache.hadoop.hbase.client.security.SecurityCapability; import org.apache.hadoop.hbase.replication.ReplicationPeerConfig; import org.apache.hadoop.hbase.replication.ReplicationPeerDescription; import org.apache.hadoop.hbase.util.Pair; @@ -404,6 +407,34 @@ public interface AsyncAdmin { */ CompletableFuture majorCompactRegionServer(ServerName serverName); + /** + * Turn the Merge switch on or off. + * @param on + * @return Previous switch value wrapped by a {@link CompletableFuture} + */ + CompletableFuture setMergeOn(boolean on); + + /** + * Query the current state of the Merge switch. + * @return true if the switch is on, false otherwise. The return value will be wrapped by a + * {@link CompletableFuture} + */ + CompletableFuture isMergeOn(); + + /** + * Turn the Split switch on or off. + * @param on + * @return Previous switch value wrapped by a {@link CompletableFuture} + */ + CompletableFuture setSplitOn(boolean on); + + /** + * Query the current state of the Split switch. + * @return true if the switch is on, false otherwise. The return value will be wrapped by a + * {@link CompletableFuture} + */ + CompletableFuture isSplitOn(); + /** * Merge two regions. * @param nameOfRegionA encoded or full name of region a @@ -770,6 +801,12 @@ public interface AsyncAdmin { */ CompletableFuture> listProcedures(); + /** + * List procedure locks. + * @return lock list wrapped by {@link CompletableFuture} + */ + CompletableFuture> listProcedureLocks(); + /** * Mark a region server as draining to prevent additional regions from getting assigned to it. * @param servers @@ -851,6 +888,24 @@ public interface AsyncAdmin { */ CompletableFuture updateConfiguration(); + /** + * Roll the log writer. I.e. for filesystem based write ahead logs, start writing to a new file. + *

+ * When the returned CompletableFuture is done, it only means the rollWALWriter request was sent + * to the region server and may need some time to finish the rollWALWriter operation. As a side + * effect of this call, the named region server may schedule store flushes at the request of the + * wal. + * @param serverName The servername of the region server. + */ + CompletableFuture rollWALWriter(ServerName serverName); + + /** + * Clear compacting queues on a region server. + * @param serverName + * @param queues the set of queue name + */ + CompletableFuture clearCompactionQueues(ServerName serverName, Set queues); + /** * Get a list of {@link RegionLoad} of all regions hosted on a region seerver for a table. * @param serverName @@ -903,6 +958,12 @@ public interface AsyncAdmin { */ CompletableFuture> getLastMajorCompactionTimestampForRegion(byte[] regionName); + /** + * @return the list of supported security capabilities. The return value will be wrapped by a + * {@link CompletableFuture}. + */ + CompletableFuture> getSecurityCapabilities(); + /** * Turn the load balancer on or off. * @param on diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AsyncHBaseAdmin.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AsyncHBaseAdmin.java index 8e2b3c016ef..8e5a28c7331 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AsyncHBaseAdmin.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AsyncHBaseAdmin.java @@ -21,6 +21,7 @@ import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutorService; import java.util.regex.Pattern; @@ -36,6 +37,8 @@ import org.apache.hadoop.hbase.NamespaceDescriptor; import org.apache.hadoop.hbase.TableName; import org.apache.hadoop.hbase.classification.InterfaceAudience; import org.apache.hadoop.hbase.client.replication.TableCFs; +import org.apache.hadoop.hbase.client.security.SecurityCapability; +import org.apache.hadoop.hbase.procedure2.LockInfo; import org.apache.hadoop.hbase.quotas.QuotaFilter; import org.apache.hadoop.hbase.quotas.QuotaSettings; import org.apache.hadoop.hbase.replication.ReplicationPeerConfig; @@ -257,6 +260,26 @@ public class AsyncHBaseAdmin implements AsyncAdmin { return wrap(rawAdmin.majorCompactRegionServer(serverName)); } + @Override + public CompletableFuture setMergeOn(boolean on) { + return wrap(rawAdmin.setMergeOn(on)); + } + + @Override + public CompletableFuture isMergeOn() { + return wrap(rawAdmin.isMergeOn()); + } + + @Override + public CompletableFuture setSplitOn(boolean on) { + return wrap(rawAdmin.setSplitOn(on)); + } + + @Override + public CompletableFuture isSplitOn() { + return wrap(rawAdmin.isSplitOn()); + } + @Override public CompletableFuture mergeRegions(byte[] nameOfRegionA, byte[] nameOfRegionB, boolean forcible) { @@ -438,6 +461,11 @@ public class AsyncHBaseAdmin implements AsyncAdmin { return wrap(rawAdmin.listProcedures()); } + @Override + public CompletableFuture> listProcedureLocks() { + return wrap(rawAdmin.listProcedureLocks()); + } + @Override public CompletableFuture drainRegionServers(List servers) { return wrap(rawAdmin.drainRegionServers(servers)); @@ -483,6 +511,21 @@ public class AsyncHBaseAdmin implements AsyncAdmin { return wrap(rawAdmin.updateConfiguration()); } + @Override + public CompletableFuture rollWALWriter(ServerName serverName) { + return wrap(rawAdmin.rollWALWriter(serverName)); + } + + @Override + public CompletableFuture clearCompactionQueues(ServerName serverName, Set queues) { + return wrap(rawAdmin.clearCompactionQueues(serverName, queues)); + } + + @Override + public CompletableFuture> getSecurityCapabilities() { + return wrap(rawAdmin.getSecurityCapabilities()); + } + @Override public CompletableFuture> getRegionLoads(ServerName serverName, Optional tableName) { diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/RawAsyncHBaseAdmin.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/RawAsyncHBaseAdmin.java index 2c6dd77e981..0271a50198a 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/RawAsyncHBaseAdmin.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/RawAsyncHBaseAdmin.java @@ -29,6 +29,7 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; @@ -71,8 +72,10 @@ import org.apache.hadoop.hbase.client.AsyncRpcRetryingCallerFactory.MasterReques import org.apache.hadoop.hbase.client.Scan.ReadType; import org.apache.hadoop.hbase.client.replication.ReplicationSerDeHelper; import org.apache.hadoop.hbase.client.replication.TableCFs; +import org.apache.hadoop.hbase.client.security.SecurityCapability; import org.apache.hadoop.hbase.exceptions.DeserializationException; import org.apache.hadoop.hbase.ipc.HBaseRpcController; +import org.apache.hadoop.hbase.procedure2.LockInfo; import org.apache.hadoop.hbase.quotas.QuotaFilter; import org.apache.hadoop.hbase.quotas.QuotaSettings; import org.apache.hadoop.hbase.quotas.QuotaTableUtil; @@ -83,6 +86,8 @@ import org.apache.hadoop.hbase.shaded.com.google.protobuf.RpcCallback; import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil; import org.apache.hadoop.hbase.shaded.protobuf.RequestConverter; import org.apache.hadoop.hbase.shaded.protobuf.generated.AdminProtos.AdminService; +import org.apache.hadoop.hbase.shaded.protobuf.generated.AdminProtos.ClearCompactionQueuesRequest; +import org.apache.hadoop.hbase.shaded.protobuf.generated.AdminProtos.ClearCompactionQueuesResponse; import org.apache.hadoop.hbase.shaded.protobuf.generated.AdminProtos.CloseRegionRequest; import org.apache.hadoop.hbase.shaded.protobuf.generated.AdminProtos.CloseRegionResponse; import org.apache.hadoop.hbase.shaded.protobuf.generated.AdminProtos.CompactRegionRequest; @@ -95,6 +100,8 @@ import org.apache.hadoop.hbase.shaded.protobuf.generated.AdminProtos.GetRegionIn import org.apache.hadoop.hbase.shaded.protobuf.generated.AdminProtos.GetRegionInfoResponse; import org.apache.hadoop.hbase.shaded.protobuf.generated.AdminProtos.GetRegionLoadRequest; import org.apache.hadoop.hbase.shaded.protobuf.generated.AdminProtos.GetRegionLoadResponse; +import org.apache.hadoop.hbase.shaded.protobuf.generated.AdminProtos.RollWALWriterRequest; +import org.apache.hadoop.hbase.shaded.protobuf.generated.AdminProtos.RollWALWriterResponse; import org.apache.hadoop.hbase.shaded.protobuf.generated.AdminProtos.SplitRegionRequest; import org.apache.hadoop.hbase.shaded.protobuf.generated.AdminProtos.SplitRegionResponse; import org.apache.hadoop.hbase.shaded.protobuf.generated.AdminProtos.StopServerRequest; @@ -162,8 +169,12 @@ import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.IsProcedur import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.IsProcedureDoneResponse; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.IsSnapshotDoneRequest; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.IsSnapshotDoneResponse; +import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.IsSplitOrMergeEnabledRequest; +import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.IsSplitOrMergeEnabledResponse; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.ListDrainingRegionServersRequest; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.ListDrainingRegionServersResponse; +import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.ListLocksRequest; +import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.ListLocksResponse; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.ListNamespaceDescriptorsRequest; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.ListNamespaceDescriptorsResponse; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.ListProceduresRequest; @@ -192,6 +203,8 @@ import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.RunCatalog import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.RunCatalogScanResponse; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.RunCleanerChoreRequest; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.RunCleanerChoreResponse; +import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.SecurityCapabilitiesRequest; +import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.SecurityCapabilitiesResponse; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.SetBalancerRunningRequest; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.SetBalancerRunningResponse; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.SetCleanerChoreRunningRequest; @@ -200,6 +213,8 @@ import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.SetNormali import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.SetNormalizerRunningResponse; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.SetQuotaRequest; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.SetQuotaResponse; +import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.SetSplitOrMergeEnabledRequest; +import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.SetSplitOrMergeEnabledResponse; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.ShutdownRequest; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.ShutdownResponse; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.SnapshotRequest; @@ -1033,6 +1048,51 @@ public class RawAsyncHBaseAdmin implements AsyncAdmin { return future; } + @Override + public CompletableFuture setMergeOn(boolean on) { + return setSplitOrMergeOn(on, MasterSwitchType.MERGE); + } + + @Override + public CompletableFuture isMergeOn() { + return isSplitOrMergeOn(MasterSwitchType.MERGE); + } + + @Override + public CompletableFuture setSplitOn(boolean on) { + return setSplitOrMergeOn(on, MasterSwitchType.SPLIT); + } + + @Override + public CompletableFuture isSplitOn() { + return isSplitOrMergeOn(MasterSwitchType.SPLIT); + } + + private CompletableFuture setSplitOrMergeOn(boolean on, MasterSwitchType switchType) { + SetSplitOrMergeEnabledRequest request = + RequestConverter.buildSetSplitOrMergeEnabledRequest(on, false, switchType); + return this + . newMasterCaller() + .action( + (controller, stub) -> this + . call( + controller, stub, request, (s, c, req, done) -> s.setSplitOrMergeEnabled(c, req, + done), (resp) -> resp.getPrevValueList().get(0))).call(); + } + + private CompletableFuture isSplitOrMergeOn(MasterSwitchType switchType) { + IsSplitOrMergeEnabledRequest request = + RequestConverter.buildIsSplitOrMergeEnabledRequest(switchType); + return this + . newMasterCaller() + .action( + (controller, stub) -> this + . call( + controller, stub, request, + (s, c, req, done) -> s.isSplitOrMergeEnabled(c, req, done), + (resp) -> resp.getEnabled())).call(); + } + @Override public CompletableFuture mergeRegions(byte[] nameOfRegionA, byte[] nameOfRegionB, boolean forcible) { @@ -1929,6 +1989,17 @@ public class RawAsyncHBaseAdmin implements AsyncAdmin { .collect(Collectors.toList()))).call(); } + @Override + public CompletableFuture> listProcedureLocks() { + return this + .> newMasterCaller() + .action( + (controller, stub) -> this.> call( + controller, stub, ListLocksRequest.newBuilder().build(), + (s, c, req, done) -> s.listLocks(c, req, done), resp -> resp.getLockList().stream() + .map(ProtobufUtil::toLockInfo).collect(Collectors.toList()))).call(); + } + @Override public CompletableFuture drainRegionServers(List servers) { return this @@ -2421,6 +2492,41 @@ public class RawAsyncHBaseAdmin implements AsyncAdmin { return future; } + @Override + public CompletableFuture rollWALWriter(ServerName serverName) { + return this + . newAdminCaller() + .action( + (controller, stub) -> this. adminCall( + controller, stub, RequestConverter.buildRollWALWriterRequest(), + (s, c, req, done) -> s.rollWALWriter(controller, req, done), resp -> null)) + .serverName(serverName).call(); + } + + @Override + public CompletableFuture clearCompactionQueues(ServerName serverName, Set queues) { + return this + . newAdminCaller() + .action( + (controller, stub) -> this + . adminCall( + controller, stub, RequestConverter.buildClearCompactionQueuesRequest(queues), (s, + c, req, done) -> s.clearCompactionQueues(controller, req, done), resp -> null)) + .serverName(serverName).call(); + } + + @Override + public CompletableFuture> getSecurityCapabilities() { + return this + .> newMasterCaller() + .action( + (controller, stub) -> this + .> call( + controller, stub, SecurityCapabilitiesRequest.newBuilder().build(), (s, c, req, + done) -> s.getSecurityCapabilities(c, req, done), (resp) -> ProtobufUtil + .toSecurityCapabilityList(resp.getCapabilitiesList()))).call(); + } + @Override public CompletableFuture> getRegionLoads(ServerName serverName, Optional tableName) { diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/client/TestAsyncClusterAdminApi.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/client/TestAsyncClusterAdminApi.java index 6a3c8e1c703..3d3f04975c5 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/client/TestAsyncClusterAdminApi.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/client/TestAsyncClusterAdminApi.java @@ -34,14 +34,18 @@ import java.util.Optional; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.ClusterStatus; import org.apache.hadoop.hbase.HBaseTestingUtility; +import org.apache.hadoop.hbase.HConstants; import org.apache.hadoop.hbase.HRegionInfo; import org.apache.hadoop.hbase.RegionLoad; import org.apache.hadoop.hbase.ServerLoad; import org.apache.hadoop.hbase.ServerName; import org.apache.hadoop.hbase.TableName; +import org.apache.hadoop.hbase.regionserver.HRegionServer; +import org.apache.hadoop.hbase.regionserver.Region; import org.apache.hadoop.hbase.testclassification.ClientTests; import org.apache.hadoop.hbase.testclassification.MediumTests; import org.apache.hadoop.hbase.util.Bytes; +import org.apache.hadoop.hbase.wal.AbstractFSWALProvider; import org.junit.Test; import org.junit.experimental.categories.Category; import org.junit.runner.RunWith; @@ -121,6 +125,92 @@ public class TestAsyncClusterAdminApi extends TestAsyncAdminBase { Files.copy(cnf3Path, cnfPath, StandardCopyOption.REPLACE_EXISTING); } + @Test + public void testRollWALWALWriter() throws Exception { + setUpforLogRolling(); + String className = this.getClass().getName(); + StringBuilder v = new StringBuilder(className); + while (v.length() < 1000) { + v.append(className); + } + byte[] value = Bytes.toBytes(v.toString()); + HRegionServer regionServer = startAndWriteData(tableName, value); + LOG.info("after writing there are " + + AbstractFSWALProvider.getNumRolledLogFiles(regionServer.getWAL(null)) + " log files"); + + // flush all regions + for (Region r : regionServer.getOnlineRegionsLocalContext()) { + r.flush(true); + } + admin.rollWALWriter(regionServer.getServerName()).join(); + int count = AbstractFSWALProvider.getNumRolledLogFiles(regionServer.getWAL(null)); + LOG.info("after flushing all regions and rolling logs there are " + + count + " log files"); + assertTrue(("actual count: " + count), count <= 2); + } + + private void setUpforLogRolling() { + // Force a region split after every 768KB + TEST_UTIL.getConfiguration().setLong(HConstants.HREGION_MAX_FILESIZE, + 768L * 1024L); + + // We roll the log after every 32 writes + TEST_UTIL.getConfiguration().setInt("hbase.regionserver.maxlogentries", 32); + + TEST_UTIL.getConfiguration().setInt( + "hbase.regionserver.logroll.errors.tolerated", 2); + TEST_UTIL.getConfiguration().setInt("hbase.rpc.timeout", 10 * 1000); + + // For less frequently updated regions flush after every 2 flushes + TEST_UTIL.getConfiguration().setInt( + "hbase.hregion.memstore.optionalflushcount", 2); + + // We flush the cache after every 8192 bytes + TEST_UTIL.getConfiguration().setInt(HConstants.HREGION_MEMSTORE_FLUSH_SIZE, + 8192); + + // Increase the amount of time between client retries + TEST_UTIL.getConfiguration().setLong("hbase.client.pause", 10 * 1000); + + // Reduce thread wake frequency so that other threads can get + // a chance to run. + TEST_UTIL.getConfiguration().setInt(HConstants.THREAD_WAKE_FREQUENCY, + 2 * 1000); + + /**** configuration for testLogRollOnDatanodeDeath ****/ + // lower the namenode & datanode heartbeat so the namenode + // quickly detects datanode failures + TEST_UTIL.getConfiguration().setInt("dfs.namenode.heartbeat.recheck-interval", 5000); + TEST_UTIL.getConfiguration().setInt("dfs.heartbeat.interval", 1); + // the namenode might still try to choose the recently-dead datanode + // for a pipeline, so try to a new pipeline multiple times + TEST_UTIL.getConfiguration().setInt("dfs.client.block.write.retries", 30); + TEST_UTIL.getConfiguration().setInt( + "hbase.regionserver.hlog.tolerable.lowreplication", 2); + TEST_UTIL.getConfiguration().setInt( + "hbase.regionserver.hlog.lowreplication.rolllimit", 3); + } + + private HRegionServer startAndWriteData(TableName tableName, byte[] value) throws Exception { + createTableWithDefaultConf(tableName); + RawAsyncTable table = ASYNC_CONN.getRawTable(tableName); + HRegionServer regionServer = TEST_UTIL.getRSForFirstRegionInTable(tableName); + for (int i = 1; i <= 256; i++) { // 256 writes should cause 8 log rolls + Put put = new Put(Bytes.toBytes("row" + String.format("%1$04d", i))); + put.addColumn(FAMILY, null, value); + table.put(put).join(); + if (i % 32 == 0) { + // After every 32 writes sleep to let the log roller run + try { + Thread.sleep(2000); + } catch (InterruptedException e) { + // continue + } + } + } + return regionServer; + } + @Test public void testGetRegionLoads() throws Exception { // Turn off the balancer diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/client/TestAsyncRegionAdminApi.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/client/TestAsyncRegionAdminApi.java index 7752d378892..fcd3d01e244 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/client/TestAsyncRegionAdminApi.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/client/TestAsyncRegionAdminApi.java @@ -354,6 +354,79 @@ public class TestAsyncRegionAdminApi extends TestAsyncAdminBase { Assert.assertEquals(regionServer.getOnlineRegion(hri.getRegionName()).getMemstoreSize(), 0); } + @Test + public void testSplitSwitch() throws Exception { + createTableWithDefaultConf(tableName); + byte[][] families = { FAMILY }; + loadData(tableName, families, 1000); + + RawAsyncTable metaTable = ASYNC_CONN.getRawTable(META_TABLE_NAME); + List regionLocations = + AsyncMetaTableAccessor.getTableHRegionLocations(metaTable, Optional.of(tableName)).get(); + int originalCount = regionLocations.size(); + + initSplitMergeSwitch(); + assertTrue(admin.setSplitOn(false).get()); + admin.split(tableName).join(); + int count = admin.getTableRegions(tableName).get().size(); + assertTrue(originalCount == count); + + assertFalse(admin.setSplitOn(true).get()); + admin.split(tableName).join(); + while ((count = admin.getTableRegions(tableName).get().size()) == originalCount) { + Threads.sleep(100); + } + assertTrue(originalCount < count); + } + + @Test + @Ignore + // It was ignored in TestSplitOrMergeStatus, too + public void testMergeSwitch() throws Exception { + createTableWithDefaultConf(tableName); + byte[][] families = { FAMILY }; + loadData(tableName, families, 1000); + + RawAsyncTable metaTable = ASYNC_CONN.getRawTable(META_TABLE_NAME); + List regionLocations = + AsyncMetaTableAccessor.getTableHRegionLocations(metaTable, Optional.of(tableName)).get(); + int originalCount = regionLocations.size(); + + initSplitMergeSwitch(); + admin.split(tableName).join(); + int postSplitCount = originalCount; + while ((postSplitCount = admin.getTableRegions(tableName).get().size()) == originalCount) { + Threads.sleep(100); + } + assertTrue("originalCount=" + originalCount + ", postSplitCount=" + postSplitCount, + originalCount != postSplitCount); + + // Merge switch is off so merge should NOT succeed. + assertTrue(admin.setMergeOn(false).get()); + List regions = admin.getTableRegions(tableName).get(); + assertTrue(regions.size() > 1); + admin.mergeRegions(regions.get(0).getRegionName(), regions.get(1).getRegionName(), true).join(); + int count = admin.getTableRegions(tableName).get().size(); + assertTrue("postSplitCount=" + postSplitCount + ", count=" + count, postSplitCount == count); + + // Merge switch is on so merge should succeed. + assertFalse(admin.setMergeOn(true).get()); + admin.mergeRegions(regions.get(0).getRegionName(), regions.get(1).getRegionName(), true).join(); + count = admin.getTableRegions(tableName).get().size(); + assertTrue((postSplitCount / 2) == count); + } + + private void initSplitMergeSwitch() throws Exception { + if (!admin.isSplitOn().get()) { + admin.setSplitOn(true).get(); + } + if (!admin.isMergeOn().get()) { + admin.setMergeOn(true).get(); + } + assertTrue(admin.isSplitOn().get()); + assertTrue(admin.isMergeOn().get()); + } + @Test public void testMergeRegions() throws Exception { byte[][] splitRows = new byte[][] { Bytes.toBytes("3"), Bytes.toBytes("6") }; @@ -569,6 +642,11 @@ public class TestAsyncRegionAdminApi extends TestAsyncAdminBase { return count; } + private static void loadData(final TableName tableName, final byte[][] families, final int rows) + throws IOException { + loadData(tableName, families, rows, 1); + } + private static void loadData(final TableName tableName, final byte[][] families, final int rows, final int flushes) throws IOException { RawAsyncTable table = ASYNC_CONN.getRawTable(tableName);