This is an automated email from the ASF dual-hosted git repository.
cconnell pushed a commit to branch branch-2
in repository https://gitbox.apache.org/repos/asf/hbase.git
The following commit(s) were added to refs/heads/branch-2 by this push:
new 51d1e5e658b HBASE-29387: Reload quotas from hbase:quota table when
changes are made (#7091)
51d1e5e658b is described below
commit 51d1e5e658b15506833ac9b6285f3f1040663dba
Author: Charles Connell <[email protected]>
AuthorDate: Thu Jun 26 08:00:07 2025 -0400
HBASE-29387: Reload quotas from hbase:quota table when changes are made
(#7091)
Signed off by: Ray Mattingly <[email protected]>
---
.../src/main/protobuf/MasterProcedure.proto | 5 +
.../apache/hadoop/hbase/executor/EventType.java | 8 +-
.../apache/hadoop/hbase/executor/ExecutorType.java | 3 +-
.../org/apache/hadoop/hbase/master/HMaster.java | 7 +
.../hadoop/hbase/master/MasterRpcServices.java | 4 +-
.../master/procedure/ReloadQuotasProcedure.java | 124 ++++++++++++++
.../master/procedure/ServerProcedureInterface.java | 8 +-
.../hadoop/hbase/master/procedure/ServerQueue.java | 1 +
.../org/apache/hadoop/hbase/quotas/QuotaCache.java | 17 +-
.../hbase/quotas/RegionServerRpcQuotaManager.java | 13 +-
.../hadoop/hbase/regionserver/HRegionServer.java | 6 +
.../hbase/regionserver/ReloadQuotasCallable.java | 46 ++++++
.../procedure/TestReloadQuotasProcedure.java | 183 +++++++++++++++++++++
13 files changed, 412 insertions(+), 13 deletions(-)
diff --git a/hbase-protocol-shaded/src/main/protobuf/MasterProcedure.proto
b/hbase-protocol-shaded/src/main/protobuf/MasterProcedure.proto
index 15b93675574..024cb7b8b00 100644
--- a/hbase-protocol-shaded/src/main/protobuf/MasterProcedure.proto
+++ b/hbase-protocol-shaded/src/main/protobuf/MasterProcedure.proto
@@ -725,3 +725,8 @@ enum CloseTableRegionsProcedureState {
message CloseTableRegionsProcedureStateData {
required TableName table_name = 1;
}
+
+message ReloadQuotasProcedureStateData {
+ required ServerName target_server = 1;
+ optional ForeignExceptionMessage error = 2;
+}
diff --git
a/hbase-server/src/main/java/org/apache/hadoop/hbase/executor/EventType.java
b/hbase-server/src/main/java/org/apache/hadoop/hbase/executor/EventType.java
index 2a6d8e46796..a42795e045d 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/executor/EventType.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/executor/EventType.java
@@ -291,7 +291,13 @@ public enum EventType {
* RS flush regions.<br>
* RS_FLUSH_OPERATIONS
*/
- RS_FLUSH_REGIONS(89, ExecutorType.RS_FLUSH_OPERATIONS);
+ RS_FLUSH_REGIONS(89, ExecutorType.RS_FLUSH_OPERATIONS),
+
+ /**
+ * RS reload quotas.<br>
+ * RS_RELOAD_QUOTAS
+ */
+ RS_RELOAD_QUOTAS(90, ExecutorType.RS_RELOAD_QUOTAS_OPERATIONS);
private final int code;
private final ExecutorType executor;
diff --git
a/hbase-server/src/main/java/org/apache/hadoop/hbase/executor/ExecutorType.java
b/hbase-server/src/main/java/org/apache/hadoop/hbase/executor/ExecutorType.java
index feb703a4e58..87f3d8ad9db 100644
---
a/hbase-server/src/main/java/org/apache/hadoop/hbase/executor/ExecutorType.java
+++
b/hbase-server/src/main/java/org/apache/hadoop/hbase/executor/ExecutorType.java
@@ -54,7 +54,8 @@ public enum ExecutorType {
RS_IN_MEMORY_COMPACTION(34),
RS_CLAIM_REPLICATION_QUEUE(35),
RS_SNAPSHOT_OPERATIONS(36),
- RS_FLUSH_OPERATIONS(37);
+ RS_FLUSH_OPERATIONS(37),
+ RS_RELOAD_QUOTAS_OPERATIONS(38);
ExecutorType(int value) {
}
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 817977ff896..e099760a7a8 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
@@ -163,6 +163,7 @@ import
org.apache.hadoop.hbase.master.procedure.ModifyTableProcedure;
import org.apache.hadoop.hbase.master.procedure.ProcedurePrepareLatch;
import org.apache.hadoop.hbase.master.procedure.ProcedureSyncWait;
import org.apache.hadoop.hbase.master.procedure.RSProcedureDispatcher;
+import org.apache.hadoop.hbase.master.procedure.ReloadQuotasProcedure;
import org.apache.hadoop.hbase.master.procedure.ReopenTableRegionsProcedure;
import org.apache.hadoop.hbase.master.procedure.ServerCrashProcedure;
import org.apache.hadoop.hbase.master.procedure.TruncateRegionProcedure;
@@ -2921,6 +2922,12 @@ public class HMaster extends HRegionServer implements
MasterServices {
}
}
+ public void reloadRegionServerQuotas() {
+ // multiple reloads are harmless, so no need for NonceProcedureRunnable
+ getLiveRegionServers()
+ .forEach(sn -> procedureExecutor.submitProcedure(new
ReloadQuotasProcedure(sn)));
+ }
+
public ClusterMetrics getClusterMetricsWithoutCoprocessor() throws
InterruptedIOException {
return getClusterMetricsWithoutCoprocessor(EnumSet.allOf(Option.class));
}
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 1020f56a74a..194cdcca65b 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
@@ -1812,7 +1812,9 @@ public class MasterRpcServices extends RSRpcServices
public SetQuotaResponse setQuota(RpcController c, SetQuotaRequest req)
throws ServiceException {
try {
master.checkInitialized();
- return master.getMasterQuotaManager().setQuota(req);
+ SetQuotaResponse response = master.getMasterQuotaManager().setQuota(req);
+ master.reloadRegionServerQuotas();
+ return response;
} catch (Exception e) {
throw new ServiceException(e);
}
diff --git
a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/procedure/ReloadQuotasProcedure.java
b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/procedure/ReloadQuotasProcedure.java
new file mode 100644
index 00000000000..5b76967c199
--- /dev/null
+++
b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/procedure/ReloadQuotasProcedure.java
@@ -0,0 +1,124 @@
+/*
+ * 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.master.procedure;
+
+import java.io.IOException;
+import java.util.Optional;
+import org.apache.hadoop.hbase.ServerName;
+import org.apache.hadoop.hbase.procedure2.ProcedureStateSerializer;
+import org.apache.hadoop.hbase.procedure2.RemoteProcedureDispatcher;
+import org.apache.hadoop.hbase.regionserver.ReloadQuotasCallable;
+import org.apache.hadoop.hbase.util.ForeignExceptionUtil;
+import org.apache.yetus.audience.InterfaceAudience;
+
+import org.apache.hbase.thirdparty.com.google.common.base.Throwables;
+
+import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil;
+import org.apache.hadoop.hbase.shaded.protobuf.generated.ErrorHandlingProtos;
+import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProcedureProtos;
+
[email protected]
+public class ReloadQuotasProcedure extends ServerRemoteProcedure
+ implements ServerProcedureInterface {
+
+ public ReloadQuotasProcedure() {
+ }
+
+ public ReloadQuotasProcedure(ServerName targetServer) {
+ this.targetServer = targetServer;
+ }
+
+ @Override
+ protected boolean complete(MasterProcedureEnv env, Throwable error) {
+ if (error != null && containsCause(error, ClassNotFoundException.class)) {
+ LOG.warn(
+ "Failed to reload quotas on server {}, but will allow this procedure
to complete. The RegionServer may be on an older version of HBase that does not
support ReloadQuotasProcedure.",
+ targetServer);
+ return true;
+ } else if (error != null) {
+ LOG.error("Failed to reload quotas on server {}", targetServer, error);
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ /** visibility for testing */
+ boolean containsCause(Throwable t, Class<? extends Throwable> causeClass) {
+ return
Throwables.getCausalChain(t).stream().anyMatch(causeClass::isInstance);
+ }
+
+ @Override
+ protected void rollback(MasterProcedureEnv env) throws IOException,
InterruptedException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ protected boolean abort(MasterProcedureEnv env) {
+ return false;
+ }
+
+ @Override
+ protected void serializeStateData(ProcedureStateSerializer serializer)
throws IOException {
+ MasterProcedureProtos.ReloadQuotasProcedureStateData.Builder builder =
+ MasterProcedureProtos.ReloadQuotasProcedureStateData.newBuilder();
+ if (this.remoteError != null) {
+ ErrorHandlingProtos.ForeignExceptionMessage fem =
+ ForeignExceptionUtil.toProtoForeignException(remoteError);
+ builder.setError(fem);
+ }
+
serializer.serialize(builder.setTargetServer(ProtobufUtil.toServerName(targetServer)).build());
+ }
+
+ @Override
+ protected void deserializeStateData(ProcedureStateSerializer serializer)
throws IOException {
+ MasterProcedureProtos.ReloadQuotasProcedureStateData data =
+
serializer.deserialize(MasterProcedureProtos.ReloadQuotasProcedureStateData.class);
+ this.targetServer = ProtobufUtil.toServerName(data.getTargetServer());
+ if (data.hasError()) {
+ this.remoteError = ForeignExceptionUtil.toException(data.getError());
+ }
+ }
+
+ @Override
+ public Optional<RemoteProcedureDispatcher.RemoteOperation>
remoteCallBuild(MasterProcedureEnv env,
+ ServerName serverName) {
+ if (!serverName.equals(targetServer)) {
+ throw new IllegalArgumentException(
+ "ReloadQuotasProcedure#remoteCallBuild called with unexpected
serverName: " + serverName
+ + " != " + targetServer);
+ }
+ return Optional.of(new RSProcedureDispatcher.ServerOperation(this,
getProcId(),
+ ReloadQuotasCallable.class, new byte[] {},
env.getMasterServices().getMasterActiveTime()));
+ }
+
+ @Override
+ public ServerName getServerName() {
+ return targetServer;
+ }
+
+ @Override
+ public boolean hasMetaTableRegion() {
+ return false;
+ }
+
+ @Override
+ public ServerOperationType getServerOperationType() {
+ return ServerOperationType.RELOAD_QUOTAS;
+ }
+}
diff --git
a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/procedure/ServerProcedureInterface.java
b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/procedure/ServerProcedureInterface.java
index 1d7dcb77e09..e73b23a3f96 100644
---
a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/procedure/ServerProcedureInterface.java
+++
b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/procedure/ServerProcedureInterface.java
@@ -18,6 +18,7 @@
package org.apache.hadoop.hbase.master.procedure;
import org.apache.hadoop.hbase.ServerName;
+import org.apache.hadoop.hbase.quotas.QuotaCache;
import org.apache.yetus.audience.InterfaceAudience;
/**
@@ -55,7 +56,12 @@ public interface ServerProcedureInterface {
/**
* send verify snapshot request to region server and handle the response
*/
- VERIFY_SNAPSHOT
+ VERIFY_SNAPSHOT,
+
+ /**
+ * Re-read the hbase:quotas table and update {@link QuotaCache}.
+ */
+ RELOAD_QUOTAS,
}
/** Returns Name of this server instance. */
diff --git
a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/procedure/ServerQueue.java
b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/procedure/ServerQueue.java
index a3144cc157a..57912f41903 100644
---
a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/procedure/ServerQueue.java
+++
b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/procedure/ServerQueue.java
@@ -43,6 +43,7 @@ class ServerQueue extends Queue<ServerName> {
case CLAIM_REPLICATION_QUEUES:
case CLAIM_REPLICATION_QUEUE_REMOTE:
case VERIFY_SNAPSHOT:
+ case RELOAD_QUOTAS:
return false;
default:
break;
diff --git
a/hbase-server/src/main/java/org/apache/hadoop/hbase/quotas/QuotaCache.java
b/hbase-server/src/main/java/org/apache/hadoop/hbase/quotas/QuotaCache.java
index cecda2a154c..d77f6219ae5 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/quotas/QuotaCache.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/quotas/QuotaCache.java
@@ -76,8 +76,8 @@ public class QuotaCache implements Stoppable {
// from the perspective of user quotas
public static final String QUOTA_USER_REQUEST_ATTRIBUTE_OVERRIDE_KEY =
"hbase.quota.user.override.key";
- private static final int REFRESH_DEFAULT_PERIOD = 5 * 60000; // 5min
- private static final int EVICT_PERIOD_FACTOR = 5; // N *
REFRESH_DEFAULT_PERIOD
+ private static final int REFRESH_DEFAULT_PERIOD = 43_200_000; // 12 hours
+ private static final int EVICT_PERIOD_FACTOR = 5;
// for testing purpose only, enforce the cache to be always refreshed
static boolean TEST_FORCE_REFRESH = false;
@@ -109,8 +109,12 @@ public class QuotaCache implements Stoppable {
public void start() throws IOException {
stopped = false;
- // TODO: This will be replaced once we have the notification bus ready.
Configuration conf = rsServices.getConfiguration();
+ // Refresh the cache every 12 hours, and every time a quota is changed,
and every time a
+ // configuration
+ // reload is triggered. Periodic reloads are kept to a minimum to avoid
flooding the
+ // RegionServer
+ // holding the hbase:quota table with requests.
int period = conf.getInt(REFRESH_CONF_KEY, REFRESH_DEFAULT_PERIOD);
refreshChore = new QuotaRefresherChore(conf, period, this);
rsServices.getChoreService().scheduleChore(refreshChore);
@@ -385,18 +389,15 @@ public class QuotaCache implements Stoppable {
private <K, V extends QuotaState> void fetch(final String type,
final ConcurrentMap<K, V> quotasMap, final Fetcher<K, V> fetcher) {
long now = EnvironmentEdgeManager.currentTime();
- long refreshPeriod = getPeriod();
- long evictPeriod = refreshPeriod * EVICT_PERIOD_FACTOR;
-
+ long evictPeriod = getPeriod() * EVICT_PERIOD_FACTOR;
// Find the quota entries to update
List<Get> gets = new ArrayList<>();
List<K> toRemove = new ArrayList<>();
for (Map.Entry<K, V> entry : quotasMap.entrySet()) {
- long lastUpdate = entry.getValue().getLastUpdate();
long lastQuery = entry.getValue().getLastQuery();
if (lastQuery > 0 && (now - lastQuery) >= evictPeriod) {
toRemove.add(entry.getKey());
- } else if (TEST_FORCE_REFRESH || (now - lastUpdate) >= refreshPeriod) {
+ } else {
gets.add(fetcher.makeGet(entry));
}
}
diff --git
a/hbase-server/src/main/java/org/apache/hadoop/hbase/quotas/RegionServerRpcQuotaManager.java
b/hbase-server/src/main/java/org/apache/hadoop/hbase/quotas/RegionServerRpcQuotaManager.java
index 7c23666490d..03fbfde47a1 100644
---
a/hbase-server/src/main/java/org/apache/hadoop/hbase/quotas/RegionServerRpcQuotaManager.java
+++
b/hbase-server/src/main/java/org/apache/hadoop/hbase/quotas/RegionServerRpcQuotaManager.java
@@ -22,8 +22,10 @@ import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
+import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.TableDescriptor;
+import org.apache.hadoop.hbase.conf.ConfigurationObserver;
import org.apache.hadoop.hbase.ipc.RpcScheduler;
import org.apache.hadoop.hbase.ipc.RpcServer;
import org.apache.hadoop.hbase.regionserver.Region;
@@ -47,7 +49,7 @@ import
org.apache.hadoop.hbase.shaded.protobuf.generated.ClientProtos;
*/
@InterfaceAudience.Private
@InterfaceStability.Evolving
-public class RegionServerRpcQuotaManager implements RpcQuotaManager {
+public class RegionServerRpcQuotaManager implements RpcQuotaManager,
ConfigurationObserver {
private static final Logger LOG =
LoggerFactory.getLogger(RegionServerRpcQuotaManager.class);
private final RegionServerServices rsServices;
@@ -88,6 +90,15 @@ public class RegionServerRpcQuotaManager implements
RpcQuotaManager {
}
}
+ public void reload() {
+ quotaCache.forceSynchronousCacheRefresh();
+ }
+
+ @Override
+ public void onConfigurationChange(Configuration conf) {
+ reload();
+ }
+
protected boolean isRpcThrottleEnabled() {
return rpcThrottleEnabled;
}
diff --git
a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HRegionServer.java
b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HRegionServer.java
index 351b4fef191..810e10f1c56 100644
---
a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HRegionServer.java
+++
b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HRegionServer.java
@@ -2256,6 +2256,11 @@ public class HRegionServer extends Thread
conf.getInt("hbase.regionserver.executor.flush.operations.threads", 3);
executorService.startExecutorService(executorService.new ExecutorConfig()
.setExecutorType(ExecutorType.RS_FLUSH_OPERATIONS).setCorePoolSize(rsFlushOperationThreads));
+ final int rsRefreshQuotasThreads =
+ conf.getInt("hbase.regionserver.executor.refresh.quotas.threads", 1);
+ executorService.startExecutorService(
+ executorService.new
ExecutorConfig().setExecutorType(ExecutorType.RS_RELOAD_QUOTAS_OPERATIONS)
+ .setCorePoolSize(rsRefreshQuotasThreads));
Threads.setDaemonThreadRunning(this.walRoller, getName() + ".logRoller",
uncaughtExceptionHandler);
@@ -2371,6 +2376,7 @@ public class HRegionServer extends Thread
// Setup the Quota Manager
rsQuotaManager = new RegionServerRpcQuotaManager(this);
+ configurationManager.registerObserver(rsQuotaManager);
rsSpaceQuotaManager = new RegionServerSpaceQuotaManager(this);
if (QuotaUtil.isQuotaEnabled(conf)) {
diff --git
a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/ReloadQuotasCallable.java
b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/ReloadQuotasCallable.java
new file mode 100644
index 00000000000..e134dfda7ac
--- /dev/null
+++
b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/ReloadQuotasCallable.java
@@ -0,0 +1,46 @@
+/*
+ * 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.regionserver;
+
+import org.apache.hadoop.hbase.executor.EventType;
+import org.apache.hadoop.hbase.procedure2.BaseRSProcedureCallable;
+import org.apache.yetus.audience.InterfaceAudience;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
[email protected]
+public class ReloadQuotasCallable extends BaseRSProcedureCallable {
+
+ private static final Logger LOG =
LoggerFactory.getLogger(ReloadQuotasCallable.class);
+
+ @Override
+ protected void doCall() throws Exception {
+ LOG.info("Reloading quotas");
+ rs.getRegionServerRpcQuotaManager().reload();
+ }
+
+ @Override
+ protected void initParameter(byte[] parameter) throws Exception {
+
+ }
+
+ @Override
+ public EventType getEventType() {
+ return EventType.RS_RELOAD_QUOTAS;
+ }
+}
diff --git
a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/procedure/TestReloadQuotasProcedure.java
b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/procedure/TestReloadQuotasProcedure.java
new file mode 100644
index 00000000000..3dd138adbf4
--- /dev/null
+++
b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/procedure/TestReloadQuotasProcedure.java
@@ -0,0 +1,183 @@
+/*
+ * 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.master.procedure;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+
+import java.io.IOException;
+import java.util.Optional;
+import java.util.concurrent.Future;
+import org.apache.hadoop.hbase.HBaseClassTestRule;
+import org.apache.hadoop.hbase.HBaseTestingUtility;
+import org.apache.hadoop.hbase.ServerName;
+import org.apache.hadoop.hbase.master.assignment.MockMasterServices;
+import org.apache.hadoop.hbase.procedure2.RemoteProcedureDispatcher;
+import org.apache.hadoop.hbase.testclassification.MasterTests;
+import org.apache.hadoop.hbase.testclassification.MediumTests;
+import org.junit.After;
+import org.junit.Before;
+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;
+
+@Category({ MasterTests.class, MediumTests.class })
+public class TestReloadQuotasProcedure {
+ private static final Logger LOG =
LoggerFactory.getLogger(TestReloadQuotasProcedure.class);
+
+ @ClassRule
+ public static final HBaseClassTestRule CLASS_RULE =
+ HBaseClassTestRule.forClass(TestReloadQuotasProcedure.class);
+
+ @Rule
+ public TestName name = new TestName();
+
+ private HBaseTestingUtility util;
+ private MockMasterServices master;
+ private TestServerRemoteProcedure.MockRSProcedureDispatcher rsDispatcher;
+
+ @Before
+ public void setUp() throws Exception {
+ util = new HBaseTestingUtility();
+ master = new MockMasterServices(util.getConfiguration());
+ rsDispatcher = new
TestServerRemoteProcedure.MockRSProcedureDispatcher(master);
+ master.start(2, rsDispatcher);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ master.stop("tearDown");
+ }
+
+ @Test
+ public void itHandlesClassNotFoundExceptionGracefully() throws Exception {
+ ServerName targetServer =
master.getServerManager().getOnlineServersList().get(0);
+ ReloadQuotasProcedure procedure = new ReloadQuotasProcedure(targetServer);
+
+ ClassNotFoundException classNotFound =
+ new ClassNotFoundException("ReloadQuotasCallable not found");
+ IOException wrappedException = new IOException("Remote call failed",
classNotFound);
+
+ boolean result =
+ procedure.complete(master.getMasterProcedureExecutor().getEnvironment(),
wrappedException);
+
+ assertTrue(result);
+ }
+
+ @Test
+ public void itReturnsFailureForOtherExceptions() throws Exception {
+ ServerName targetServer =
master.getServerManager().getOnlineServersList().get(0);
+ ReloadQuotasProcedure procedure = new ReloadQuotasProcedure(targetServer);
+
+ IOException otherException = new IOException("Some other error");
+
+ boolean result =
+ procedure.complete(master.getMasterProcedureExecutor().getEnvironment(),
otherException);
+
+ assertFalse(result);
+ }
+
+ @Test
+ public void itReturnsSuccessForNoError() throws Exception {
+ ServerName targetServer =
master.getServerManager().getOnlineServersList().get(0);
+ ReloadQuotasProcedure procedure = new ReloadQuotasProcedure(targetServer);
+
+ boolean result =
procedure.complete(master.getMasterProcedureExecutor().getEnvironment(), null);
+
+ assertTrue(result);
+ }
+
+ @Test
+ public void itCorrectlyDetectsCauseClass() {
+ ReloadQuotasProcedure procedure = new ReloadQuotasProcedure();
+
+ ClassNotFoundException classNotFound = new ClassNotFoundException("test");
+ IOException wrappedException = new IOException("wrapper", classNotFound);
+ RuntimeException outerWrapper = new RuntimeException("outer",
wrappedException);
+
+ assertTrue(procedure.containsCause(outerWrapper,
ClassNotFoundException.class));
+ assertFalse(procedure.containsCause(outerWrapper,
IllegalArgumentException.class));
+ assertTrue(procedure.containsCause(classNotFound,
ClassNotFoundException.class));
+ }
+
+ @Test
+ public void itValidatesServerNameInRemoteCallBuild() throws Exception {
+ ServerName targetServer =
master.getServerManager().getOnlineServersList().get(0);
+ ServerName wrongServer =
master.getServerManager().getOnlineServersList().get(1);
+ ReloadQuotasProcedure procedure = new ReloadQuotasProcedure(targetServer);
+
+ MasterProcedureEnv env =
master.getMasterProcedureExecutor().getEnvironment();
+
+ Optional<RemoteProcedureDispatcher.RemoteOperation> result =
+ procedure.remoteCallBuild(env, targetServer);
+ assertTrue(result.isPresent());
+ assertTrue(result.get() instanceof RSProcedureDispatcher.ServerOperation);
+
+ assertThrows(IllegalArgumentException.class, () -> {
+ procedure.remoteCallBuild(env, wrongServer);
+ });
+ }
+
+ @Test
+ public void itCreatesCorrectRemoteOperation() throws Exception {
+ ServerName targetServer =
master.getServerManager().getOnlineServersList().get(0);
+ ReloadQuotasProcedure procedure = new ReloadQuotasProcedure(targetServer);
+ MasterProcedureEnv env =
master.getMasterProcedureExecutor().getEnvironment();
+
+ Optional<RemoteProcedureDispatcher.RemoteOperation> operation =
+ procedure.remoteCallBuild(env, targetServer);
+
+ assertTrue(operation.isPresent());
+ RSProcedureDispatcher.ServerOperation serverOp =
+ (RSProcedureDispatcher.ServerOperation) operation.get();
+ assertEquals(serverOp.getRemoteProcedure(), procedure);
+ assertEquals(serverOp.buildRequest().getProcId(), procedure.getProcId());
+ }
+
+ @Test
+ public void itThrowsUnsupportedOperationExceptionOnRollback() throws
Exception {
+ ServerName targetServer =
master.getServerManager().getOnlineServersList().get(0);
+ ReloadQuotasProcedure procedure = new ReloadQuotasProcedure(targetServer);
+ MasterProcedureEnv env =
master.getMasterProcedureExecutor().getEnvironment();
+
+ assertThrows(UnsupportedOperationException.class, () -> {
+ procedure.rollback(env);
+ });
+ }
+
+ @Test
+ public void itReturnsFalseOnAbort() throws Exception {
+ ServerName targetServer =
master.getServerManager().getOnlineServersList().get(0);
+ ReloadQuotasProcedure procedure = new ReloadQuotasProcedure(targetServer);
+ MasterProcedureEnv env =
master.getMasterProcedureExecutor().getEnvironment();
+
+ boolean result = procedure.abort(env);
+
+ assertFalse(result);
+ }
+
+ private Future<byte[]> submitProcedure(ReloadQuotasProcedure procedure) {
+ return
ProcedureSyncWait.submitProcedure(master.getMasterProcedureExecutor(),
procedure);
+ }
+}