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