diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/coprocessor/BaseMasterObserver.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/coprocessor/BaseMasterObserver.java index 78326271be2..3aa5e7fcf63 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/coprocessor/BaseMasterObserver.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/coprocessor/BaseMasterObserver.java @@ -232,6 +232,16 @@ public class BaseMasterObserver implements MasterObserver { HRegionInfo regionInfo, boolean force) throws IOException { } + @Override + public void preRegionOffline(ObserverContext ctx, + HRegionInfo regionInfo) throws IOException { + } + + @Override + public void postRegionOffline(ObserverContext ctx, + HRegionInfo regionInfo) throws IOException { + } + @Override public void preBalance(ObserverContext ctx) throws IOException { diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/coprocessor/MasterObserver.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/coprocessor/MasterObserver.java index 186a2c70ec8..ed03fb57415 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/coprocessor/MasterObserver.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/coprocessor/MasterObserver.java @@ -440,6 +440,23 @@ public interface MasterObserver extends Coprocessor { void postUnassign(final ObserverContext ctx, final HRegionInfo regionInfo, final boolean force) throws IOException; + /** + * Called prior to marking a given region as offline. ctx.bypass() will not have any + * impact on this hook. + * @param ctx the environment to interact with the framework and master + * @param regionInfo + */ + void preRegionOffline(final ObserverContext ctx, + final HRegionInfo regionInfo) throws IOException; + + /** + * Called after the region has been marked offline. + * @param ctx the environment to interact with the framework and master + * @param regionInfo + */ + void postRegionOffline(final ObserverContext ctx, + final HRegionInfo regionInfo) throws IOException; + /** * Called prior to requesting rebalancing of the cluster regions, though after * the initial checks for regions in transition and the balance switch flag. 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 04125b77635..1689e9d3ed0 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 @@ -2252,7 +2252,11 @@ Server { } /** - * Special method, only used by hbck. + * Offline specified region from master's in-memory state. It will not attempt to + * reassign the region as in unassign. + * + * This is a special method that should be used by experts or hbck. + * */ @Override public OfflineRegionResponse offlineRegion(RpcController controller, OfflineRegionRequest request) @@ -2269,7 +2273,13 @@ Server { MetaReader.getRegion(this.catalogTracker, regionName); if (pair == null) throw new UnknownRegionException(Bytes.toStringBinary(regionName)); HRegionInfo hri = pair.getFirst(); + if (cpHost != null) { + cpHost.preRegionOffline(hri); + } this.assignmentManager.regionOffline(hri); + if (cpHost != null) { + cpHost.postRegionOffline(hri); + } } catch (IOException ioe) { throw new ServiceException(ioe); } diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/MasterCoprocessorHost.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/MasterCoprocessorHost.java index 4e1762959ef..63d4345d936 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/MasterCoprocessorHost.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/MasterCoprocessorHost.java @@ -811,6 +811,40 @@ public class MasterCoprocessorHost } } + void preRegionOffline(final HRegionInfo regionInfo) throws IOException { + ObserverContext ctx = null; + for (MasterEnvironment env : coprocessors) { + if (env.getInstance() instanceof MasterObserver) { + ctx = ObserverContext.createAndPrepare(env, ctx); + try { + ((MasterObserver) env.getInstance()).preRegionOffline(ctx, regionInfo); + } catch (Throwable e) { + handleCoprocessorThrowable(env, e); + } + if (ctx.shouldComplete()) { + break; + } + } + } + } + + void postRegionOffline(final HRegionInfo regionInfo) throws IOException { + ObserverContext ctx = null; + for (MasterEnvironment env : coprocessors) { + if (env.getInstance() instanceof MasterObserver) { + ctx = ObserverContext.createAndPrepare(env, ctx); + try { + ((MasterObserver) env.getInstance()).postRegionOffline(ctx, regionInfo); + } catch (Throwable e) { + handleCoprocessorThrowable(env, e); + } + if (ctx.shouldComplete()) { + break; + } + } + } + } + boolean preBalance() throws IOException { boolean bypass = false; ObserverContext ctx = null; diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/access/AccessController.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/access/AccessController.java index 0aac1a2a545..2c3763876cb 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/access/AccessController.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/access/AccessController.java @@ -674,6 +674,17 @@ public class AccessController extends BaseRegionObserver public void postUnassign(ObserverContext c, HRegionInfo regionInfo, boolean force) throws IOException {} + @Override + public void preRegionOffline(ObserverContext c, + HRegionInfo regionInfo) throws IOException { + requirePermission("regionOffline", regionInfo.getTableName(), null, null, Action.ADMIN); + } + + @Override + public void postRegionOffline(ObserverContext c, + HRegionInfo regionInfo) throws IOException { + } + @Override public void preBalance(ObserverContext c) throws IOException { diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/coprocessor/TestMasterObserver.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/coprocessor/TestMasterObserver.java index 188fd6a8ccc..4673ba1c805 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/coprocessor/TestMasterObserver.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/coprocessor/TestMasterObserver.java @@ -90,6 +90,8 @@ public class TestMasterObserver { private boolean postAssignCalled; private boolean preUnassignCalled; private boolean postUnassignCalled; + private boolean preRegionOfflineCalled; + private boolean postRegionOfflineCalled; private boolean preBalanceCalled; private boolean postBalanceCalled; private boolean preBalanceSwitchCalled; @@ -151,6 +153,8 @@ public class TestMasterObserver { postAssignCalled = false; preUnassignCalled = false; postUnassignCalled = false; + preRegionOfflineCalled = false; + postRegionOfflineCalled = false; preBalanceCalled = false; postBalanceCalled = false; preBalanceSwitchCalled = false; @@ -441,6 +445,26 @@ public class TestMasterObserver { return preUnassignCalled && !postUnassignCalled; } + @Override + public void preRegionOffline(ObserverContext env, + final HRegionInfo regionInfo) throws IOException { + preRegionOfflineCalled = true; + } + + @Override + public void postRegionOffline(ObserverContext env, + final HRegionInfo regionInfo) throws IOException { + postRegionOfflineCalled = true; + } + + public boolean wasRegionOfflineCalled() { + return preRegionOfflineCalled && postRegionOfflineCalled; + } + + public boolean preRegionOfflineCalledOnly() { + return preRegionOfflineCalled && !postRegionOfflineCalled; + } + @Override public void preBalance(ObserverContext env) throws IOException { diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestAccessController.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestAccessController.java index afd71ae97b4..bdbd89f35f1 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestAccessController.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestAccessController.java @@ -486,6 +486,29 @@ public class TestAccessController { verifyDenied(action, USER_CREATE, USER_RW, USER_RO, USER_NONE); } + @Test + public void testRegionOffline() throws Exception { + Map regions; + HTable table = new HTable(TEST_UTIL.getConfiguration(), TEST_TABLE); + try { + regions = table.getRegionLocations(); + } finally { + table.close(); + } + final Map.Entry firstRegion = regions.entrySet().iterator().next(); + + PrivilegedExceptionAction action = new PrivilegedExceptionAction() { + public Object run() throws Exception { + ACCESS_CONTROLLER.preRegionOffline(ObserverContext.createAndPrepare(CP_ENV, null), + firstRegion.getKey()); + return null; + } + }; + + verifyAllowed(action, SUPERUSER, USER_ADMIN, USER_OWNER); + verifyDenied(action, USER_CREATE, USER_RW, USER_RO, USER_NONE); + } + @Test public void testBalance() throws Exception { PrivilegedExceptionAction action = new PrivilegedExceptionAction() {