You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hbase.apache.org by zh...@apache.org on 2020/05/17 14:49:05 UTC
[hbase] branch branch-2 updated: HBASE-24135
TableStateNotFoundException happends when table creation if rsgroup is
enable (#1550)
This is an automated email from the ASF dual-hosted git repository.
zhangduo 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 80013ec HBASE-24135 TableStateNotFoundException happends when table creation if rsgroup is enable (#1550)
80013ec is described below
commit 80013ec11d077b7226cf835a4328cdba26b6472c
Author: XinSun <dd...@gmail.com>
AuthorDate: Sun May 17 20:44:17 2020 +0800
HBASE-24135 TableStateNotFoundException happends when table creation if rsgroup is enable (#1550)
Signed-off-by: Lijin Bin <bi...@apache.org>
Signed-off-by: Duo Zhang <zh...@apache.org>
---
.../hadoop/hbase/rsgroup/RSGroupAdminEndpoint.java | 94 +++----------
.../hadoop/hbase/rsgroup/RSGroupInfoManager.java | 7 +
.../hbase/rsgroup/RSGroupInfoManagerImpl.java | 88 +++++++++++-
.../apache/hadoop/hbase/rsgroup/RSGroupUtil.java | 40 ++++++
.../rsgroup/TestDetermineRSGroupInfoForTable.java | 148 +++++++++++++++++++++
.../hbase/rsgroup/TestRSGroupMappingScript.java | 2 +-
.../hadoop/hbase/rsgroup/TestRSGroupUtil.java | 95 +++++++++++++
7 files changed, 398 insertions(+), 76 deletions(-)
diff --git a/hbase-rsgroup/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupAdminEndpoint.java b/hbase-rsgroup/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupAdminEndpoint.java
index 1c2e76e..ad4c1af 100644
--- a/hbase-rsgroup/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupAdminEndpoint.java
+++ b/hbase-rsgroup/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupAdminEndpoint.java
@@ -109,48 +109,6 @@ public class RSGroupAdminEndpoint implements MasterCoprocessor, MasterObserver {
/** Provider for mapping principal names to Users */
private UserProvider userProvider;
- /** Get rsgroup table mapping script */
- private RSGroupMappingScript script;
-
- // Package visibility for testing
- static class RSGroupMappingScript {
-
- static final String RS_GROUP_MAPPING_SCRIPT = "hbase.rsgroup.table.mapping.script";
- static final String RS_GROUP_MAPPING_SCRIPT_TIMEOUT =
- "hbase.rsgroup.table.mapping.script.timeout";
-
- private ShellCommandExecutor rsgroupMappingScript;
-
- RSGroupMappingScript(Configuration conf) {
- String script = conf.get(RS_GROUP_MAPPING_SCRIPT);
- if (script == null || script.isEmpty()) {
- return;
- }
-
- rsgroupMappingScript = new ShellCommandExecutor(
- new String[] { script, "", "" }, null, null,
- conf.getLong(RS_GROUP_MAPPING_SCRIPT_TIMEOUT, 5000) // 5 seconds
- );
- }
-
- String getRSGroup(String namespace, String tablename) {
- if (rsgroupMappingScript == null) {
- return RSGroupInfo.DEFAULT_GROUP;
- }
- String[] exec = rsgroupMappingScript.getExecString();
- exec[1] = namespace;
- exec[2] = tablename;
- try {
- rsgroupMappingScript.execute();
- } catch (IOException e) {
- LOG.error(e.getMessage() + " placing back to default rsgroup");
- return RSGroupInfo.DEFAULT_GROUP;
- }
- return rsgroupMappingScript.getOutput().trim();
- }
-
- }
-
@Override
public void start(CoprocessorEnvironment env) throws IOException {
if (!(env instanceof HasMasterServices)) {
@@ -169,7 +127,6 @@ public class RSGroupAdminEndpoint implements MasterCoprocessor, MasterObserver {
// set the user-provider.
this.userProvider = UserProvider.instantiate(env.getConfiguration());
- this.script = new RSGroupMappingScript(env.getConfiguration());
}
@Override
@@ -500,30 +457,14 @@ public class RSGroupAdminEndpoint implements MasterCoprocessor, MasterObserver {
}
void assignTableToGroup(TableDescriptor desc) throws IOException {
- String groupName =
- master.getClusterSchema().getNamespace(desc.getTableName().getNamespaceAsString())
- .getConfigurationValue(RSGroupInfo.NAMESPACE_DESC_PROP_GROUP);
- if (groupName == null) {
- groupName = RSGroupInfo.DEFAULT_GROUP;
- }
-
- if (groupName.equals(RSGroupInfo.DEFAULT_GROUP)) {
- TableName tableName = desc.getTableName();
- groupName = script.getRSGroup(
- tableName.getNamespaceAsString(),
- tableName.getQualifierAsString()
- );
- LOG.info("rsgroup for " + tableName + " is " + groupName);
- }
-
- RSGroupInfo rsGroupInfo = groupAdminServer.getRSGroupInfo(groupName);
+ RSGroupInfo rsGroupInfo = groupInfoManager.determineRSGroupInfoForTable(desc.getTableName());
if (rsGroupInfo == null) {
- throw new ConstraintException("Default RSGroup (" + groupName + ") for this table's "
- + "namespace does not exist.");
+ throw new ConstraintException("Default RSGroup for this table " + desc.getTableName()
+ + " does not exist.");
}
if (!rsGroupInfo.containsTable(desc.getTableName())) {
- LOG.debug("Pre-moving table " + desc.getTableName() + " to RSGroup " + groupName);
- groupAdminServer.moveTables(Sets.newHashSet(desc.getTableName()), groupName);
+ LOG.debug("Pre-moving table " + desc.getTableName() + " to RSGroup " + rsGroupInfo.getName());
+ groupAdminServer.moveTables(Sets.newHashSet(desc.getTableName()), rsGroupInfo.getName());
}
}
@@ -536,17 +477,22 @@ public class RSGroupAdminEndpoint implements MasterCoprocessor, MasterObserver {
final ObserverContext<MasterCoprocessorEnvironment> ctx,
final TableDescriptor desc,
final RegionInfo[] regions) throws IOException {
- if (!desc.getTableName().isSystemTable() && !rsgroupHasServersOnline(desc)) {
- throw new HBaseIOException("No online servers in the rsgroup, which table " +
- desc.getTableName().getNameAsString() + " belongs to");
+ if (desc.getTableName().isSystemTable()) {
+ return;
+ }
+ RSGroupInfo rsGroupInfo = groupInfoManager.determineRSGroupInfoForTable(desc.getTableName());
+ if (rsGroupInfo == null) {
+ throw new ConstraintException("Default RSGroup for this table " + desc.getTableName()
+ + " does not exist.");
+ }
+ if (!RSGroupUtil.rsGroupHasOnlineServer(master, rsGroupInfo)) {
+ throw new HBaseIOException("No online servers in the rsgroup " + rsGroupInfo.getName()
+ + " which table " + desc.getTableName().getNameAsString() + " belongs to");
+ }
+ synchronized (groupInfoManager) {
+ groupInfoManager.moveTables(
+ Collections.singleton(desc.getTableName()), rsGroupInfo.getName());
}
- }
-
- // Assign table to default RSGroup.
- @Override
- public void postCreateTable(ObserverContext<MasterCoprocessorEnvironment> ctx,
- TableDescriptor desc, RegionInfo[] regions) throws IOException {
- assignTableToGroup(desc);
}
// Remove table from its RSGroup.
diff --git a/hbase-rsgroup/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupInfoManager.java b/hbase-rsgroup/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupInfoManager.java
index d2bf539..594f0f0 100644
--- a/hbase-rsgroup/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupInfoManager.java
+++ b/hbase-rsgroup/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupInfoManager.java
@@ -132,4 +132,11 @@ public interface RSGroupInfoManager {
* @param newName new rsgroup name
*/
void renameRSGroup(String oldName, String newName) throws IOException;
+
+ /**
+ * Determine {@code RSGroupInfo} for the given table.
+ * @param tableName table name
+ * @return {@link RSGroupInfo} which table should belong to
+ */
+ RSGroupInfo determineRSGroupInfoForTable(TableName tableName) throws IOException;
}
diff --git a/hbase-rsgroup/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupInfoManagerImpl.java b/hbase-rsgroup/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupInfoManagerImpl.java
index 2f452b3..8c2c313 100644
--- a/hbase-rsgroup/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupInfoManagerImpl.java
+++ b/hbase-rsgroup/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupInfoManagerImpl.java
@@ -32,8 +32,10 @@ import java.util.OptionalLong;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
+import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.Coprocessor;
import org.apache.hadoop.hbase.DoNotRetryIOException;
+import org.apache.hadoop.hbase.NamespaceDescriptor;
import org.apache.hadoop.hbase.ServerName;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder;
@@ -53,6 +55,7 @@ import org.apache.hadoop.hbase.constraint.ConstraintException;
import org.apache.hadoop.hbase.coprocessor.MultiRowMutationEndpoint;
import org.apache.hadoop.hbase.exceptions.DeserializationException;
import org.apache.hadoop.hbase.ipc.CoprocessorRpcChannel;
+import org.apache.hadoop.hbase.master.ClusterSchema;
import org.apache.hadoop.hbase.master.MasterServices;
import org.apache.hadoop.hbase.master.ServerListener;
import org.apache.hadoop.hbase.master.TableStateManager;
@@ -70,11 +73,13 @@ import org.apache.hadoop.hbase.util.Threads;
import org.apache.hadoop.hbase.zookeeper.ZKUtil;
import org.apache.hadoop.hbase.zookeeper.ZKWatcher;
import org.apache.hadoop.hbase.zookeeper.ZNodePaths;
+import org.apache.hadoop.util.Shell;
import org.apache.yetus.audience.InterfaceAudience;
import org.apache.zookeeper.KeeperException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import org.apache.hbase.thirdparty.com.google.common.annotations.VisibleForTesting;
import org.apache.hbase.thirdparty.com.google.common.collect.Lists;
import org.apache.hbase.thirdparty.com.google.common.collect.Maps;
import org.apache.hbase.thirdparty.com.google.common.collect.Sets;
@@ -133,14 +138,56 @@ final class RSGroupInfoManagerImpl implements RSGroupInfoManager {
private final ServerEventsListenerThread serverEventsListenerThread =
new ServerEventsListenerThread();
+ /** Get rsgroup table mapping script */
+ @VisibleForTesting
+ RSGroupMappingScript script;
+
+ // Package visibility for testing
+ static class RSGroupMappingScript {
+
+ static final String RS_GROUP_MAPPING_SCRIPT = "hbase.rsgroup.table.mapping.script";
+ static final String RS_GROUP_MAPPING_SCRIPT_TIMEOUT =
+ "hbase.rsgroup.table.mapping.script.timeout";
+
+ private Shell.ShellCommandExecutor rsgroupMappingScript;
+
+ RSGroupMappingScript(Configuration conf) {
+ String script = conf.get(RS_GROUP_MAPPING_SCRIPT);
+ if (script == null || script.isEmpty()) {
+ return;
+ }
+
+ rsgroupMappingScript = new Shell.ShellCommandExecutor(
+ new String[] { script, "", "" }, null, null,
+ conf.getLong(RS_GROUP_MAPPING_SCRIPT_TIMEOUT, 5000) // 5 seconds
+ );
+ }
+
+ String getRSGroup(String namespace, String tablename) {
+ if (rsgroupMappingScript == null) {
+ return null;
+ }
+ String[] exec = rsgroupMappingScript.getExecString();
+ exec[1] = namespace;
+ exec[2] = tablename;
+ try {
+ rsgroupMappingScript.execute();
+ } catch (IOException e) {
+ LOG.error("Failed to get RSGroup from script for table {}:{}", namespace, tablename, e);
+ return null;
+ }
+ return rsgroupMappingScript.getOutput().trim();
+ }
+ }
+
private RSGroupInfoManagerImpl(MasterServices masterServices) throws IOException {
this.masterServices = masterServices;
this.watcher = masterServices.getZooKeeper();
this.conn = masterServices.getConnection();
this.rsGroupStartupWorker = new RSGroupStartupWorker();
+ script = new RSGroupMappingScript(masterServices.getConfiguration());
}
-
private synchronized void init() throws IOException {
refresh();
serverEventsListenerThread.start();
@@ -366,6 +413,45 @@ final class RSGroupInfoManagerImpl implements RSGroupInfoManager {
flushConfig(newGroupMap);
}
+ /**
+ * Will try to get the rsgroup from {@code tableMap} first
+ * then try to get the rsgroup from {@code script}
+ * try to get the rsgroup from the {@link NamespaceDescriptor} lastly.
+ * If still not present, return default group.
+ */
+ @Override
+ public RSGroupInfo determineRSGroupInfoForTable(TableName tableName)
+ throws IOException {
+ RSGroupInfo groupFromOldRSGroupInfo = getRSGroup(getRSGroupOfTable(tableName));
+ if (groupFromOldRSGroupInfo != null) {
+ return groupFromOldRSGroupInfo;
+ }
+ // RSGroup information determined by administrator.
+ RSGroupInfo groupDeterminedByAdmin = getRSGroup(
+ script.getRSGroup(tableName.getNamespaceAsString(), tableName.getQualifierAsString()));
+ if (groupDeterminedByAdmin != null) {
+ return groupDeterminedByAdmin;
+ }
+ // Finally, we will try to fall back to namespace as rsgroup if exists
+ ClusterSchema clusterSchema = masterServices.getClusterSchema();
+ if (clusterSchema == null) {
+ if (TableName.isMetaTableName(tableName)) {
+ LOG.info("Can not get the namespace rs group config for meta table, since the" +
+ " meta table is not online yet, will use default group to assign meta first");
+ } else {
+ LOG.warn("ClusterSchema is null, can only use default rsgroup, should not happen?");
+ }
+ } else {
+ NamespaceDescriptor nd = clusterSchema.getNamespace(tableName.getNamespaceAsString());
+ RSGroupInfo groupNameOfNs =
+ getRSGroup(nd.getConfigurationValue(RSGroupInfo.NAMESPACE_DESC_PROP_GROUP));
+ if (groupNameOfNs != null) {
+ return groupNameOfNs;
+ }
+ }
+ return getRSGroup(RSGroupInfo.DEFAULT_GROUP);
+ }
+
List<RSGroupInfo> retrieveGroupListFromGroupTable() throws IOException {
List<RSGroupInfo> rsGroupInfoList = Lists.newArrayList();
try (Table table = conn.getTable(RSGROUP_TABLE_NAME);
diff --git a/hbase-rsgroup/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupUtil.java b/hbase-rsgroup/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupUtil.java
new file mode 100644
index 0000000..e410ec0
--- /dev/null
+++ b/hbase-rsgroup/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupUtil.java
@@ -0,0 +1,40 @@
+/**
+ * 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.rsgroup;
+
+import org.apache.hadoop.hbase.ServerName;
+import org.apache.hadoop.hbase.master.MasterServices;
+import org.apache.yetus.audience.InterfaceAudience;
+
+/**
+ * Helper class for RSGroup implementation
+ */
+@InterfaceAudience.Private
+public final class RSGroupUtil {
+
+ public static boolean rsGroupHasOnlineServer(MasterServices master, RSGroupInfo rsGroupInfo) {
+ for (ServerName onlineServer : master.getServerManager().createDestinationServersList()) {
+ if (rsGroupInfo.getServers().contains(onlineServer.getAddress())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+}
diff --git a/hbase-rsgroup/src/test/java/org/apache/hadoop/hbase/rsgroup/TestDetermineRSGroupInfoForTable.java b/hbase-rsgroup/src/test/java/org/apache/hadoop/hbase/rsgroup/TestDetermineRSGroupInfoForTable.java
new file mode 100644
index 0000000..e67127d
--- /dev/null
+++ b/hbase-rsgroup/src/test/java/org/apache/hadoop/hbase/rsgroup/TestDetermineRSGroupInfoForTable.java
@@ -0,0 +1,148 @@
+/**
+ * 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.rsgroup;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.io.IOException;
+import java.util.Collections;
+
+import org.apache.hadoop.hbase.HBaseClassTestRule;
+import org.apache.hadoop.hbase.HBaseTestingUtility;
+import org.apache.hadoop.hbase.HConstants;
+import org.apache.hadoop.hbase.NamespaceDescriptor;
+import org.apache.hadoop.hbase.TableName;
+import org.apache.hadoop.hbase.client.Admin;
+import org.apache.hadoop.hbase.coprocessor.CoprocessorHost;
+import org.apache.hadoop.hbase.master.HMaster;
+import org.apache.hadoop.hbase.regionserver.HRegionServer;
+import org.apache.hadoop.hbase.rsgroup.RSGroupInfoManagerImpl.RSGroupMappingScript;
+import org.apache.hadoop.hbase.testclassification.MediumTests;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Test {@link RSGroupInfoManager#determineRSGroupInfoForTable(TableName)}
+ */
+@Category({ MediumTests.class })
+public class TestDetermineRSGroupInfoForTable {
+
+ private static final Logger LOG = LoggerFactory.getLogger(TestDetermineRSGroupInfoForTable.class);
+
+ @ClassRule
+ public static final HBaseClassTestRule CLASS_RULE =
+ HBaseClassTestRule.forClass(TestDetermineRSGroupInfoForTable.class);
+
+ private static final HBaseTestingUtility UTIL = new HBaseTestingUtility();
+
+ private static HMaster master;
+
+ private static Admin admin;
+
+ private static RSGroupInfoManager rsGroupInfoManager;
+
+ private static RSGroupAdminClient rsGroupAdminClient;
+
+ private static final String GROUP_NAME = "rsg";
+
+ private static final String NAMESPACE_NAME = "ns";
+ private static final String OTHER_NAMESPACE_NAME = "other";
+
+ private static final TableName TABLE_NAME = TableName.valueOf(NAMESPACE_NAME, "tb");
+
+ @BeforeClass
+ public static void setUp() throws Exception {
+ UTIL.getConfiguration().set(
+ HConstants.HBASE_MASTER_LOADBALANCER_CLASS,
+ RSGroupBasedLoadBalancer.class.getName());
+ UTIL.getConfiguration().set(CoprocessorHost.MASTER_COPROCESSOR_CONF_KEY,
+ RSGroupAdminEndpoint.class.getName());
+ UTIL.startMiniCluster(5);
+ master = UTIL.getMiniHBaseCluster().getMaster();
+ admin = UTIL.getAdmin();
+ rsGroupAdminClient = new RSGroupAdminClient(UTIL.getConnection());
+
+ HRegionServer rs = UTIL.getHBaseCluster().getRegionServer(0);
+ rsGroupAdminClient.addRSGroup(GROUP_NAME);
+ rsGroupAdminClient.moveServers(
+ Collections.singleton(rs.getServerName().getAddress()), GROUP_NAME);
+ admin.createNamespace(NamespaceDescriptor.create(NAMESPACE_NAME)
+ .addConfiguration(RSGroupInfo.NAMESPACE_DESC_PROP_GROUP, GROUP_NAME)
+ .build());
+ admin.createNamespace(NamespaceDescriptor.create(OTHER_NAMESPACE_NAME).build());
+ }
+
+ @AfterClass
+ public static void tearDown() throws IOException {
+ admin.deleteNamespace(NAMESPACE_NAME);
+
+ UTIL.shutdownMiniCluster();
+ }
+
+ @Before
+ public void setUpBeforeMethod() throws IOException {
+ rsGroupInfoManager = RSGroupInfoManagerImpl.getInstance(master);
+ rsGroupInfoManager.start();
+ }
+
+ @After
+ public void tearDownAfterMethod() throws IOException {
+ rsGroupInfoManager = RSGroupInfoManagerImpl.getInstance(master);
+ }
+
+ @Test
+ public void testByDefault() throws IOException {
+ RSGroupInfo group =
+ rsGroupInfoManager.determineRSGroupInfoForTable(TableName.valueOf("tb"));
+ assertEquals(group.getName(), RSGroupInfo.DEFAULT_GROUP);
+ }
+
+ @Test
+ public void testDetermineByNamespaceConfig() throws IOException {
+ RSGroupInfo group = rsGroupInfoManager.determineRSGroupInfoForTable(TABLE_NAME);
+ assertEquals(group.getName(), GROUP_NAME);
+
+ group = rsGroupInfoManager.determineRSGroupInfoForTable(
+ TableName.valueOf(OTHER_NAMESPACE_NAME, "tb"));
+ assertEquals(group.getName(), RSGroupInfo.DEFAULT_GROUP);
+ }
+
+ /**
+ * determine by script
+ */
+ @Test
+ public void testDetermineByScript() throws IOException {
+ RSGroupMappingScript script = mock(RSGroupMappingScript.class);
+ when(script.getRSGroup(anyString(), anyString())).thenReturn(GROUP_NAME);
+ ((RSGroupInfoManagerImpl) rsGroupInfoManager).script = script;
+
+ RSGroupInfo group = rsGroupInfoManager.determineRSGroupInfoForTable(TABLE_NAME);
+ assertEquals(group.getName(), GROUP_NAME);
+ }
+
+}
diff --git a/hbase-rsgroup/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupMappingScript.java b/hbase-rsgroup/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupMappingScript.java
index df2f89b..d626ab3 100644
--- a/hbase-rsgroup/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupMappingScript.java
+++ b/hbase-rsgroup/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupMappingScript.java
@@ -26,7 +26,7 @@ import java.io.PrintWriter;
import org.apache.hadoop.hbase.HBaseClassTestRule;
import org.apache.hadoop.hbase.HBaseTestingUtility;
import org.apache.hadoop.hbase.TableName;
-import org.apache.hadoop.hbase.rsgroup.RSGroupAdminEndpoint.RSGroupMappingScript;
+import org.apache.hadoop.hbase.rsgroup.RSGroupInfoManagerImpl.RSGroupMappingScript;
import org.apache.hadoop.hbase.testclassification.SmallTests;
import org.junit.After;
import org.junit.Assert;
diff --git a/hbase-rsgroup/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupUtil.java b/hbase-rsgroup/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupUtil.java
new file mode 100644
index 0000000..3c73437
--- /dev/null
+++ b/hbase-rsgroup/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupUtil.java
@@ -0,0 +1,95 @@
+/**
+ * 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.rsgroup;
+
+import java.io.IOException;
+import java.util.Collections;
+
+import org.apache.hadoop.hbase.HBaseTestingUtility;
+import org.apache.hadoop.hbase.HConstants;
+import org.apache.hadoop.hbase.NamespaceDescriptor;
+import org.apache.hadoop.hbase.client.Admin;
+import org.apache.hadoop.hbase.coprocessor.CoprocessorHost;
+import org.apache.hadoop.hbase.master.HMaster;
+import org.apache.hadoop.hbase.regionserver.HRegionServer;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class TestRSGroupUtil {
+
+ private static final HBaseTestingUtility UTIL = new HBaseTestingUtility();
+
+ private static Admin admin;
+
+ private static HMaster master;
+
+ private static RSGroupAdminClient rsGroupAdminClient;
+
+ private static final String GROUP_NAME = "rsg";
+
+ private static final String NAMESPACE_NAME = "ns";
+
+ private static RSGroupInfoManager rsGroupInfoManager;
+
+ @BeforeClass
+ public static void setUp() throws Exception {
+ UTIL.getConfiguration().set(
+ HConstants.HBASE_MASTER_LOADBALANCER_CLASS,
+ RSGroupBasedLoadBalancer.class.getName());
+ UTIL.getConfiguration().set(CoprocessorHost.MASTER_COPROCESSOR_CONF_KEY,
+ RSGroupAdminEndpoint.class.getName());
+ UTIL.startMiniCluster(5);
+ master = UTIL.getMiniHBaseCluster().getMaster();
+ admin = UTIL.getAdmin();
+ rsGroupAdminClient = new RSGroupAdminClient(UTIL.getConnection());
+
+ HRegionServer rs = UTIL.getHBaseCluster().getRegionServer(0);
+ rsGroupAdminClient.addRSGroup(GROUP_NAME);
+ rsGroupAdminClient.moveServers(Collections.singleton(rs.getServerName().getAddress()), GROUP_NAME);
+ admin.createNamespace(NamespaceDescriptor.create(NAMESPACE_NAME)
+ .addConfiguration(RSGroupInfo.NAMESPACE_DESC_PROP_GROUP, GROUP_NAME)
+ .build());
+
+ rsGroupInfoManager = RSGroupInfoManagerImpl.getInstance(master);
+ rsGroupInfoManager.start();
+ }
+
+ @AfterClass
+ public static void tearDown() throws IOException {
+ admin.deleteNamespace(NAMESPACE_NAME);
+
+ UTIL.shutdownMiniCluster();
+ }
+
+ @Test
+ public void rsGroupHasOnlineServer() throws IOException {
+ rsGroupInfoManager.refresh();
+ RSGroupInfo defaultGroup = rsGroupInfoManager.getRSGroup(RSGroupInfo.DEFAULT_GROUP);
+ Assert.assertTrue(RSGroupUtil.rsGroupHasOnlineServer(master, defaultGroup));
+
+ RSGroupInfo rsGroup = rsGroupInfoManager.getRSGroup(GROUP_NAME);
+ Assert.assertTrue(RSGroupUtil.rsGroupHasOnlineServer(master, rsGroup));
+
+ rsGroupAdminClient.addRSGroup("empty");
+ rsGroupInfoManager.refresh();
+ RSGroupInfo emptyGroup = rsGroupInfoManager.getRSGroup("empty");
+ Assert.assertFalse(RSGroupUtil.rsGroupHasOnlineServer(master, emptyGroup));
+ }
+}