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

taklwu pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/hbase.git


The following commit(s) were added to refs/heads/master by this push:
     new 531df7aca86 HBASE-30180: Can still add data to read-only region after 
flipping read-only flag multiple times (#8280)
531df7aca86 is described below

commit 531df7aca8611bd77c9923410b95beb461ae9cdd
Author: Kevin Geiszler <[email protected]>
AuthorDate: Wed Jun 10 12:36:50 2026 -0700

    HBASE-30180: Can still add data to read-only region after flipping 
read-only flag multiple times (#8280)
    
    Signed-off-by: Wellington Chevreuil <[email protected]>
    Signed-off-by: Tak Lon (Stephen) Wu <[email protected]>
---
 .../org/apache/hadoop/hbase/master/HMaster.java    | 26 +++++---
 .../apache/hadoop/hbase/regionserver/HRegion.java  | 33 +++++++---
 .../hadoop/hbase/regionserver/HRegionServer.java   | 26 +++++---
 .../hbase/util/CoprocessorConfigurationUtil.java   | 74 ++++++++++++---------
 .../hadoop/hbase/regionserver/TestHRegion.java     | 75 ++++++++++++++++++++--
 5 files changed, 170 insertions(+), 64 deletions(-)

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 df6c616db4a..d2cf0cc9379 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
@@ -4491,26 +4491,32 @@ public class HMaster extends 
HBaseServerBase<MasterRpcServices> implements Maste
     return masterRegion;
   }
 
+  /**
+   * Dynamically updates HMaster's configuration. Since HMaster inherits from
+   * {@link HBaseServerBase}, the {@code updatedConf} parameter references the 
same
+   * {@link Configuration} object as HMaster's {@code this.conf} instance 
variable in a real HBase
+   * deployment. This isn't necessarily the case in unit tests.
+   * @param updatedConf the dynamically updated configuration
+   */
   @Override
-  public void onConfigurationChange(Configuration newConf) {
+  public void onConfigurationChange(Configuration updatedConf) {
     try {
-      Superusers.initialize(newConf);
+      Superusers.initialize(updatedConf);
     } catch (IOException e) {
       LOG.warn("Failed to initialize SuperUsers on reloading of the 
configuration");
     }
     // append the quotas observer back to the master coprocessor key
-    setQuotasObserver(newConf);
+    setQuotasObserver(updatedConf);
 
     boolean originalIsReadOnlyEnabled = CoprocessorConfigurationUtil
       .areReadOnlyCoprocessorsLoaded(this.conf, 
CoprocessorHost.MASTER_COPROCESSOR_CONF_KEY);
 
-    CoprocessorConfigurationUtil.maybeUpdateCoprocessors(newConf, 
originalIsReadOnlyEnabled,
-      this.cpHost, CoprocessorHost.MASTER_COPROCESSOR_CONF_KEY, 
this.maintenanceMode,
-      this.toString(), conf -> {
-        this.initializeCoprocessorHost(conf);
-        CoprocessorConfigurationUtil.updateCoprocessorListInConf(this.conf, 
conf,
-          CoprocessorHost.MASTER_COPROCESSOR_CONF_KEY);
-      });
+    // updatedConf and this.conf reference the same Configuration object in an 
actual HBase
+    // deployment. However, in unit test cases they reference different 
Configuration objects, so
+    // this.conf needs to be updated.
+    CoprocessorConfigurationUtil.maybeUpdateCoprocessors(updatedConf, 
this.conf,
+      originalIsReadOnlyEnabled, this.cpHost, 
CoprocessorHost.MASTER_COPROCESSOR_CONF_KEY,
+      this.maintenanceMode, this.toString(), this::initializeCoprocessorHost);
 
     boolean maybeUpdatedReadOnlyMode = CoprocessorConfigurationUtil
       .areReadOnlyCoprocessorsLoaded(this.conf, 
CoprocessorHost.MASTER_COPROCESSOR_CONF_KEY);
diff --git 
a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HRegion.java 
b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HRegion.java
index f2a67df842c..6871a79324f 100644
--- 
a/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HRegion.java
+++ 
b/hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/HRegion.java
@@ -150,6 +150,7 @@ import org.apache.hadoop.hbase.ipc.ServerCall;
 import org.apache.hadoop.hbase.keymeta.KeyManagementService;
 import org.apache.hadoop.hbase.keymeta.ManagedKeyDataCache;
 import org.apache.hadoop.hbase.keymeta.SystemKeyCache;
+import org.apache.hadoop.hbase.master.HMaster;
 import org.apache.hadoop.hbase.mob.MobFileCache;
 import org.apache.hadoop.hbase.monitoring.MonitoredTask;
 import org.apache.hadoop.hbase.monitoring.TaskMonitor;
@@ -8982,25 +8983,33 @@ public class HRegion implements HeapSize, 
PropagatingConfigurationObserver, Regi
   }
 
   /**
-   * {@inheritDoc}
+   * Dynamically updates HRegion's configuration. Unlike {@link HMaster} and 
{@link HRegionServer},
+   * this {@code updatedConf} parameter does not reference the same {@link 
Configuration} object as
+   * HRegion's {@code this.conf} instance variable in a real HBase deployment. 
This is because
+   * HRegion's {@code this.conf} is a {@link CompoundConfiguration} object. 
Instead,
+   * {@code updatedConf} references the same Configuration object as HRegion's 
{@code this.baseConf}
+   * instance variable.
+   * @param updatedConf the dynamically updated configuration
    */
   @Override
-  public void onConfigurationChange(Configuration newConf) {
-    this.storeHotnessProtector.update(newConf);
+  public void onConfigurationChange(Configuration updatedConf) {
+    this.storeHotnessProtector.update(updatedConf);
 
     boolean originalIsReadOnlyEnabled = CoprocessorConfigurationUtil
       .areReadOnlyCoprocessorsLoaded(this.conf, 
CoprocessorHost.REGION_COPROCESSOR_CONF_KEY);
 
-    CoprocessorConfigurationUtil.maybeUpdateCoprocessors(newConf, 
originalIsReadOnlyEnabled,
-      this.coprocessorHost, CoprocessorHost.REGION_COPROCESSOR_CONF_KEY, 
false, this.toString(),
-      conf -> {
+    // HRegion's this.conf is a special Configuration type called 
CompoundConfiguration. This means
+    // we don't want to use the updatedConf provided in 
onConfigurationChange() for creating a new
+    // RegionCoprocessorHost. Instead, we update this.conf and use that for 
decorating the region
+    // config and updating this.coprocessorHost.
+    CoprocessorConfigurationUtil.maybeUpdateCoprocessors(updatedConf, 
this.conf,
+      originalIsReadOnlyEnabled, this.coprocessorHost, 
CoprocessorHost.REGION_COPROCESSOR_CONF_KEY,
+      false, this.toString(), conf -> {
         decorateRegionConfiguration(conf);
         this.coprocessorHost = new RegionCoprocessorHost(this, rsServices, 
conf);
-        CoprocessorConfigurationUtil.updateCoprocessorListInConf(this.conf, 
conf,
-          CoprocessorHost.REGION_COPROCESSOR_CONF_KEY);
       });
 
-    boolean newReadOnlyEnabled = 
ConfigurationUtil.isReadOnlyModeEnabledInConf(newConf);
+    boolean newReadOnlyEnabled = 
ConfigurationUtil.isReadOnlyModeEnabledInConf(updatedConf);
 
     if (originalIsReadOnlyEnabled && !newReadOnlyEnabled) {
       LOG.info("Cluster Read Only mode disabled");
@@ -9157,4 +9166,10 @@ public class HRegion implements HeapSize, 
PropagatingConfigurationObserver, Regi
   public ConfigurationManager getConfigurationManager() {
     return configurationManager;
   }
+
+  @RestrictedApi(explanation = "Should only be called in tests", link = "",
+      allowedOnPath = ".*/src/test/.*")
+  public Configuration getConfiguration() {
+    return this.conf;
+  }
 }
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 009e0d8a134..a7bf72dabd7 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
@@ -3483,15 +3483,22 @@ public class HRegionServer extends 
HBaseServerBase<RSRpcServices>
     return getRegionServerAccounting().getFlushPressure();
   }
 
+  /**
+   * Dynamically updates HRegionServer's configuration. Since HRegionServer 
inherits from
+   * {@link HBaseServerBase}, the {@code updatedConf} parameter references the 
same
+   * {@link Configuration} object as HRegionServer's {@code this.conf} 
instance variable in a real
+   * HBase deployment. This isn't necessarily the case in unit tests.
+   * @param updatedConf the dynamically updated configuration
+   */
   @Override
-  public void onConfigurationChange(Configuration newConf) {
+  public void onConfigurationChange(Configuration updatedConf) {
     ThroughputController old = this.flushThroughputController;
     if (old != null) {
       old.stop("configuration change");
     }
-    this.flushThroughputController = 
FlushThroughputControllerFactory.create(this, newConf);
+    this.flushThroughputController = 
FlushThroughputControllerFactory.create(this, updatedConf);
     try {
-      Superusers.initialize(newConf);
+      Superusers.initialize(updatedConf);
     } catch (IOException e) {
       LOG.warn("Failed to initialize SuperUsers on reloading of the 
configuration");
     }
@@ -3499,13 +3506,12 @@ public class HRegionServer extends 
HBaseServerBase<RSRpcServices>
     boolean originalIsReadOnlyEnabled = CoprocessorConfigurationUtil
       .areReadOnlyCoprocessorsLoaded(this.conf, 
CoprocessorHost.REGIONSERVER_COPROCESSOR_CONF_KEY);
 
-    CoprocessorConfigurationUtil.maybeUpdateCoprocessors(newConf, 
originalIsReadOnlyEnabled,
-      this.rsHost, CoprocessorHost.REGIONSERVER_COPROCESSOR_CONF_KEY, false, 
this.toString(),
-      conf -> {
-        this.rsHost = new RegionServerCoprocessorHost(this, conf);
-        CoprocessorConfigurationUtil.updateCoprocessorListInConf(this.conf, 
conf,
-          CoprocessorHost.REGIONSERVER_COPROCESSOR_CONF_KEY);
-      });
+    // updatedConf and this.conf reference the same Configuration object in an 
actual HBase
+    // deployment. However, in unit test cases they reference different 
Configuration objects, so
+    // this.conf needs to be updated.
+    CoprocessorConfigurationUtil.maybeUpdateCoprocessors(updatedConf, 
this.conf,
+      originalIsReadOnlyEnabled, this.rsHost, 
CoprocessorHost.REGIONSERVER_COPROCESSOR_CONF_KEY,
+      false, this.toString(), conf -> this.rsHost = new 
RegionServerCoprocessorHost(this, conf));
   }
 
   @Override
diff --git 
a/hbase-server/src/main/java/org/apache/hadoop/hbase/util/CoprocessorConfigurationUtil.java
 
b/hbase-server/src/main/java/org/apache/hadoop/hbase/util/CoprocessorConfigurationUtil.java
index 8c462fbbc34..6a7c5b7f773 100644
--- 
a/hbase-server/src/main/java/org/apache/hadoop/hbase/util/CoprocessorConfigurationUtil.java
+++ 
b/hbase-server/src/main/java/org/apache/hadoop/hbase/util/CoprocessorConfigurationUtil.java
@@ -17,10 +17,13 @@
  */
 package org.apache.hadoop.hbase.util;
 
+import static 
org.apache.hadoop.hbase.HConstants.HBASE_GLOBAL_READONLY_ENABLED_KEY;
+
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Objects;
 import java.util.Set;
 import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.hbase.HConstants;
@@ -177,6 +180,25 @@ public final class CoprocessorConfigurationUtil {
     return READONLY_COPROCESSORS.get(configurationKey);
   }
 
+  /**
+   * Updates the coprocessors in one Configuration object to match the 
coprocessors in the other
+   * @param srcConf            the Configuration object we are getting 
coprocessors from
+   * @param dstConf            the Configuration object we are updating
+   * @param coprocessorConfKey the type of coprocessors we are updating
+   */
+  private static void syncCoprocessorsWithConf(Configuration srcConf, 
Configuration dstConf,
+    String coprocessorConfKey) {
+    String configuredCps = srcConf.get(coprocessorConfKey);
+    dstConf.set(coprocessorConfKey, Objects.requireNonNullElse(configuredCps, 
""));
+
+    // A configuration with region coprocessors may also have user region 
coprocessors
+    if 
(CoprocessorHost.REGION_COPROCESSOR_CONF_KEY.equals(coprocessorConfKey)) {
+      String configuredUserCps = 
srcConf.get(CoprocessorHost.USER_REGION_COPROCESSOR_CONF_KEY);
+      dstConf.set(CoprocessorHost.USER_REGION_COPROCESSOR_CONF_KEY,
+        Objects.requireNonNullElse(configuredUserCps, ""));
+    }
+  }
+
   /**
    * This method adds or removes relevant ReadOnlyController coprocessors to 
the provided
    * configuration based on whether read-only mode is enabled in the provided 
Configuration.
@@ -211,26 +233,6 @@ public final class CoprocessorConfigurationUtil {
     return allCoprocessors.containsAll(readOnlyCoprocessors);
   }
 
-  /**
-   * Takes an updated configuration and updates the coprocessors for that 
configuration key in the
-   * current configuration.
-   * @param currentConf        the configuration currently used by the master, 
region server, or
-   *                           region
-   * @param updatedConf        the updated version of the configuration whose 
coprocessors we want
-   *                           to copy
-   * @param coprocessorConfKey configuration key used for setting master, 
region server, or region
-   *                           coprocessors
-   */
-  public static void updateCoprocessorListInConf(Configuration currentConf,
-    Configuration updatedConf, String coprocessorConfKey) {
-    String[] updatedCoprocessorList = 
updatedConf.getStrings(coprocessorConfKey);
-    if (updatedCoprocessorList != null) {
-      currentConf.setStrings(coprocessorConfKey, updatedCoprocessorList);
-    } else {
-      currentConf.unset(coprocessorConfKey);
-    }
-  }
-
   /**
    * Gets the name of a component based on the provided coprocessor 
configuration key.
    * @param coprocessorConfKey configuration key used for setting master, 
region server, or region
@@ -252,7 +254,9 @@ public final class CoprocessorConfigurationUtil {
    * been detected. Detected changes include changes in coprocessors or 
changes in read-only mode
    * configuration. If a change is detected, then new coprocessors are loaded 
using the provided
    * reload method. The new value for the read-only config variable is updated 
as well.
-   * @param newConf                   an updated configuration
+   * @param updatedConf               an updated configuration
+   * @param originalConf              the actual configuration we want to 
update, which may or may
+   *                                  not be the same Configuration object as 
{@code updatedConf}
    * @param originalIsReadOnlyEnabled the original value for
    *                                  {@value 
HConstants#HBASE_GLOBAL_READONLY_ENABLED_KEY}
    * @param coprocessorHost           the coprocessor host for HMaster, 
HRegionServer, or HRegion
@@ -264,28 +268,36 @@ public final class CoprocessorConfigurationUtil {
    * @param reloadTask                lambda function that reloads 
coprocessors on the master,
    *                                  region server, or region
    */
-  public static void maybeUpdateCoprocessors(Configuration newConf,
+  public static void maybeUpdateCoprocessors(Configuration updatedConf, 
Configuration originalConf,
     boolean originalIsReadOnlyEnabled, CoprocessorHost<?, ?> coprocessorHost,
     String coprocessorConfKey, boolean isMaintenanceMode, String instance,
     CoprocessorReloadTask reloadTask) {
 
-    boolean maybeUpdatedReadOnlyMode = 
ConfigurationUtil.isReadOnlyModeEnabledInConf(newConf);
-    boolean hasReadOnlyModeChanged = originalIsReadOnlyEnabled != 
maybeUpdatedReadOnlyMode;
+    String componentName = getComponentName(coprocessorConfKey);
+    boolean updatedReadOnlyMode = 
ConfigurationUtil.isReadOnlyModeEnabledInConf(updatedConf);
+    boolean hasReadOnlyModeChanged = originalIsReadOnlyEnabled != 
updatedReadOnlyMode;
     boolean hasCoprocessorConfigChanged = CoprocessorConfigurationUtil
-      .checkConfigurationChange(coprocessorHost, newConf, coprocessorConfKey);
+      .checkConfigurationChange(coprocessorHost, updatedConf, 
coprocessorConfKey);
 
-    // update region server coprocessor if the configuration has changed.
     if ((hasCoprocessorConfigChanged || hasReadOnlyModeChanged) && 
!isMaintenanceMode) {
       LOG.info("Updating coprocessors for {} {} because the configuration has 
changed",
-        getComponentName(coprocessorConfKey), instance);
-      CoprocessorConfigurationUtil.syncReadOnlyConfigurations(newConf, 
coprocessorConfKey);
-      reloadTask.reload(newConf);
+        componentName, instance);
+      // In real HBase deployments, updatedConf and originalConf reference the 
same Configuration
+      // object for HMaster and HRegionServer respectively. However, for 
HRegion and for unit test
+      // cases, these are different objects, and the original conf needs to be 
updated accordingly.
+      if (updatedConf != originalConf) {
+        syncCoprocessorsWithConf(updatedConf, originalConf, 
coprocessorConfKey);
+        originalConf.setBoolean(HBASE_GLOBAL_READONLY_ENABLED_KEY, 
updatedReadOnlyMode);
+      }
+      // This needs to run even if read-only mode has not changed in case 
ReadOnly coprocessors
+      // were unintentionally added/removed in the previous code block
+      syncReadOnlyConfigurations(originalConf, coprocessorConfKey);
+      reloadTask.reload(originalConf);
     }
 
     if (hasReadOnlyModeChanged) {
       LOG.info("Config {} has been dynamically changed to {} for {} {}",
-        HConstants.HBASE_GLOBAL_READONLY_ENABLED_KEY, maybeUpdatedReadOnlyMode,
-        getComponentName(coprocessorConfKey), instance);
+        HBASE_GLOBAL_READONLY_ENABLED_KEY, updatedReadOnlyMode, componentName, 
instance);
     }
   }
 }
diff --git 
a/hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/TestHRegion.java
 
b/hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/TestHRegion.java
index c138b0a448a..380e657b3d1 100644
--- 
a/hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/TestHRegion.java
+++ 
b/hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/TestHRegion.java
@@ -24,6 +24,7 @@ import static org.apache.hadoop.hbase.HBaseTestingUtil.fam3;
 import static org.junit.jupiter.api.Assertions.assertArrayEquals;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -45,6 +46,7 @@ import static org.mockito.Mockito.when;
 
 import java.io.IOException;
 import java.io.InterruptedIOException;
+import java.lang.reflect.Field;
 import java.math.BigDecimal;
 import java.security.PrivilegedExceptionAction;
 import java.util.ArrayList;
@@ -82,6 +84,7 @@ import org.apache.hadoop.hbase.CellBuilderType;
 import org.apache.hadoop.hbase.CellUtil;
 import org.apache.hadoop.hbase.CompareOperator;
 import org.apache.hadoop.hbase.CompatibilitySingletonFactory;
+import org.apache.hadoop.hbase.CompoundConfiguration;
 import org.apache.hadoop.hbase.DoNotRetryIOException;
 import org.apache.hadoop.hbase.DroppedSnapshotException;
 import org.apache.hadoop.hbase.ExtendedCell;
@@ -159,11 +162,13 @@ import 
org.apache.hadoop.hbase.regionserver.wal.MetricsWALSource;
 import org.apache.hadoop.hbase.regionserver.wal.WALUtil;
 import org.apache.hadoop.hbase.replication.regionserver.ReplicationObserver;
 import org.apache.hadoop.hbase.security.User;
+import org.apache.hadoop.hbase.security.access.RegionReadOnlyController;
 import org.apache.hadoop.hbase.test.MetricsAssertHelper;
 import org.apache.hadoop.hbase.testclassification.LargeTests;
 import org.apache.hadoop.hbase.testclassification.VerySlowRegionServerTests;
 import org.apache.hadoop.hbase.util.Bytes;
 import org.apache.hadoop.hbase.util.CommonFSUtils;
+import org.apache.hadoop.hbase.util.CoprocessorConfigurationUtil;
 import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
 import org.apache.hadoop.hbase.util.EnvironmentEdgeManagerTestHelper;
 import org.apache.hadoop.hbase.util.HFileArchiveUtil;
@@ -7891,9 +7896,9 @@ public class TestHRegion {
       NoOpRegionCoprocessor.class.getName());
     // trigger configuration change
     region.onConfigurationChange(newConf);
-    assertTrue(region.getCoprocessorHost() != null);
+    assertNotNull(region.getCoprocessorHost());
     Set<String> coprocessors = region.getCoprocessorHost().getCoprocessors();
-    assertTrue(coprocessors.size() == 2);
+    assertEquals(2, coprocessors.size());
     assertTrue(region.getCoprocessorHost().getCoprocessors()
       .contains(MetaTableMetrics.class.getSimpleName()));
     assertTrue(region.getCoprocessorHost().getCoprocessors()
@@ -7902,9 +7907,9 @@ public class TestHRegion {
     // remove region coprocessor and keep only user region coprocessor
     newConf.unset(CoprocessorHost.REGION_COPROCESSOR_CONF_KEY);
     region.onConfigurationChange(newConf);
-    assertTrue(region.getCoprocessorHost() != null);
+    assertNotNull(region.getCoprocessorHost());
     coprocessors = region.getCoprocessorHost().getCoprocessors();
-    assertTrue(coprocessors.size() == 1);
+    assertEquals(1, coprocessors.size());
     assertTrue(region.getCoprocessorHost().getCoprocessors()
       .contains(NoOpRegionCoprocessor.class.getSimpleName()));
   }
@@ -8006,4 +8011,66 @@ public class TestHRegion {
       TEST_UTIL.cleanupTestDir();
     }
   }
+
+  /**
+   * A region can miss read-only coprocessors after repeated toggles if
+   * {@code maybeUpdateCoprocessors} syncs to the server configuration instead 
of the region's
+   * {@link org.apache.hadoop.hbase.CompoundConfiguration}.
+   */
+  @Test
+  public void 
testRegionOnConfigurationChangeLoadsReadOnlyCoprocessorsAfterRepeatedToggle()
+    throws IOException {
+    byte[] cf1 = Bytes.toBytes("CF1");
+    Configuration serverConf = new Configuration(CONF);
+    serverConf.setBoolean(HConstants.HBASE_GLOBAL_READONLY_ENABLED_KEY, false);
+
+    region = initHRegion(tableName, method, serverConf, new byte[][] { cf1 });
+    region.setCoprocessorHost(new RegionCoprocessorHost(region, null, 
region.getConfiguration()));
+
+    String regionCpKey = CoprocessorHost.REGION_COPROCESSOR_CONF_KEY;
+    boolean[] toggleSequence = new boolean[] { true, false, true, true, false, 
false };
+
+    for (boolean readOnlyEnabled : toggleSequence) {
+      Configuration newConf = new Configuration(serverConf);
+      newConf.setBoolean(HConstants.HBASE_GLOBAL_READONLY_ENABLED_KEY, 
readOnlyEnabled);
+      region.onConfigurationChange(newConf);
+
+      if (readOnlyEnabled) {
+        assertNotNull(
+          
region.getCoprocessorHost().findCoprocessor(RegionReadOnlyController.class.getName()));
+      } else {
+        assertNull(
+          
region.getCoprocessorHost().findCoprocessor(RegionReadOnlyController.class.getName()));
+      }
+      assertEquals(readOnlyEnabled, CoprocessorConfigurationUtil
+        .areReadOnlyCoprocessorsLoaded(region.getConfiguration(), 
regionCpKey));
+    }
+  }
+
+  /**
+   * Verifies the new RegionCoprocessorHost object created in 
HRegion.onConfigurationChange() uses a
+   * CompoundConfiguration in its constructor rather than a Configuration 
object.
+   */
+  @Test
+  public void 
testRegionCoprocessorHostUsesCompoundConfigurationAfterConfigChange()
+    throws Exception {
+    byte[] cf1 = Bytes.toBytes("CF1");
+    Configuration serverConf = new Configuration(CONF);
+    serverConf.setBoolean(HConstants.HBASE_GLOBAL_READONLY_ENABLED_KEY, false);
+
+    region = initHRegion(tableName, method, serverConf, new byte[][] { cf1 });
+    region.setCoprocessorHost(new RegionCoprocessorHost(region, null, 
region.getConfiguration()));
+
+    // Enable read-only mode to trigger coprocessor reload and new 
RegionCoprocessorHost creation
+    Configuration newConf = new Configuration(serverConf);
+    newConf.setBoolean(HConstants.HBASE_GLOBAL_READONLY_ENABLED_KEY, true);
+    region.onConfigurationChange(newConf);
+
+    // Use reflection to access the protected conf field on CoprocessorHost, 
which holds the
+    // Configuration passed to the RegionCoprocessorHost constructor
+    Field confField = CoprocessorHost.class.getDeclaredField("conf");
+    confField.setAccessible(true);
+    Configuration hostConf = (Configuration) 
confField.get(region.getCoprocessorHost());
+    assertInstanceOf(CompoundConfiguration.class, hostConf);
+  }
 }

Reply via email to