This is an automated email from the ASF dual-hosted git repository.
ndimiduk pushed a commit to branch branch-2.5
in repository https://gitbox.apache.org/repos/asf/hbase.git
The following commit(s) were added to refs/heads/branch-2.5 by this push:
new 1c15f55986c HBASE-29005 Cannot split hbase:quota table when quota
enforcement is enabled (#6501)
1c15f55986c is described below
commit 1c15f55986cbc3d6bc28a468049ba928d53df661
Author: Nick Dimiduk <[email protected]>
AuthorDate: Thu Dec 5 09:49:24 2024 +0100
HBASE-29005 Cannot split hbase:quota table when quota enforcement is
enabled (#6501)
Signed-off-by: Bryan Beaudreault <[email protected]>
---
.../hbase/namespace/NamespaceStateManager.java | 33 +++--
.../hadoop/hbase/TestSplitMergeQuotaTable.java | 159 +++++++++++++++++++++
2 files changed, 183 insertions(+), 9 deletions(-)
diff --git
a/hbase-server/src/main/java/org/apache/hadoop/hbase/namespace/NamespaceStateManager.java
b/hbase-server/src/main/java/org/apache/hadoop/hbase/namespace/NamespaceStateManager.java
index d95378f9b86..0f2f50db048 100644
---
a/hbase-server/src/main/java/org/apache/hadoop/hbase/namespace/NamespaceStateManager.java
+++
b/hbase-server/src/main/java/org/apache/hadoop/hbase/namespace/NamespaceStateManager.java
@@ -41,8 +41,9 @@ import org.slf4j.LoggerFactory;
class NamespaceStateManager {
private static final Logger LOG =
LoggerFactory.getLogger(NamespaceStateManager.class);
- private ConcurrentMap<String, NamespaceTableAndRegionInfo> nsStateCache;
- private MasterServices master;
+
+ private final ConcurrentMap<String, NamespaceTableAndRegionInfo>
nsStateCache;
+ private final MasterServices master;
private volatile boolean initialized = false;
public NamespaceStateManager(MasterServices masterServices) {
@@ -76,6 +77,9 @@ class NamespaceStateManager {
*/
synchronized boolean checkAndUpdateNamespaceRegionCount(TableName name,
byte[] regionName,
int incr) throws IOException {
+ if (name.isSystemTable()) {
+ return true;
+ }
String namespace = name.getNamespaceAsString();
NamespaceDescriptor nspdesc = getNamespaceDescriptor(namespace);
if (nspdesc != null) {
@@ -84,17 +88,18 @@ class NamespaceStateManager {
int regionCount = currentStatus.getRegionCount();
long maxRegionCount = TableNamespaceManager.getMaxRegions(nspdesc);
if (incr > 0 && regionCount >= maxRegionCount) {
- LOG.warn("The region " + Bytes.toStringBinary(regionName)
- + " cannot be created. The region count will exceed quota on the
namespace. "
- + "This may be transient, please retry later if there are any
ongoing split"
- + " operations in the namespace.");
+ LOG.warn(
+ "The region {} cannot be created. The region count will exceed
quota on the namespace. "
+ + "This may be transient, please retry later if there are any
ongoing split"
+ + " operations in the namespace.",
+ Bytes.toStringBinary(regionName));
return false;
}
NamespaceTableAndRegionInfo nsInfo = nsStateCache.get(namespace);
if (nsInfo != null) {
nsInfo.incRegionCountForTable(name, incr);
} else {
- LOG.warn("Namespace state found null for namespace : " + namespace);
+ LOG.warn("Namespace state found null for namespace : {}", namespace);
}
}
return true;
@@ -110,6 +115,9 @@ class NamespaceStateManager {
*/
synchronized void checkAndUpdateNamespaceRegionCount(TableName name, int
incr)
throws IOException {
+ if (name.isSystemTable()) {
+ return;
+ }
String namespace = name.getNamespaceAsString();
NamespaceDescriptor nspdesc = getNamespaceDescriptor(namespace);
if (nspdesc != null) {
@@ -133,13 +141,16 @@ class NamespaceStateManager {
try {
return this.master.getClusterSchema().getNamespace(namespaceAsString);
} catch (IOException e) {
- LOG.error("Error while fetching namespace descriptor for namespace : " +
namespaceAsString);
+ LOG.error("Error while fetching namespace descriptor for namespace :
{}", namespaceAsString);
return null;
}
}
synchronized void checkAndUpdateNamespaceTableCount(TableName table, int
numRegions)
throws IOException {
+ if (table.isSystemTable()) {
+ return;
+ }
String namespace = table.getNamespaceAsString();
NamespaceDescriptor nspdesc = getNamespaceDescriptor(namespace);
if (nspdesc != null) {
@@ -186,6 +197,7 @@ class NamespaceStateManager {
}
private void addTable(TableName tableName, int regionCount) throws
IOException {
+ assert !tableName.isSystemTable() : "Tracking of system tables is not
supported";
NamespaceTableAndRegionInfo info =
nsStateCache.get(tableName.getNamespaceAsString());
if (info != null) {
info.addTable(tableName, regionCount);
@@ -196,6 +208,9 @@ class NamespaceStateManager {
}
synchronized void removeTable(TableName tableName) {
+ if (tableName.isSystemTable()) {
+ return;
+ }
NamespaceTableAndRegionInfo info =
nsStateCache.get(tableName.getNamespaceAsString());
if (info != null) {
info.removeTable(tableName);
@@ -219,7 +234,7 @@ class NamespaceStateManager {
addTable(table, regions.size());
}
}
- LOG.info("Finished updating state of " + nsStateCache.size() + "
namespaces. ");
+ LOG.info("Finished updating state of {} namespaces.", nsStateCache.size());
initialized = true;
}
diff --git
a/hbase-server/src/test/java/org/apache/hadoop/hbase/TestSplitMergeQuotaTable.java
b/hbase-server/src/test/java/org/apache/hadoop/hbase/TestSplitMergeQuotaTable.java
new file mode 100644
index 00000000000..1e19c0ca1dd
--- /dev/null
+++
b/hbase-server/src/test/java/org/apache/hadoop/hbase/TestSplitMergeQuotaTable.java
@@ -0,0 +1,159 @@
+/*
+ * 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;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.hbase.client.AsyncAdmin;
+import org.apache.hadoop.hbase.client.AsyncConnection;
+import org.apache.hadoop.hbase.client.ConnectionFactory;
+import org.apache.hadoop.hbase.client.RegionInfo;
+import org.apache.hadoop.hbase.quotas.QuotaUtil;
+import org.apache.hadoop.hbase.testclassification.LargeTests;
+import org.apache.hadoop.hbase.testclassification.MiscTests;
+import org.apache.hadoop.hbase.util.Bytes;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.rules.ExternalResource;
+import org.junit.rules.RuleChain;
+import org.junit.rules.TestRule;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Test that we can split and merge the quota table given the presence of
various configuration
+ * settings.
+ */
+@Category({ MiscTests.class, LargeTests.class })
+@RunWith(Parameterized.class)
+public class TestSplitMergeQuotaTable {
+
+ private static final Logger LOG =
LoggerFactory.getLogger(TestSplitMergeQuotaTable.class);
+
+ @ClassRule
+ public static final HBaseClassTestRule CLASS_RULE =
+ HBaseClassTestRule.forClass(TestSplitMergeQuotaTable.class);
+
+ @Parameterized.Parameters(name = "{1}")
+ public static Object[][] params() {
+ Map<String, String> quotasDisabledMap = new HashMap<>();
+ quotasDisabledMap.put(QuotaUtil.QUOTA_CONF_KEY, "false");
+ Map<String, String> quotasEnabledMap = new HashMap<>();
+ quotasEnabledMap.put(QuotaUtil.QUOTA_CONF_KEY, "true");
+ return new Object[][] { { quotasDisabledMap }, { quotasEnabledMap }, };
+ }
+
+ private final TableName tableName = QuotaUtil.QUOTA_TABLE_NAME;
+ private final MiniClusterRule miniClusterRule;
+
+ @Rule
+ public final RuleChain ruleChain;
+
+ public TestSplitMergeQuotaTable(Map<String, String> configMap) {
+ this.miniClusterRule = MiniClusterRule.newBuilder().setConfiguration(() ->
{
+ Configuration conf = HBaseConfiguration.create();
+ conf.setInt(HConstants.HBASE_CLIENT_META_OPERATION_TIMEOUT, 1000);
+ conf.setInt(HConstants.HBASE_CLIENT_RETRIES_NUMBER, 2);
+ configMap.forEach(conf::set);
+ return conf;
+ }).build();
+ TestRule ensureQuotaTableRule = new ExternalResource() {
+ @Override
+ protected void before() throws Throwable {
+ try (AsyncConnection conn = ConnectionFactory
+
.createAsyncConnection(miniClusterRule.getTestingUtility().getConfiguration())
+ .get(30, TimeUnit.SECONDS)) {
+ AsyncAdmin admin = conn.getAdmin();
+ if (!admin.tableExists(QuotaUtil.QUOTA_TABLE_NAME).get(30,
TimeUnit.SECONDS)) {
+ miniClusterRule.getTestingUtility().getHBaseCluster().getMaster()
+ .createSystemTable(QuotaUtil.QUOTA_TABLE_DESC);
+ }
+ }
+ }
+ };
+ this.ruleChain =
RuleChain.outerRule(miniClusterRule).around(ensureQuotaTableRule);
+ }
+
+ @Test
+ public void testSplitMerge() throws Exception {
+ HBaseTestingUtility util = miniClusterRule.getTestingUtility();
+ util.waitTableAvailable(tableName, 30_000);
+ try (AsyncConnection conn =
+ ConnectionFactory.createAsyncConnection(util.getConfiguration()).get(30,
TimeUnit.SECONDS)) {
+ AsyncAdmin admin = conn.getAdmin();
+ admin.split(tableName, Bytes.toBytes(0x10)).get(30, TimeUnit.SECONDS);
+ util.waitFor(30_000, new Waiter.ExplainingPredicate<Exception>() {
+
+ @Override
+ public boolean evaluate() throws Exception {
+ // can potentially observe the parent and both children via this
interface.
+ return admin.getRegions(tableName)
+ .thenApply(val -> val.stream()
+ .filter(info -> info.getReplicaId() ==
RegionInfo.DEFAULT_REPLICA_ID)
+ .collect(Collectors.toList()))
+ .get(30, TimeUnit.SECONDS).size() > 1;
+ }
+
+ @Override
+ public String explainFailure() {
+ return "Split has not finished yet";
+ }
+ });
+ util.waitUntilNoRegionsInTransition();
+ List<RegionInfo> regionInfos = admin.getRegions(tableName)
+ .thenApply(
+ val -> val.stream().filter(info -> info.getReplicaId() ==
RegionInfo.DEFAULT_REPLICA_ID)
+ .collect(Collectors.toList()))
+ .get(30, TimeUnit.SECONDS);
+ assertEquals(2, regionInfos.size());
+ LOG.info("{}", regionInfos);
+ admin
+ .mergeRegions(
+
regionInfos.stream().map(RegionInfo::getRegionName).collect(Collectors.toList()),
false)
+ .get(30, TimeUnit.SECONDS);
+ util.waitFor(30000, new Waiter.ExplainingPredicate<Exception>() {
+
+ @Override
+ public boolean evaluate() throws Exception {
+ // can potentially observe the parent and both children via this
interface.
+ return admin.getRegions(tableName)
+ .thenApply(val -> val.stream()
+ .filter(info -> info.getReplicaId() ==
RegionInfo.DEFAULT_REPLICA_ID)
+ .collect(Collectors.toList()))
+ .get(30, TimeUnit.SECONDS).size() == 1;
+ }
+
+ @Override
+ public String explainFailure() {
+ return "Merge has not finished yet";
+ }
+ });
+ assertEquals(1, admin.getRegions(tableName).get(30,
TimeUnit.SECONDS).size());
+ }
+ }
+}