diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/Admin.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/Admin.java index b8153a97ae4..20b1fc08868 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/Admin.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/Admin.java @@ -1292,10 +1292,24 @@ public interface Admin extends Abortable, Closeable { * the request was submitted successfully. We need to check logs for the details of which regions * were split/merged. * - * @return true if region normalizer ran, false otherwise. + * @return {@code true} if region normalizer ran, {@code false} otherwise. * @throws IOException if a remote or network exception occurs */ - boolean normalize() throws IOException; + default boolean normalize() throws IOException { + return normalize(new NormalizeTableFilterParams.Builder().build()); + } + + /** + * Invoke region normalizer. Can NOT run for various reasons. Check logs. + * This is a non-blocking invocation to region normalizer. If return value is true, it means + * the request was submitted successfully. We need to check logs for the details of which regions + * were split/merged. + * + * @param ntfp limit to tables matching the specified filter. + * @return {@code true} if region normalizer ran, {@code false} otherwise. + * @throws IOException if a remote or network exception occurs + */ + boolean normalize(NormalizeTableFilterParams ntfp) throws IOException; /** * Query the current state of the region normalizer. 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 87e1df9c6aa..b37785d2eb6 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 @@ -1,4 +1,4 @@ -/** +/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information @@ -1253,7 +1253,17 @@ public interface AsyncAdmin { * @return true if region normalizer ran, false otherwise. The return value will be wrapped by a * {@link CompletableFuture} */ - CompletableFuture normalize(); + default CompletableFuture normalize() { + return normalize(new NormalizeTableFilterParams.Builder().build()); + } + + /** + * Invoke region normalizer. Can NOT run for various reasons. Check logs. + * @param ntfp limit to tables matching the specified filter. + * @return true if region normalizer ran, false otherwise. The return value will be wrapped by a + * {@link CompletableFuture} + */ + CompletableFuture normalize(NormalizeTableFilterParams ntfp); /** * Turn the cleaner chore on/off. 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 376c5dcba90..29bf6ea043f 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 @@ -1,4 +1,4 @@ -/** +/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information @@ -704,8 +704,8 @@ class AsyncHBaseAdmin implements AsyncAdmin { } @Override - public CompletableFuture normalize() { - return wrap(rawAdmin.normalize()); + public CompletableFuture normalize(NormalizeTableFilterParams ntfp) { + return wrap(rawAdmin.normalize(ntfp)); } @Override diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/HBaseAdmin.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/HBaseAdmin.java index ac7857885a4..137572e8a2f 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/HBaseAdmin.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/HBaseAdmin.java @@ -1557,18 +1557,13 @@ public class HBaseAdmin implements Admin { } } - /** - * Invoke region normalizer. Can NOT run for various reasons. Check logs. - * - * @return True if region normalizer ran, false otherwise. - */ @Override - public boolean normalize() throws IOException { + public boolean normalize(NormalizeTableFilterParams ntfp) throws IOException { return executeCallable(new MasterCallable(getConnection(), getRpcControllerFactory()) { @Override protected Boolean rpcCall() throws Exception { return master.normalize(getRpcController(), - RequestConverter.buildNormalizeRequest()).getNormalizerRan(); + RequestConverter.buildNormalizeRequest(ntfp)).getNormalizerRan(); } }); } diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/NormalizeTableFilterParams.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/NormalizeTableFilterParams.java new file mode 100644 index 00000000000..982ec5b0065 --- /dev/null +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/NormalizeTableFilterParams.java @@ -0,0 +1,107 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.hadoop.hbase.client; + +import java.util.List; +import org.apache.hadoop.hbase.TableName; +import org.apache.yetus.audience.InterfaceAudience; + +/** + * A collection of criteria used for table selection. The logic of table selection is as follows: + *
    + *
  • + * When no parameter values are provided, an unfiltered list of all user tables is returned. + *
  • + *
  • + * When a list of {@link TableName TableNames} are provided, the filter starts with any of + * these tables that exist. + *
  • + *
  • + * When a {@code namespace} name is provided, the filter starts with all the tables present in + * that namespace. + *
  • + *
  • + * If both a list of {@link TableName TableNames} and a {@code namespace} name are provided, + * the {@link TableName} list is honored and the {@code namespace} name is ignored. + *
  • + *
  • + * If a {@code regex} is provided, this subset of {@link TableName TableNames} is further + * reduced to those that match the provided regular expression. + *
  • + *
+ */ +@InterfaceAudience.Public +public final class NormalizeTableFilterParams { + private final List tableNames; + private final String regex; + private final String namespace; + + private NormalizeTableFilterParams(final List tableNames, final String regex, + final String namespace) { + this.tableNames = tableNames; + this.regex = regex; + this.namespace = namespace; + } + + public List getTableNames() { + return tableNames; + } + + public String getRegex() { + return regex; + } + + public String getNamespace() { + return namespace; + } + + /** + * Used to instantiate an instance of {@link NormalizeTableFilterParams}. + */ + public static class Builder { + private List tableNames; + private String regex; + private String namespace; + + public Builder tableFilterParams(final NormalizeTableFilterParams ntfp) { + this.tableNames = ntfp.getTableNames(); + this.regex = ntfp.getRegex(); + this.namespace = ntfp.getNamespace(); + return this; + } + + public Builder tableNames(final List tableNames) { + this.tableNames = tableNames; + return this; + } + + public Builder regex(final String regex) { + this.regex = regex; + return this; + } + + public Builder namespace(final String namespace) { + this.namespace = namespace; + return this; + } + + public NormalizeTableFilterParams build() { + return new NormalizeTableFilterParams(tableNames, regex, namespace); + } + } +} 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 02cbcefaaff..392447a4063 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 @@ -3257,14 +3257,18 @@ class RawAsyncHBaseAdmin implements AsyncAdmin { } @Override - public CompletableFuture normalize() { + public CompletableFuture normalize(NormalizeTableFilterParams ntfp) { + return normalize(RequestConverter.buildNormalizeRequest(ntfp)); + } + + private CompletableFuture normalize(NormalizeRequest request) { return this - . newMasterCaller() - .action( - (controller, stub) -> this. call( - controller, stub, RequestConverter.buildNormalizeRequest(), - (s, c, req, done) -> s.normalize(c, req, done), (resp) -> resp.getNormalizerRan())) - .call(); + . newMasterCaller() + .action( + (controller, stub) -> this.call( + controller, stub, request, MasterService.Interface::normalize, + NormalizeResponse::getNormalizerRan)) + .call(); } @Override diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/shaded/protobuf/ProtobufUtil.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/shaded/protobuf/ProtobufUtil.java index c82243a9837..094065cefc8 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/shaded/protobuf/ProtobufUtil.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/shaded/protobuf/ProtobufUtil.java @@ -2251,6 +2251,13 @@ public final class ProtobufUtil { .setQualifier(UnsafeByteOperations.unsafeWrap(tableName.getQualifier())).build(); } + public static List toProtoTableNameList(List tableNameList) { + if (tableNameList == null) { + return new ArrayList<>(); + } + return tableNameList.stream().map(ProtobufUtil::toProtoTableName).collect(Collectors.toList()); + } + public static List toTableNameList(List tableNamesList) { if (tableNamesList == null) { return new ArrayList<>(); diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/shaded/protobuf/RequestConverter.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/shaded/protobuf/RequestConverter.java index f377d6c8daa..c877050b8b7 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/shaded/protobuf/RequestConverter.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/shaded/protobuf/RequestConverter.java @@ -1,4 +1,4 @@ -/** +/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information @@ -48,6 +48,7 @@ import org.apache.hadoop.hbase.client.Increment; import org.apache.hadoop.hbase.client.LogQueryFilter; import org.apache.hadoop.hbase.client.MasterSwitchType; import org.apache.hadoop.hbase.client.Mutation; +import org.apache.hadoop.hbase.client.NormalizeTableFilterParams; import org.apache.hadoop.hbase.client.Put; import org.apache.hadoop.hbase.client.RegionCoprocessorServiceExec; import org.apache.hadoop.hbase.client.RegionInfo; @@ -1724,8 +1725,18 @@ public final class RequestConverter { * * @return a NormalizeRequest */ - public static NormalizeRequest buildNormalizeRequest() { - return NormalizeRequest.newBuilder().build(); + public static NormalizeRequest buildNormalizeRequest(NormalizeTableFilterParams ntfp) { + final NormalizeRequest.Builder builder = NormalizeRequest.newBuilder(); + if (ntfp.getTableNames() != null) { + builder.addAllTableNames(ProtobufUtil.toProtoTableNameList(ntfp.getTableNames())); + } + if (ntfp.getRegex() != null) { + builder.setRegex(ntfp.getRegex()); + } + if (ntfp.getNamespace() != null) { + builder.setNamespace(ntfp.getNamespace()); + } + return builder.build(); } /** diff --git a/hbase-protocol-shaded/src/main/protobuf/Master.proto b/hbase-protocol-shaded/src/main/protobuf/Master.proto index 505eb81aa2f..78674ab5f15 100644 --- a/hbase-protocol-shaded/src/main/protobuf/Master.proto +++ b/hbase-protocol-shaded/src/main/protobuf/Master.proto @@ -353,6 +353,9 @@ message IsSplitOrMergeEnabledResponse { } message NormalizeRequest { + repeated TableName table_names = 1; + optional string regex = 2; + optional string namespace = 3; } message NormalizeResponse { diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/HMaster.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/HMaster.java index 6218536d90a..4d2042284b7 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/HMaster.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/HMaster.java @@ -21,7 +21,6 @@ import static org.apache.hadoop.hbase.HConstants.DEFAULT_HBASE_SPLIT_COORDINATED import static org.apache.hadoop.hbase.HConstants.HBASE_MASTER_LOGCLEANER_PLUGINS; import static org.apache.hadoop.hbase.HConstants.HBASE_SPLIT_WAL_COORDINATED_BY_ZK; import static org.apache.hadoop.hbase.util.DNS.MASTER_HOSTNAME_KEY; - import com.google.protobuf.Descriptors; import com.google.protobuf.Service; import java.io.IOException; @@ -40,6 +39,7 @@ import java.util.Comparator; import java.util.EnumSet; import java.util.HashMap; import java.util.Iterator; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Objects; @@ -82,9 +82,9 @@ import org.apache.hadoop.hbase.TableName; import org.apache.hadoop.hbase.TableNotDisabledException; import org.apache.hadoop.hbase.TableNotFoundException; import org.apache.hadoop.hbase.UnknownRegionException; -import org.apache.hadoop.hbase.client.Admin; import org.apache.hadoop.hbase.client.ColumnFamilyDescriptor; import org.apache.hadoop.hbase.client.MasterSwitchType; +import org.apache.hadoop.hbase.client.NormalizeTableFilterParams; import org.apache.hadoop.hbase.client.RegionInfo; import org.apache.hadoop.hbase.client.RegionInfoBuilder; import org.apache.hadoop.hbase.client.RegionStatesCount; @@ -214,6 +214,7 @@ import org.apache.hadoop.hbase.zookeeper.ZKClusterId; import org.apache.hadoop.hbase.zookeeper.ZKUtil; import org.apache.hadoop.hbase.zookeeper.ZKWatcher; import org.apache.hadoop.hbase.zookeeper.ZNodePaths; +import org.apache.hbase.thirdparty.com.google.common.collect.Sets; import org.apache.yetus.audience.InterfaceAudience; import org.apache.zookeeper.KeeperException; import org.eclipse.jetty.server.Server; @@ -222,12 +223,10 @@ import org.eclipse.jetty.servlet.ServletHolder; import org.eclipse.jetty.webapp.WebAppContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; - import org.apache.hbase.thirdparty.com.google.common.annotations.VisibleForTesting; import org.apache.hbase.thirdparty.com.google.common.collect.Lists; import org.apache.hbase.thirdparty.com.google.common.collect.Maps; import org.apache.hbase.thirdparty.org.apache.commons.collections4.CollectionUtils; - import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil; import org.apache.hadoop.hbase.shaded.protobuf.generated.AdminProtos.GetRegionInfoResponse.CompactionState; import org.apache.hadoop.hbase.shaded.protobuf.generated.SnapshotProtos.SnapshotDescription; @@ -1882,14 +1881,18 @@ public class HMaster extends HRegionServer implements MasterServices { return this.normalizer; } + public boolean normalizeRegions() throws IOException { + return normalizeRegions(new NormalizeTableFilterParams.Builder().build()); + } + /** - * Perform normalization of cluster (invoked by {@link RegionNormalizerChore}). + * Perform normalization of cluster. * * @return true if an existing normalization was already in progress, or if a new normalization * was performed successfully; false otherwise (specifically, if HMaster finished initializing * or normalization is globally disabled). */ - public boolean normalizeRegions() throws IOException { + public boolean normalizeRegions(final NormalizeTableFilterParams ntfp) throws IOException { final long startTime = EnvironmentEdgeManager.currentTime(); if (regionNormalizerTracker == null || !regionNormalizerTracker.isNormalizerOn()) { LOG.debug("Region normalization is disabled, don't run region normalizer."); @@ -1910,12 +1913,19 @@ public class HMaster extends HRegionServer implements MasterServices { int affectedTables = 0; try { - final List allEnabledTables = - new ArrayList<>(tableStateManager.getTablesInStates(TableState.State.ENABLED)); - Collections.shuffle(allEnabledTables); + final Set matchingTables = getTableDescriptors(new LinkedList<>(), + ntfp.getNamespace(), ntfp.getRegex(), ntfp.getTableNames(), false) + .stream() + .map(TableDescriptor::getTableName) + .collect(Collectors.toSet()); + final Set allEnabledTables = + tableStateManager.getTablesInStates(TableState.State.ENABLED); + final List targetTables = + new ArrayList<>(Sets.intersection(matchingTables, allEnabledTables)); + Collections.shuffle(targetTables); final List submittedPlanProcIds = new ArrayList<>(); - for (TableName table : allEnabledTables) { + for (TableName table : targetTables) { if (table.isSystemTable()) { continue; } @@ -3370,9 +3380,9 @@ public class HMaster extends HRegionServer implements MasterServices { } /** - * @return list of table table descriptors after filtering by regex and whether to include system - * tables, etc. - * @throws IOException + * Return a list of table table descriptors after applying any provided filter parameters. Note + * that the user-facing description of this filter logic is presented on the class-level javadoc + * of {@link NormalizeTableFilterParams}. */ private List getTableDescriptors(final List htds, final String namespace, final String regex, final List tableNameList, diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/MasterRpcServices.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/MasterRpcServices.java index b46abc8549e..3e7a2ac30a5 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/MasterRpcServices.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/MasterRpcServices.java @@ -49,6 +49,7 @@ import org.apache.hadoop.hbase.TableName; import org.apache.hadoop.hbase.UnknownRegionException; import org.apache.hadoop.hbase.client.ColumnFamilyDescriptor; import org.apache.hadoop.hbase.client.MasterSwitchType; +import org.apache.hadoop.hbase.client.NormalizeTableFilterParams; import org.apache.hadoop.hbase.client.Put; import org.apache.hadoop.hbase.client.RegionInfo; import org.apache.hadoop.hbase.client.RegionInfoBuilder; @@ -1932,7 +1933,14 @@ public class MasterRpcServices extends RSRpcServices implements NormalizeRequest request) throws ServiceException { rpcPreCheck("normalize"); try { - return NormalizeResponse.newBuilder().setNormalizerRan(master.normalizeRegions()).build(); + final NormalizeTableFilterParams ntfp = new NormalizeTableFilterParams.Builder() + .tableNames(ProtobufUtil.toTableNameList(request.getTableNamesList())) + .regex(request.hasRegex() ? request.getRegex() : null) + .namespace(request.hasNamespace() ? request.getNamespace() : null) + .build(); + return NormalizeResponse.newBuilder() + .setNormalizerRan(master.normalizeRegions(ntfp)) + .build(); } catch (IOException ex) { throw new ServiceException(ex); } diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/normalizer/TestSimpleRegionNormalizerOnCluster.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/normalizer/TestSimpleRegionNormalizerOnCluster.java index da4c52ea8bd..7d6ea20b1ba 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/normalizer/TestSimpleRegionNormalizerOnCluster.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/normalizer/TestSimpleRegionNormalizerOnCluster.java @@ -22,6 +22,7 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import java.io.IOException; +import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.concurrent.TimeUnit; @@ -36,6 +37,7 @@ import org.apache.hadoop.hbase.Size; import org.apache.hadoop.hbase.TableName; import org.apache.hadoop.hbase.Waiter.ExplainingPredicate; import org.apache.hadoop.hbase.client.Admin; +import org.apache.hadoop.hbase.client.NormalizeTableFilterParams; import org.apache.hadoop.hbase.client.Put; import org.apache.hadoop.hbase.client.RegionInfo; import org.apache.hadoop.hbase.client.Table; @@ -94,6 +96,9 @@ public class TestSimpleRegionNormalizerOnCluster { // no way for the test to set the regionId on a created region, so disable this feature. TEST_UTIL.getConfiguration().setInt("hbase.normalizer.merge.min_region_age.days", 0); + // disable the normalizer coming along and running via Chore + TEST_UTIL.getConfiguration().setInt("hbase.normalizer.period", Integer.MAX_VALUE); + TEST_UTIL.startMiniCluster(1); TestNamespaceAuditor.waitForQuotaInitialize(TEST_UTIL); admin = TEST_UTIL.getAdmin(); @@ -107,13 +112,13 @@ public class TestSimpleRegionNormalizerOnCluster { } @Before - public void before() throws IOException { + public void before() throws Exception { // disable the normalizer ahead of time, let the test enable it when its ready. admin.normalizerSwitch(false); } @Test - public void testHonorsNormalizerSwitch() throws IOException { + public void testHonorsNormalizerSwitch() throws Exception { assertFalse(admin.isNormalizerEnabled()); assertFalse(admin.normalize()); assertFalse(admin.normalizerSwitch(true)); @@ -220,14 +225,103 @@ public class TestSimpleRegionNormalizerOnCluster { } } - private static TableName buildTableNameForQuotaTest(final String methodName) throws IOException { + @Test + public void testHonorsNamespaceFilter() throws Exception { + final NamespaceDescriptor namespaceDescriptor = NamespaceDescriptor.create("ns").build(); + final TableName tn1 = TableName.valueOf("ns", name.getMethodName()); + final TableName tn2 = TableName.valueOf(name.getMethodName()); + + try { + admin.createNamespace(namespaceDescriptor); + final int tn1RegionCount = createTableBegsSplit(tn1, true, false); + final int tn2RegionCount = createTableBegsSplit(tn2, true, false); + final NormalizeTableFilterParams ntfp = new NormalizeTableFilterParams.Builder() + .namespace("ns") + .build(); + + assertFalse(admin.normalizerSwitch(true)); + assertTrue(admin.normalize(ntfp)); + waitForTableSplit(tn1, tn1RegionCount + 1); + + // confirm that tn1 has (tn1RegionCount + 1) number of regions. + // tn2 has tn2RegionCount number of regions because it's not a member of the target namespace. + assertEquals( + tn1 + " should have split.", + tn1RegionCount + 1, + MetaTableAccessor.getRegionCount(TEST_UTIL.getConnection(), tn1)); + waitForTableRegionCount(tn2, tn2RegionCount); + } finally { + dropIfExists(tn1); + dropIfExists(tn2); + } + } + + @Test + public void testHonorsPatternFilter() throws Exception { + final TableName tn1 = TableName.valueOf(name.getMethodName() + "1"); + final TableName tn2 = TableName.valueOf(name.getMethodName() + "2"); + + try { + final int tn1RegionCount = createTableBegsSplit(tn1, true, false); + final int tn2RegionCount = createTableBegsSplit(tn2, true, false); + final NormalizeTableFilterParams ntfp = new NormalizeTableFilterParams.Builder() + .regex(".*[1]") + .build(); + + assertFalse(admin.normalizerSwitch(true)); + assertTrue(admin.normalize(ntfp)); + waitForTableSplit(tn1, tn1RegionCount + 1); + + // confirm that tn1 has (tn1RegionCount + 1) number of regions. + // tn2 has tn2RegionCount number of regions because it fails filter. + assertEquals( + tn1 + " should have split.", + tn1RegionCount + 1, + MetaTableAccessor.getRegionCount(TEST_UTIL.getConnection(), tn1)); + waitForTableRegionCount(tn2, tn2RegionCount); + } finally { + dropIfExists(tn1); + dropIfExists(tn2); + } + } + + @Test + public void testHonorsNameFilter() throws Exception { + final TableName tn1 = TableName.valueOf(name.getMethodName() + "1"); + final TableName tn2 = TableName.valueOf(name.getMethodName() + "2"); + + try { + final int tn1RegionCount = createTableBegsSplit(tn1, true, false); + final int tn2RegionCount = createTableBegsSplit(tn2, true, false); + final NormalizeTableFilterParams ntfp = new NormalizeTableFilterParams.Builder() + .tableNames(Collections.singletonList(tn1)) + .build(); + + assertFalse(admin.normalizerSwitch(true)); + assertTrue(admin.normalize(ntfp)); + waitForTableSplit(tn1, tn1RegionCount + 1); + + // confirm that tn1 has (tn1RegionCount + 1) number of regions. + // tn2 has tn3RegionCount number of regions because it fails filter: + assertEquals( + tn1 + " should have split.", + tn1RegionCount + 1, + MetaTableAccessor.getRegionCount(TEST_UTIL.getConnection(), tn1)); + waitForTableRegionCount(tn2, tn2RegionCount); + } finally { + dropIfExists(tn1); + dropIfExists(tn2); + } + } + + private static TableName buildTableNameForQuotaTest(final String methodName) throws Exception { String nsp = "np2"; NamespaceDescriptor nspDesc = NamespaceDescriptor.create(nsp) .addConfiguration(TableNamespaceManager.KEY_MAX_REGIONS, "5") .addConfiguration(TableNamespaceManager.KEY_MAX_TABLES, "2").build(); admin.createNamespace(nspDesc); - return TableName.valueOf(nsp + TableName.NAMESPACE_DELIM + methodName); + return TableName.valueOf(nsp, methodName); } private static void waitForSkippedSplits(final HMaster master, @@ -347,12 +441,13 @@ public class TestSimpleRegionNormalizerOnCluster { */ private static int createTableBegsSplit(final TableName tableName, final boolean normalizerEnabled, final boolean isMergeEnabled) - throws IOException { + throws Exception { final List generatedRegions = generateTestData(tableName, 1, 1, 2, 3, 5); assertEquals(5, MetaTableAccessor.getRegionCount(TEST_UTIL.getConnection(), tableName)); admin.flush(tableName); - final TableDescriptor td = TableDescriptorBuilder.newBuilder(admin.getDescriptor(tableName)) + final TableDescriptor td = TableDescriptorBuilder + .newBuilder(admin.getDescriptor(tableName)) .setNormalizationEnabled(normalizerEnabled) .setMergeEnabled(isMergeEnabled) .build(); @@ -383,13 +478,14 @@ public class TestSimpleRegionNormalizerOnCluster { *
  • sum of sizes of first two regions < average
  • * */ - private static int createTableBegsMerge(final TableName tableName) throws IOException { + private static int createTableBegsMerge(final TableName tableName) throws Exception { // create 5 regions with sizes to trigger merge of small regions final List generatedRegions = generateTestData(tableName, 1, 1, 3, 3, 5); assertEquals(5, MetaTableAccessor.getRegionCount(TEST_UTIL.getConnection(), tableName)); admin.flush(tableName); - final TableDescriptor td = TableDescriptorBuilder.newBuilder(admin.getDescriptor(tableName)) + final TableDescriptor td = TableDescriptorBuilder + .newBuilder(admin.getDescriptor(tableName)) .setNormalizationEnabled(true) .build(); admin.modifyTable(td); @@ -411,7 +507,7 @@ public class TestSimpleRegionNormalizerOnCluster { return 5; } - private static void dropIfExists(final TableName tableName) throws IOException { + private static void dropIfExists(final TableName tableName) throws Exception { if (tableName != null && admin.tableExists(tableName)) { if (admin.isTableEnabled(tableName)) { admin.disableTable(tableName); diff --git a/hbase-shell/src/main/ruby/hbase/admin.rb b/hbase-shell/src/main/ruby/hbase/admin.rb index 1849aad3d75..6c29e548761 100644 --- a/hbase-shell/src/main/ruby/hbase/admin.rb +++ b/hbase-shell/src/main/ruby/hbase/admin.rb @@ -234,9 +234,54 @@ module Hbase #---------------------------------------------------------------------------------------------- # Requests region normalization for all configured tables in the cluster - # Returns true if normalizer ran successfully - def normalize - @admin.normalize + # Returns true if normalize request was successfully submitted + def normalize(*args) + builder = org.apache.hadoop.hbase.client.NormalizeTableFilterParams::Builder.new + args.each do |arg| + unless arg.is_a?(String) || arg.is_a?(Hash) + raise(ArgumentError, "#{arg.class} of #{arg.inspect} is not of Hash or String type") + end + + if arg.key?(TABLE_NAME) + table_name = arg.delete(TABLE_NAME) + unless table_name.is_a?(String) + raise(ArgumentError, "#{TABLE_NAME} must be of type String") + end + + builder.tableNames(java.util.Collections.singletonList(TableName.valueOf(table_name))) + elsif arg.key?(TABLE_NAMES) + table_names = arg.delete(TABLE_NAMES) + unless table_names.is_a?(Array) + raise(ArgumentError, "#{TABLE_NAMES} must be of type Array") + end + + table_name_list = java.util.LinkedList.new + table_names.each do |tn| + unless tn.is_a?(String) + raise(ArgumentError, "#{TABLE_NAMES} value #{tn} must be of type String") + end + + table_name_list.add(TableName.valueOf(tn)) + end + builder.tableNames(table_name_list) + elsif arg.key?(REGEX) + regex = arg.delete(REGEX) + raise(ArgumentError, "#{REGEX} must be of type String") unless regex.is_a?(String) + + builder.regex(regex) + elsif arg.key?(NAMESPACE) + namespace = arg.delete(NAMESPACE) + unless namespace.is_a?(String) + raise(ArgumentError, "#{NAMESPACE} must be of type String") + end + + builder.namespace(namespace) + else + raise(ArgumentError, "Unrecognized argument #{arg}") + end + end + ntfp = builder.build + @admin.normalize(ntfp) end #---------------------------------------------------------------------------------------------- diff --git a/hbase-shell/src/main/ruby/hbase_constants.rb b/hbase-shell/src/main/ruby/hbase_constants.rb index badd7d0caaa..712973b18ff 100644 --- a/hbase-shell/src/main/ruby/hbase_constants.rb +++ b/hbase-shell/src/main/ruby/hbase_constants.rb @@ -69,6 +69,7 @@ module HBaseConstants POLICY = 'POLICY'.freeze RAW = 'RAW'.freeze READ_TYPE = 'READ_TYPE'.freeze + REGEX = 'REGEX'.freeze REGIONSERVER = 'REGIONSERVER'.freeze REGION_REPLICATION = 'REGION_REPLICATION'.freeze REGION_REPLICA_ID = 'REGION_REPLICA_ID'.freeze @@ -87,6 +88,8 @@ module HBaseConstants STOPROW = 'STOPROW'.freeze TABLE = 'TABLE'.freeze TABLE_CFS = 'TABLE_CFS'.freeze + TABLE_NAME = 'TABLE_NAME'.freeze + TABLE_NAMES = 'TABLE_NAMES'.freeze TIMERANGE = 'TIMERANGE'.freeze TIMESTAMP = 'TIMESTAMP'.freeze TYPE = 'TYPE'.freeze diff --git a/hbase-shell/src/main/ruby/shell/commands/normalize.rb b/hbase-shell/src/main/ruby/shell/commands/normalize.rb index 2840e845bd6..70e524ae11c 100644 --- a/hbase-shell/src/main/ruby/shell/commands/normalize.rb +++ b/hbase-shell/src/main/ruby/shell/commands/normalize.rb @@ -22,18 +22,24 @@ module Shell class Normalize < Command def help <<-EOF -Trigger region normalizer for all tables which have NORMALIZATION_ENABLED flag set. Returns true - if normalizer ran successfully, false otherwise. Note that this command has no effect - if region normalizer is disabled (make sure it's turned on using 'normalizer_switch' command). +Trigger the region normalizer. Without arguments, invokes the normalizer without a table filter. +Any arguments are used to limit table selection. Returns true if the normalize request was +submitted successfully, false otherwise. Note that this command has no effect if region normalizer +is disabled (make sure it's turned on using 'normalizer_switch' command). - Examples: +Examples: - hbase> normalize + hbase> normalize + hbase> normalize TABLE_NAME => 'my_table' + hbase> normalize TABLE_NAMES => ['foo', 'bar', 'baz'] + hbase> normalize REGEX => 'my_.*' + hbase> normalize NAMESPACE => 'ns1' + hbase> normalize NAMESPACE => 'ns', REGEX => '*._BIG_.*' EOF end - def command - did_normalize_run = !!admin.normalize + def command(*args) + did_normalize_run = !!admin.normalize(*args) formatter.row([did_normalize_run.to_s]) did_normalize_run end diff --git a/hbase-thrift/src/main/java/org/apache/hadoop/hbase/thrift2/client/ThriftAdmin.java b/hbase-thrift/src/main/java/org/apache/hadoop/hbase/thrift2/client/ThriftAdmin.java index 1d0980b4611..9359d4360ee 100644 --- a/hbase-thrift/src/main/java/org/apache/hadoop/hbase/thrift2/client/ThriftAdmin.java +++ b/hbase-thrift/src/main/java/org/apache/hadoop/hbase/thrift2/client/ThriftAdmin.java @@ -1,5 +1,4 @@ -/** - * +/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information @@ -46,6 +45,7 @@ import org.apache.hadoop.hbase.client.CompactType; import org.apache.hadoop.hbase.client.CompactionState; import org.apache.hadoop.hbase.client.Connection; import org.apache.hadoop.hbase.client.LogQueryFilter; +import org.apache.hadoop.hbase.client.NormalizeTableFilterParams; import org.apache.hadoop.hbase.client.OnlineLogRecord; import org.apache.hadoop.hbase.client.RegionInfo; import org.apache.hadoop.hbase.client.SnapshotDescription; @@ -796,7 +796,7 @@ public class ThriftAdmin implements Admin { } @Override - public boolean normalize() { + public boolean normalize(NormalizeTableFilterParams ntfp) { throw new NotImplementedException("normalize not supported in ThriftAdmin"); }