Skip to content

Commit 6f5010e

Browse files
authored
HBASE-29005 Cannot split hbase:quota table when quota enforcement is enabled (#6501)
Signed-off-by: Bryan Beaudreault <bbeaudreault@apache.org>
1 parent 65e255b commit 6f5010e

2 files changed

Lines changed: 183 additions & 9 deletions

File tree

hbase-server/src/main/java/org/apache/hadoop/hbase/namespace/NamespaceStateManager.java

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,9 @@
4141
class NamespaceStateManager {
4242

4343
private static final Logger LOG = LoggerFactory.getLogger(NamespaceStateManager.class);
44-
private ConcurrentMap<String, NamespaceTableAndRegionInfo> nsStateCache;
45-
private MasterServices master;
44+
45+
private final ConcurrentMap<String, NamespaceTableAndRegionInfo> nsStateCache;
46+
private final MasterServices master;
4647
private volatile boolean initialized = false;
4748

4849
public NamespaceStateManager(MasterServices masterServices) {
@@ -76,6 +77,9 @@ public NamespaceTableAndRegionInfo getState(String name) {
7677
*/
7778
synchronized boolean checkAndUpdateNamespaceRegionCount(TableName name, byte[] regionName,
7879
int incr) throws IOException {
80+
if (name.isSystemTable()) {
81+
return true;
82+
}
7983
String namespace = name.getNamespaceAsString();
8084
NamespaceDescriptor nspdesc = getNamespaceDescriptor(namespace);
8185
if (nspdesc != null) {
@@ -84,17 +88,18 @@ synchronized boolean checkAndUpdateNamespaceRegionCount(TableName name, byte[] r
8488
int regionCount = currentStatus.getRegionCount();
8589
long maxRegionCount = TableNamespaceManager.getMaxRegions(nspdesc);
8690
if (incr > 0 && regionCount >= maxRegionCount) {
87-
LOG.warn("The region " + Bytes.toStringBinary(regionName)
88-
+ " cannot be created. The region count will exceed quota on the namespace. "
89-
+ "This may be transient, please retry later if there are any ongoing split"
90-
+ " operations in the namespace.");
91+
LOG.warn(
92+
"The region {} cannot be created. The region count will exceed quota on the namespace. "
93+
+ "This may be transient, please retry later if there are any ongoing split"
94+
+ " operations in the namespace.",
95+
Bytes.toStringBinary(regionName));
9196
return false;
9297
}
9398
NamespaceTableAndRegionInfo nsInfo = nsStateCache.get(namespace);
9499
if (nsInfo != null) {
95100
nsInfo.incRegionCountForTable(name, incr);
96101
} else {
97-
LOG.warn("Namespace state found null for namespace : " + namespace);
102+
LOG.warn("Namespace state found null for namespace : {}", namespace);
98103
}
99104
}
100105
return true;
@@ -110,6 +115,9 @@ synchronized boolean checkAndUpdateNamespaceRegionCount(TableName name, byte[] r
110115
*/
111116
synchronized void checkAndUpdateNamespaceRegionCount(TableName name, int incr)
112117
throws IOException {
118+
if (name.isSystemTable()) {
119+
return;
120+
}
113121
String namespace = name.getNamespaceAsString();
114122
NamespaceDescriptor nspdesc = getNamespaceDescriptor(namespace);
115123
if (nspdesc != null) {
@@ -133,13 +141,16 @@ private NamespaceDescriptor getNamespaceDescriptor(String namespaceAsString) {
133141
try {
134142
return this.master.getClusterSchema().getNamespace(namespaceAsString);
135143
} catch (IOException e) {
136-
LOG.error("Error while fetching namespace descriptor for namespace : " + namespaceAsString);
144+
LOG.error("Error while fetching namespace descriptor for namespace : {}", namespaceAsString);
137145
return null;
138146
}
139147
}
140148

141149
synchronized void checkAndUpdateNamespaceTableCount(TableName table, int numRegions)
142150
throws IOException {
151+
if (table.isSystemTable()) {
152+
return;
153+
}
143154
String namespace = table.getNamespaceAsString();
144155
NamespaceDescriptor nspdesc = getNamespaceDescriptor(namespace);
145156
if (nspdesc != null) {
@@ -186,6 +197,7 @@ void deleteNamespace(String namespace) {
186197
}
187198

188199
private void addTable(TableName tableName, int regionCount) throws IOException {
200+
assert !tableName.isSystemTable() : "Tracking of system tables is not supported";
189201
NamespaceTableAndRegionInfo info = nsStateCache.get(tableName.getNamespaceAsString());
190202
if (info != null) {
191203
info.addTable(tableName, regionCount);
@@ -196,6 +208,9 @@ private void addTable(TableName tableName, int regionCount) throws IOException {
196208
}
197209

198210
synchronized void removeTable(TableName tableName) {
211+
if (tableName.isSystemTable()) {
212+
return;
213+
}
199214
NamespaceTableAndRegionInfo info = nsStateCache.get(tableName.getNamespaceAsString());
200215
if (info != null) {
201216
info.removeTable(tableName);
@@ -219,7 +234,7 @@ private void initialize() throws IOException {
219234
addTable(table, regions.size());
220235
}
221236
}
222-
LOG.info("Finished updating state of " + nsStateCache.size() + " namespaces. ");
237+
LOG.info("Finished updating state of {} namespaces.", nsStateCache.size());
223238
initialized = true;
224239
}
225240

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
package org.apache.hadoop.hbase;
19+
20+
import static org.junit.Assert.assertEquals;
21+
22+
import java.util.HashMap;
23+
import java.util.List;
24+
import java.util.Map;
25+
import java.util.concurrent.TimeUnit;
26+
import java.util.stream.Collectors;
27+
import org.apache.hadoop.conf.Configuration;
28+
import org.apache.hadoop.hbase.client.AsyncAdmin;
29+
import org.apache.hadoop.hbase.client.AsyncConnection;
30+
import org.apache.hadoop.hbase.client.ConnectionFactory;
31+
import org.apache.hadoop.hbase.client.RegionInfo;
32+
import org.apache.hadoop.hbase.quotas.QuotaUtil;
33+
import org.apache.hadoop.hbase.testclassification.LargeTests;
34+
import org.apache.hadoop.hbase.testclassification.MiscTests;
35+
import org.apache.hadoop.hbase.util.Bytes;
36+
import org.junit.ClassRule;
37+
import org.junit.Rule;
38+
import org.junit.Test;
39+
import org.junit.experimental.categories.Category;
40+
import org.junit.rules.ExternalResource;
41+
import org.junit.rules.RuleChain;
42+
import org.junit.rules.TestRule;
43+
import org.junit.runner.RunWith;
44+
import org.junit.runners.Parameterized;
45+
import org.slf4j.Logger;
46+
import org.slf4j.LoggerFactory;
47+
48+
/**
49+
* Test that we can split and merge the quota table given the presence of various configuration
50+
* settings.
51+
*/
52+
@Category({ MiscTests.class, LargeTests.class })
53+
@RunWith(Parameterized.class)
54+
public class TestSplitMergeQuotaTable {
55+
56+
private static final Logger LOG = LoggerFactory.getLogger(TestSplitMergeQuotaTable.class);
57+
58+
@ClassRule
59+
public static final HBaseClassTestRule CLASS_RULE =
60+
HBaseClassTestRule.forClass(TestSplitMergeQuotaTable.class);
61+
62+
@Parameterized.Parameters(name = "{1}")
63+
public static Object[][] params() {
64+
Map<String, String> quotasDisabledMap = new HashMap<>();
65+
quotasDisabledMap.put(QuotaUtil.QUOTA_CONF_KEY, "false");
66+
Map<String, String> quotasEnabledMap = new HashMap<>();
67+
quotasEnabledMap.put(QuotaUtil.QUOTA_CONF_KEY, "true");
68+
return new Object[][] { { quotasDisabledMap }, { quotasEnabledMap }, };
69+
}
70+
71+
private final TableName tableName = QuotaUtil.QUOTA_TABLE_NAME;
72+
private final MiniClusterRule miniClusterRule;
73+
74+
@Rule
75+
public final RuleChain ruleChain;
76+
77+
public TestSplitMergeQuotaTable(Map<String, String> configMap) {
78+
this.miniClusterRule = MiniClusterRule.newBuilder().setConfiguration(() -> {
79+
Configuration conf = HBaseConfiguration.create();
80+
conf.setInt(HConstants.HBASE_CLIENT_META_OPERATION_TIMEOUT, 1000);
81+
conf.setInt(HConstants.HBASE_CLIENT_RETRIES_NUMBER, 2);
82+
configMap.forEach(conf::set);
83+
return conf;
84+
}).build();
85+
TestRule ensureQuotaTableRule = new ExternalResource() {
86+
@Override
87+
protected void before() throws Throwable {
88+
try (AsyncConnection conn = ConnectionFactory
89+
.createAsyncConnection(miniClusterRule.getTestingUtility().getConfiguration())
90+
.get(30, TimeUnit.SECONDS)) {
91+
AsyncAdmin admin = conn.getAdmin();
92+
if (!admin.tableExists(QuotaUtil.QUOTA_TABLE_NAME).get(30, TimeUnit.SECONDS)) {
93+
miniClusterRule.getTestingUtility().getHBaseCluster().getMaster()
94+
.createSystemTable(QuotaUtil.QUOTA_TABLE_DESC);
95+
}
96+
}
97+
}
98+
};
99+
this.ruleChain = RuleChain.outerRule(miniClusterRule).around(ensureQuotaTableRule);
100+
}
101+
102+
@Test
103+
public void testSplitMerge() throws Exception {
104+
HBaseTestingUtility util = miniClusterRule.getTestingUtility();
105+
util.waitTableAvailable(tableName, 30_000);
106+
try (AsyncConnection conn =
107+
ConnectionFactory.createAsyncConnection(util.getConfiguration()).get(30, TimeUnit.SECONDS)) {
108+
AsyncAdmin admin = conn.getAdmin();
109+
admin.split(tableName, Bytes.toBytes(0x10)).get(30, TimeUnit.SECONDS);
110+
util.waitFor(30_000, new Waiter.ExplainingPredicate<Exception>() {
111+
112+
@Override
113+
public boolean evaluate() throws Exception {
114+
// can potentially observe the parent and both children via this interface.
115+
return admin.getRegions(tableName)
116+
.thenApply(val -> val.stream()
117+
.filter(info -> info.getReplicaId() == RegionInfo.DEFAULT_REPLICA_ID)
118+
.collect(Collectors.toList()))
119+
.get(30, TimeUnit.SECONDS).size() > 1;
120+
}
121+
122+
@Override
123+
public String explainFailure() {
124+
return "Split has not finished yet";
125+
}
126+
});
127+
util.waitUntilNoRegionsInTransition();
128+
List<RegionInfo> regionInfos = admin.getRegions(tableName)
129+
.thenApply(
130+
val -> val.stream().filter(info -> info.getReplicaId() == RegionInfo.DEFAULT_REPLICA_ID)
131+
.collect(Collectors.toList()))
132+
.get(30, TimeUnit.SECONDS);
133+
assertEquals(2, regionInfos.size());
134+
LOG.info("{}", regionInfos);
135+
admin
136+
.mergeRegions(
137+
regionInfos.stream().map(RegionInfo::getRegionName).collect(Collectors.toList()), false)
138+
.get(30, TimeUnit.SECONDS);
139+
util.waitFor(30000, new Waiter.ExplainingPredicate<Exception>() {
140+
141+
@Override
142+
public boolean evaluate() throws Exception {
143+
// can potentially observe the parent and both children via this interface.
144+
return admin.getRegions(tableName)
145+
.thenApply(val -> val.stream()
146+
.filter(info -> info.getReplicaId() == RegionInfo.DEFAULT_REPLICA_ID)
147+
.collect(Collectors.toList()))
148+
.get(30, TimeUnit.SECONDS).size() == 1;
149+
}
150+
151+
@Override
152+
public String explainFailure() {
153+
return "Merge has not finished yet";
154+
}
155+
});
156+
assertEquals(1, admin.getRegions(tableName).get(30, TimeUnit.SECONDS).size());
157+
}
158+
}
159+
}

0 commit comments

Comments
 (0)