This is an automated email from the ASF dual-hosted git repository.

cconnell pushed a commit to branch branch-2.6
in repository https://gitbox.apache.org/repos/asf/hbase.git


The following commit(s) were added to refs/heads/branch-2.6 by this push:
     new f0dcbf964b1 HBASE-29387: Reload quotas from hbase:quota table when 
changes are made (#7091)
f0dcbf964b1 is described below

commit f0dcbf964b1564c04f5d4789203bc5296e196c0e
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);
+  }
+}

Reply via email to