This is an automated email from the ASF dual-hosted git repository.
andor pushed a commit to branch HBASE-29081
in repository https://gitbox.apache.org/repos/asf/hbase.git
The following commit(s) were added to refs/heads/HBASE-29081 by this push:
new e34ec5fb608 HBASE-29236: Add Support for Dynamic Configuration at the
Coprocessor Level (#6931)
e34ec5fb608 is described below
commit e34ec5fb6082b34a113a4cc7efe13a5af64d9ce7
Author: Kevin Geiszler <[email protected]>
AuthorDate: Thu May 29 05:40:52 2025 -0700
HBASE-29236: Add Support for Dynamic Configuration at the Coprocessor Level
(#6931)
Co-authored-by: Andor Molnar <[email protected]>
Co-authored-by: Anuj Sharma <[email protected]>
---
.../hadoop/hbase/coprocessor/CoprocessorHost.java | 35 +++++
.../org/apache/hadoop/hbase/master/HMaster.java | 8 +-
.../apache/hadoop/hbase/regionserver/HRegion.java | 12 ++
.../hadoop/hbase/regionserver/HRegionServer.java | 6 +
.../hbase/security/access/ReadOnlyController.java | 44 +++---
.../access/TestCanStartHBaseInReadOnlyMode.java | 74 +++++++++
.../security/access/TestReadOnlyController.java | 170 ++++++++++++++++++---
src/main/asciidoc/_chapters/configuration.adoc | 1 +
8 files changed, 309 insertions(+), 41 deletions(-)
diff --git
a/hbase-server/src/main/java/org/apache/hadoop/hbase/coprocessor/CoprocessorHost.java
b/hbase-server/src/main/java/org/apache/hadoop/hbase/coprocessor/CoprocessorHost.java
index 137fe3b061d..191e9573e90 100644
---
a/hbase-server/src/main/java/org/apache/hadoop/hbase/coprocessor/CoprocessorHost.java
+++
b/hbase-server/src/main/java/org/apache/hadoop/hbase/coprocessor/CoprocessorHost.java
@@ -37,6 +37,8 @@ import org.apache.hadoop.hbase.Coprocessor;
import org.apache.hadoop.hbase.CoprocessorEnvironment;
import org.apache.hadoop.hbase.DoNotRetryIOException;
import org.apache.hadoop.hbase.HConstants;
+import org.apache.hadoop.hbase.conf.ConfigurationManager;
+import org.apache.hadoop.hbase.conf.ConfigurationObserver;
import org.apache.hadoop.hbase.ipc.RpcServer;
import org.apache.hadoop.hbase.security.User;
import org.apache.hadoop.hbase.util.CoprocessorClassLoader;
@@ -116,6 +118,39 @@ public abstract class CoprocessorHost<C extends
Coprocessor, E extends Coprocess
return returnValue;
}
+ /**
+ * Used to help make the relevant loaded coprocessors dynamically
configurable by registering them
+ * to the {@link ConfigurationManager}. Coprocessors are considered
"relevant" if they implement
+ * the {@link ConfigurationObserver} interface.
+ * @param configurationManager the ConfigurationManager the coprocessors get
registered to
+ */
+ public void registerConfigurationObservers(ConfigurationManager
configurationManager) {
+ Coprocessor foundCp;
+ Set<String> coprocessors = this.getCoprocessors();
+ for (String cp : coprocessors) {
+ foundCp = this.findCoprocessor(cp);
+ if (foundCp instanceof ConfigurationObserver) {
+ configurationManager.registerObserver((ConfigurationObserver) foundCp);
+ }
+ }
+ }
+
+ /**
+ * Deregisters relevant coprocessors from the {@link ConfigurationManager}.
Coprocessors are
+ * considered "relevant" if they implement the {@link ConfigurationObserver}
interface.
+ * @param configurationManager the ConfigurationManager the coprocessors get
deregistered from
+ */
+ public void deregisterConfigurationObservers(ConfigurationManager
configurationManager) {
+ Coprocessor foundCp;
+ Set<String> coprocessors = this.getCoprocessors();
+ for (String cp : coprocessors) {
+ foundCp = this.findCoprocessor(cp);
+ if (foundCp instanceof ConfigurationObserver) {
+ configurationManager.deregisterObserver((ConfigurationObserver)
foundCp);
+ }
+ }
+ }
+
/**
* Load system coprocessors once only. Read the class names from
configuration. Called by
* constructor.
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 56130fd2e6e..387be56fdf3 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
@@ -609,6 +609,12 @@ public class HMaster extends
HBaseServerBase<MasterRpcServices> implements Maste
private void registerConfigurationObservers() {
configurationManager.registerObserver(this.rpcServices);
configurationManager.registerObserver(this);
+ if (cpHost != null) {
+ cpHost.registerConfigurationObservers(configurationManager);
+ } else {
+ LOG.warn("Could not register HMaster coprocessors to the
ConfigurationManager because "
+ + "MasterCoprocessorHost is null");
+ }
}
// Main run loop. Calls through to the regionserver run loop AFTER becoming
active Master; will
@@ -617,7 +623,6 @@ public class HMaster extends
HBaseServerBase<MasterRpcServices> implements Maste
public void run() {
try {
installShutdownHook();
- registerConfigurationObservers();
Threads.setDaemonThreadRunning(new Thread(TraceUtil.tracedRunnable(() ->
{
try {
int infoPort = putUpJettyServer();
@@ -4506,6 +4511,7 @@ public class HMaster extends
HBaseServerBase<MasterRpcServices> implements Maste
private void initializeCoprocessorHost(Configuration conf) {
// initialize master side coprocessors before we start handling requests
this.cpHost = new MasterCoprocessorHost(this, conf);
+ registerConfigurationObservers();
}
@Override
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 3ff1227db5f..71ca8340031 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
@@ -8832,6 +8832,12 @@ public class HRegion implements HeapSize,
PropagatingConfigurationObserver, Regi
public void registerChildren(ConfigurationManager manager) {
configurationManager = manager;
stores.values().forEach(manager::registerObserver);
+ if (coprocessorHost != null) {
+ coprocessorHost.registerConfigurationObservers(manager);
+ } else {
+ LOG.warn("Could not register HRegion coprocessors to the
ConfigurationManager because "
+ + "RegionCoprocessorHost is null");
+ }
}
/**
@@ -8840,6 +8846,12 @@ public class HRegion implements HeapSize,
PropagatingConfigurationObserver, Regi
@Override
public void deregisterChildren(ConfigurationManager manager) {
stores.values().forEach(configurationManager::deregisterObserver);
+ if (coprocessorHost != null) {
+ coprocessorHost.deregisterConfigurationObservers(manager);
+ } else {
+ LOG.warn("Could not deregister HRegion coprocessors from the
ConfigurationManager because "
+ + "RegionCoprocessorHost is null");
+ }
}
@Override
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 a4105a31bfa..2771e62efa8 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
@@ -2136,6 +2136,12 @@ public class HRegionServer extends
HBaseServerBase<RSRpcServices>
configurationManager.registerObserver(this.rpcServices);
configurationManager.registerObserver(this.prefetchExecutorNotifier);
configurationManager.registerObserver(this);
+ if (rsHost != null) {
+ rsHost.registerConfigurationObservers(configurationManager);
+ } else {
+ LOG.warn("Could not register HRegionServer coprocessors to the
ConfigurationManager because "
+ + "RegionServerCoprocessorHost is null");
+ }
}
/*
diff --git
a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/access/ReadOnlyController.java
b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/access/ReadOnlyController.java
index 90d154ebec5..13f458299b9 100644
---
a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/access/ReadOnlyController.java
+++
b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/access/ReadOnlyController.java
@@ -35,6 +35,7 @@ import org.apache.hadoop.hbase.client.RegionInfo;
import org.apache.hadoop.hbase.client.Result;
import org.apache.hadoop.hbase.client.SnapshotDescription;
import org.apache.hadoop.hbase.client.TableDescriptor;
+import org.apache.hadoop.hbase.conf.ConfigurationObserver;
import org.apache.hadoop.hbase.coprocessor.BulkLoadObserver;
import org.apache.hadoop.hbase.coprocessor.CoreCoprocessor;
import org.apache.hadoop.hbase.coprocessor.EndpointObserver;
@@ -65,26 +66,24 @@ import
org.apache.hadoop.hbase.shaded.protobuf.generated.AdminProtos;
@CoreCoprocessor
@InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.CONFIG)
-public class ReadOnlyController
- implements MasterCoprocessor, RegionCoprocessor, MasterObserver,
RegionObserver,
- RegionServerCoprocessor, RegionServerObserver, EndpointObserver,
BulkLoadObserver {
+public class ReadOnlyController implements MasterCoprocessor,
RegionCoprocessor, MasterObserver,
+ RegionObserver, RegionServerCoprocessor, RegionServerObserver,
EndpointObserver, BulkLoadObserver,
+ ConfigurationObserver {
private static final Logger LOG =
LoggerFactory.getLogger(ReadOnlyController.class);
- private Configuration conf;
+ private volatile boolean globalReadOnlyEnabled;
private void internalReadOnlyGuard() throws IOException {
- if (
- conf.getBoolean(HConstants.HBASE_GLOBAL_READONLY_ENABLED_KEY,
- HConstants.HBASE_GLOBAL_READONLY_ENABLED_DEFAULT)
- ) {
- // throw new FailedSanityCheckException("Operation not allowed in
Read-Only Mode");
+ if (this.globalReadOnlyEnabled) {
throw new IOException("Operation not allowed in Read-Only Mode");
}
}
@Override
public void start(CoprocessorEnvironment env) throws IOException {
- conf = env.getConfiguration();
+ this.globalReadOnlyEnabled =
+
env.getConfiguration().getBoolean(HConstants.HBASE_GLOBAL_READONLY_ENABLED_KEY,
+ HConstants.HBASE_GLOBAL_READONLY_ENABLED_DEFAULT);
}
@Override
@@ -100,7 +99,8 @@ public class ReadOnlyController
@Override
public void prePut(ObserverContext<? extends RegionCoprocessorEnvironment>
c, Put put,
WALEdit edit) throws IOException {
- if (edit.isMetaEdit() || edit.isEmpty()) {
+ TableName tableName = c.getEnvironment().getRegionInfo().getTable();
+ if (tableName.isSystemTable()) {
return;
}
internalReadOnlyGuard();
@@ -115,13 +115,11 @@ public class ReadOnlyController
@Override
public void preBatchMutate(ObserverContext<? extends
RegionCoprocessorEnvironment> c,
MiniBatchOperationInProgress<Mutation> miniBatchOp) throws IOException {
- for (int i = 0; i < miniBatchOp.size(); i++) {
- WALEdit edit = miniBatchOp.getWalEdit(i);
- if (edit == null || edit.isMetaEdit() || edit.isEmpty()) {
- continue;
- }
- internalReadOnlyGuard();
+ TableName tableName = c.getEnvironment().getRegionInfo().getTable();
+ if (tableName.isSystemTable()) {
+ return;
}
+ internalReadOnlyGuard();
}
@Override
@@ -390,4 +388,16 @@ public class ReadOnlyController
internalReadOnlyGuard();
BulkLoadObserver.super.preCleanupBulkLoad(ctx);
}
+
+ /* ---- ConfigurationObserver Overrides ---- */
+ @Override
+ public void onConfigurationChange(Configuration conf) {
+ boolean maybeUpdatedConfValue =
conf.getBoolean(HConstants.HBASE_GLOBAL_READONLY_ENABLED_KEY,
+ HConstants.HBASE_GLOBAL_READONLY_ENABLED_DEFAULT);
+ if (this.globalReadOnlyEnabled != maybeUpdatedConfValue) {
+ this.globalReadOnlyEnabled = maybeUpdatedConfValue;
+ LOG.info("Config {} has been dynamically changed to {}",
+ HConstants.HBASE_GLOBAL_READONLY_ENABLED_KEY,
this.globalReadOnlyEnabled);
+ }
+ }
}
diff --git
a/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestCanStartHBaseInReadOnlyMode.java
b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestCanStartHBaseInReadOnlyMode.java
new file mode 100644
index 00000000000..ed182edbe91
--- /dev/null
+++
b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestCanStartHBaseInReadOnlyMode.java
@@ -0,0 +1,74 @@
+/*
+ * 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.security.access;
+
+import static org.apache.hadoop.hbase.HConstants.HBASE_CLIENT_RETRIES_NUMBER;
+
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.hbase.*;
+import org.apache.hadoop.hbase.client.*;
+import org.apache.hadoop.hbase.coprocessor.CoprocessorHost;
+import org.apache.hadoop.hbase.testclassification.LargeTests;
+import org.apache.hadoop.hbase.testclassification.SecurityTests;
+import org.junit.*;
+import org.junit.experimental.categories.Category;
+import org.junit.rules.TestName;
+
+@Category({ SecurityTests.class, LargeTests.class })
+@SuppressWarnings("deprecation")
+public class TestCanStartHBaseInReadOnlyMode {
+
+ @ClassRule
+ public static final HBaseClassTestRule CLASS_RULE =
+ HBaseClassTestRule.forClass(TestCanStartHBaseInReadOnlyMode.class);
+
+ private static final String READ_ONLY_CONTROLLER_NAME =
ReadOnlyController.class.getName();
+ private static final HBaseTestingUtil TEST_UTIL = new HBaseTestingUtil();
+ private static Configuration conf;
+
+ @Rule
+ public TestName name = new TestName();
+
+ @BeforeClass
+ public static void beforeClass() throws Exception {
+ conf = TEST_UTIL.getConfiguration();
+
+ // Shorten the run time of failed unit tests by limiting retries and the
session timeout
+ // threshold
+ conf.setInt(HBASE_CLIENT_RETRIES_NUMBER, 0);
+ conf.setInt("hbase.master.init.timeout.localHBaseCluster", 10000);
+
+ // Enable Read-Only mode to prove the cluster can be started in this state
+ conf.setBoolean(HConstants.HBASE_GLOBAL_READONLY_ENABLED_KEY, true);
+
+ // Add ReadOnlyController coprocessors to the master, region server, and
regions
+ conf.set(CoprocessorHost.REGION_COPROCESSOR_CONF_KEY,
READ_ONLY_CONTROLLER_NAME);
+ conf.set(CoprocessorHost.REGIONSERVER_COPROCESSOR_CONF_KEY,
READ_ONLY_CONTROLLER_NAME);
+ conf.set(CoprocessorHost.MASTER_COPROCESSOR_CONF_KEY,
READ_ONLY_CONTROLLER_NAME);
+ }
+
+ @AfterClass
+ public static void afterClass() throws Exception {
+ TEST_UTIL.shutdownMiniCluster();
+ }
+
+ @Test
+ public void testCanStartHBaseInReadOnlyMode() throws Exception {
+ TEST_UTIL.startMiniCluster(1);
+ }
+}
diff --git
a/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestReadOnlyController.java
b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestReadOnlyController.java
index 1b286214e6d..ddf513fcfe7 100644
---
a/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestReadOnlyController.java
+++
b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestReadOnlyController.java
@@ -17,17 +17,26 @@
*/
package org.apache.hadoop.hbase.security.access;
+import static org.apache.hadoop.hbase.HConstants.HBASE_CLIENT_RETRIES_NUMBER;
+
import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HBaseClassTestRule;
import org.apache.hadoop.hbase.HBaseTestingUtil;
import org.apache.hadoop.hbase.HConstants;
+import org.apache.hadoop.hbase.SingleProcessHBaseCluster;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.Connection;
import org.apache.hadoop.hbase.client.ConnectionFactory;
+import org.apache.hadoop.hbase.client.Delete;
+import org.apache.hadoop.hbase.client.Put;
+import org.apache.hadoop.hbase.client.Row;
import org.apache.hadoop.hbase.client.Table;
import org.apache.hadoop.hbase.coprocessor.CoprocessorHost;
-import org.apache.hadoop.hbase.coprocessor.RegionServerCoprocessorEnvironment;
+import org.apache.hadoop.hbase.master.HMaster;
+import org.apache.hadoop.hbase.regionserver.HRegionServer;
import org.apache.hadoop.hbase.testclassification.LargeTests;
import org.apache.hadoop.hbase.testclassification.SecurityTests;
import org.apache.hadoop.hbase.util.Bytes;
@@ -50,16 +59,18 @@ public class TestReadOnlyController {
public static final HBaseClassTestRule CLASS_RULE =
HBaseClassTestRule.forClass(TestReadOnlyController.class);
- private static final Logger LOG =
LoggerFactory.getLogger(TestAccessController.class);
+ private static final Logger LOG =
LoggerFactory.getLogger(TestReadOnlyController.class);
+ private static final String READ_ONLY_CONTROLLER_NAME =
ReadOnlyController.class.getName();
private static final HBaseTestingUtil TEST_UTIL = new HBaseTestingUtil();
- private static TableName TEST_TABLE = TableName.valueOf("readonlytesttable");
- private static byte[] TEST_FAMILY = Bytes.toBytes("readonlytablecolfam");
+ private static final TableName TEST_TABLE =
TableName.valueOf("read_only_test_table");
+ private static final byte[] TEST_FAMILY =
Bytes.toBytes("read_only_table_col_fam");
+ private static HRegionServer hRegionServer;
+ private static HMaster hMaster;
private static Configuration conf;
private static Connection connection;
+ private static SingleProcessHBaseCluster cluster;
- private static RegionServerCoprocessorEnvironment RSCP_ENV;
-
- private static Table TestTable;
+ private static Table testTable;
@Rule
public TestName name = new TestName();
@@ -69,20 +80,39 @@ public class TestReadOnlyController {
@BeforeClass
public static void beforeClass() throws Exception {
conf = TEST_UTIL.getConfiguration();
- // Only try once so that if there is failure in connection then test
should fail faster
- conf.setInt("hbase.ipc.client.connect.max.retries", 1);
- // Shorter session timeout is added so that in case failures test should
not take more time
+
+ // Shorten the run time of failed unit tests by limiting retries and the
session timeout
+ // threshold
+ conf.setInt(HBASE_CLIENT_RETRIES_NUMBER, 1);
conf.setInt(HConstants.ZK_SESSION_TIMEOUT, 1000);
- // Enable ReadOnly mode for the cluster
- conf.setBoolean(HConstants.HBASE_GLOBAL_READONLY_ENABLED_KEY, true);
- // Add the ReadOnlyController coprocessor for region server to interrupt
any write operation
- conf.set(CoprocessorHost.REGION_COPROCESSOR_CONF_KEY,
ReadOnlyController.class.getName());
- // Add the ReadOnlyController coprocessor to for master to interrupt any
write operation
- conf.set(CoprocessorHost.MASTER_COPROCESSOR_CONF_KEY,
ReadOnlyController.class.getName());
- // Start the test cluster
- TEST_UTIL.startMiniCluster(2);
- // Get connection to the HBase
- connection = ConnectionFactory.createConnection(conf);
+
+ // Set up test class with Read-Only mode disabled so a table can be created
+ conf.setBoolean(HConstants.HBASE_GLOBAL_READONLY_ENABLED_KEY, false);
+
+ // Add ReadOnlyController coprocessors to the master, region server, and
regions
+ conf.set(CoprocessorHost.REGION_COPROCESSOR_CONF_KEY,
READ_ONLY_CONTROLLER_NAME);
+ conf.set(CoprocessorHost.REGIONSERVER_COPROCESSOR_CONF_KEY,
READ_ONLY_CONTROLLER_NAME);
+ conf.set(CoprocessorHost.MASTER_COPROCESSOR_CONF_KEY,
READ_ONLY_CONTROLLER_NAME);
+
+ try {
+ // Start the test cluster
+ cluster = TEST_UTIL.startMiniCluster(1);
+
+ hMaster = cluster.getMaster();
+ hRegionServer =
cluster.getRegionServerThreads().get(0).getRegionServer();
+ connection = ConnectionFactory.createConnection(conf);
+
+ // Create a test table
+ testTable = TEST_UTIL.createTable(TEST_TABLE, TEST_FAMILY);
+ } catch (Exception e) {
+ // Delete the created table, and clean up the connection and cluster
before throwing an
+ // exception
+ disableReadOnlyMode();
+ TEST_UTIL.deleteTable(TEST_TABLE);
+ connection.close();
+ TEST_UTIL.shutdownMiniCluster();
+ throw new RuntimeException(e);
+ }
}
@AfterClass
@@ -93,8 +123,102 @@ public class TestReadOnlyController {
TEST_UTIL.shutdownMiniCluster();
}
- @Test(expected = IOException.class)
- public void testCreateTable() throws IOException {
- TEST_UTIL.createTable(TEST_TABLE, TEST_FAMILY);
+ private static void enableReadOnlyMode() {
+ // Dynamically enable Read-Only mode if it is not active
+ if (!isReadOnlyModeEnabled()) {
+ LOG.info("Dynamically enabling Read-Only mode by setting {} to true",
+ HConstants.HBASE_GLOBAL_READONLY_ENABLED_DEFAULT);
+ conf.setBoolean(HConstants.HBASE_GLOBAL_READONLY_ENABLED_KEY, true);
+ notifyObservers();
+ }
+ }
+
+ private static void disableReadOnlyMode() {
+ // Dynamically disable Read-Only mode if it is active
+ if (isReadOnlyModeEnabled()) {
+ LOG.info("Dynamically disabling Read-Only mode by setting {} to false",
+ HConstants.HBASE_GLOBAL_READONLY_ENABLED_DEFAULT);
+ conf.setBoolean(HConstants.HBASE_GLOBAL_READONLY_ENABLED_KEY, false);
+ notifyObservers();
+ }
+ }
+
+ private static boolean isReadOnlyModeEnabled() {
+ return conf.getBoolean(HConstants.HBASE_GLOBAL_READONLY_ENABLED_KEY,
+ HConstants.HBASE_GLOBAL_READONLY_ENABLED_DEFAULT);
+ }
+
+ private static void notifyObservers() {
+ LOG.info("Notifying observers about configuration changes");
+ hMaster.getConfigurationManager().notifyAllObservers(conf);
+ hRegionServer.getConfigurationManager().notifyAllObservers(conf);
+ }
+
+ // The test case for successfully creating a table with Read-Only mode
disabled happens when
+ // setting up the test class, so we only need a test function for a failed
table creation.
+ @Test
+ public void testCannotCreateTableWithReadOnlyEnabled() throws IOException {
+ // Expect an IOException to result from the createTable attempt since
Read-Only mode is enabled
+ enableReadOnlyMode();
+ TableName newTable = TableName.valueOf("bad_read_only_test_table");
+ exception.expect(IOException.class);
+ exception.expectMessage("Operation not allowed in Read-Only Mode");
+
+ // This should throw the IOException
+ TEST_UTIL.createTable(newTable, TEST_FAMILY);
+ }
+
+ @Test
+ public void testPutWithReadOnlyDisabled() throws IOException {
+ // Successfully put a row in the table since Read-Only mode is disabled
+ disableReadOnlyMode();
+ final byte[] row2 = Bytes.toBytes("row2");
+ final byte[] value = Bytes.toBytes("efgh");
+ Put put = new Put(row2);
+ put.addColumn(TEST_FAMILY, null, value);
+ testTable.put(put);
+ }
+
+ @Test
+ public void testCannotPutWithReadOnlyEnabled() throws IOException {
+ // Prepare a Put command with Read-Only mode enabled
+ enableReadOnlyMode();
+ final byte[] row1 = Bytes.toBytes("row1");
+ final byte[] value = Bytes.toBytes("abcd");
+ Put put = new Put(row1);
+ put.addColumn(TEST_FAMILY, null, value);
+
+ // Expect an IOException to result from the Put attempt
+ exception.expect(IOException.class);
+ exception.expectMessage("Operation not allowed in Read-Only Mode");
+
+ // This should throw the IOException
+ testTable.put(put);
+ }
+
+ @Test
+ public void testBatchPutWithReadOnlyDisabled() throws IOException,
InterruptedException {
+ // Successfully create and run a batch Put operation with Read-Only mode
disabled
+ disableReadOnlyMode();
+ List<Row> actions = new ArrayList<>();
+ actions.add(new Put(Bytes.toBytes("row10")).addColumn(TEST_FAMILY, null,
Bytes.toBytes("10")));
+ actions.add(new Delete(Bytes.toBytes("row10")));
+ testTable.batch(actions, null);
+ }
+
+ @Test
+ public void testCannotBatchPutWithReadOnlyEnabled() throws IOException,
InterruptedException {
+ // Create a batch Put operation that is expected to fail with Read-Only
mode enabled
+ enableReadOnlyMode();
+ List<Row> actions = new ArrayList<>();
+ actions.add(new Put(Bytes.toBytes("row11")).addColumn(TEST_FAMILY, null,
Bytes.toBytes("11")));
+ actions.add(new Delete(Bytes.toBytes("row11")));
+
+ // Expect an IOException to result from the batch Put attempt
+ exception.expect(IOException.class);
+ exception.expectMessage("Operation not allowed in Read-Only Mode");
+
+ // This should throw the IOException
+ testTable.batch(actions, null);
}
}
diff --git a/src/main/asciidoc/_chapters/configuration.adoc
b/src/main/asciidoc/_chapters/configuration.adoc
index 8ae6824cb22..35e361a44cc 100644
--- a/src/main/asciidoc/_chapters/configuration.adoc
+++ b/src/main/asciidoc/_chapters/configuration.adoc
@@ -1477,6 +1477,7 @@ Here are those configurations:
| hbase.coprocessor.region.classes
| hbase.coprocessor.regionserver.classes
| hbase.coprocessor.user.region.classes
+| hbase.global.readonly.enabled
| hbase.regionserver.thread.compaction.large
| hbase.regionserver.thread.compaction.small
| hbase.regionserver.thread.split