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/02/09 02:59:58 UTC

[hbase] branch HBASE-22514 updated (e0c2f20 -> 660d105)

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

zhangduo pushed a change to branch HBASE-22514
in repository https://gitbox.apache.org/repos/asf/hbase.git.


 discard e0c2f20  HBASE-23807 Make rsgroup related shell command to use the new admin methods (#1148)
 discard d794deb  HBASE-23276 Add admin methods to get tables within a group (#1118)
 discard 9051e72  HBASE-23235 Re-enable TestRSGroupsKillRS.testLowerMetaGroupVersion (#1117)
 discard 864c0ab  HBASE-23253 Rewrite rsgroup related UTs with the new methods introduced in HBASE-22932 (#813)
 discard 0a4503c  HBASE-22932 Add rs group management methods in Admin and AsyncAdmin (#657)
 discard 674c6f0  HBASE-23050 Use RSGroupInfoManager to get rsgroups in master UI's rsgroup part (#776)
 discard 2ce89bf  HBASE-23232 Remove rsgroup profile from pom.xml of hbase-assembly (#779)
 discard fd9f7ca  HBASE-23081 Add an option to enable/disable rs group feature (#691)
     new 247d063  HBASE-23081 Add an option to enable/disable rs group feature (#691)
     new c53d12a  HBASE-23232 Remove rsgroup profile from pom.xml of hbase-assembly (#779)
     new c71ceb0  HBASE-23050 Use RSGroupInfoManager to get rsgroups in master UI's rsgroup part (#776)
     new 6b27cef  HBASE-22932 Add rs group management methods in Admin and AsyncAdmin (#657)
     new 57804ae  HBASE-23253 Rewrite rsgroup related UTs with the new methods introduced in HBASE-22932 (#813)
     new 300db27  HBASE-23235 Re-enable TestRSGroupsKillRS.testLowerMetaGroupVersion (#1117)
     new 6db3856  HBASE-23276 Add admin methods to get tables within a group (#1118)
     new 660d105  HBASE-23807 Make rsgroup related shell command to use the new admin methods (#1148)

This update added new revisions after undoing existing revisions.
That is to say, some revisions that were in the old version of the
branch are not in the new version.  This situation occurs
when a user --force pushes a change and generates a repository
containing something like this:

 * -- * -- B -- O -- O -- O   (e0c2f20)
            \
             N -- N -- N   refs/heads/HBASE-22514 (660d105)

You should already have received notification emails for all of the O
revisions, and so the following emails describe only the N revisions
from the common base, B.

Any revisions marked "omit" are not gone; other references still
refer to them.  Any revisions marked "discard" are gone forever.

The 8 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../java/org/apache/hadoop/hbase/rsgroup/DisabledRSGroupInfoManager.java | 1 -
 1 file changed, 1 deletion(-)


[hbase] 06/08: HBASE-23235 Re-enable TestRSGroupsKillRS.testLowerMetaGroupVersion (#1117)

Posted by zh...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

zhangduo pushed a commit to branch HBASE-22514
in repository https://gitbox.apache.org/repos/asf/hbase.git

commit 300db27479007d994d81abbc8742e2724828e614
Author: Duo Zhang <zh...@apache.org>
AuthorDate: Mon Feb 3 19:41:49 2020 +0800

    HBASE-23235 Re-enable TestRSGroupsKillRS.testLowerMetaGroupVersion (#1117)
    
    Signed-off-by: Guanghao Zhang <zg...@apache.org>
---
 .../java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsKillRS.java     | 5 -----
 1 file changed, 5 deletions(-)

diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsKillRS.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsKillRS.java
index bb4f441..d6eaeb2 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsKillRS.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsKillRS.java
@@ -51,7 +51,6 @@ import org.junit.AfterClass;
 import org.junit.Before;
 import org.junit.BeforeClass;
 import org.junit.ClassRule;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.experimental.categories.Category;
 import org.slf4j.Logger;
@@ -231,9 +230,6 @@ public class TestRSGroupsKillRS extends TestRSGroupsBase {
     TEST_UTIL.waitTableAvailable(tableName, 30000);
   }
 
-  // TODO: can not change meta group for now as we can not change the table descriptor of meta
-  // table, this has to be done before we merge back to master.
-  @Ignore
   @Test
   public void testLowerMetaGroupVersion() throws Exception {
     // create a rsgroup and move one regionserver to it
@@ -247,7 +243,6 @@ public class TestRSGroupsKillRS extends TestRSGroupsBase {
     ADMIN.setRSGroup(toAddTables, groupName);
     assertTrue(RS_GROUP_ADMIN_CLIENT.getRSGroupInfo(groupName).getTables()
       .contains(TableName.META_TABLE_NAME));
-    TEST_UTIL.waitTableAvailable(tableName, 30000);
 
     // restart the regionserver in meta_group, and lower its version
     String originVersion = "";


[hbase] 03/08: HBASE-23050 Use RSGroupInfoManager to get rsgroups in master UI's rsgroup part (#776)

Posted by zh...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

zhangduo pushed a commit to branch HBASE-22514
in repository https://gitbox.apache.org/repos/asf/hbase.git

commit c71ceb0307969d6ad9dd22c088ada22e00d64588
Author: Guangxu Cheng <gu...@gmail.com>
AuthorDate: Thu Oct 31 03:05:52 2019 +0800

    HBASE-23050 Use RSGroupInfoManager to get rsgroups in master UI's rsgroup part (#776)
    
    Signed-off-by: Duo Zhang <zh...@apache.org>
---
 .../jamon/org/apache/hadoop/hbase/tmpl/master/MasterStatusTmpl.jamon   | 3 ++-
 .../jamon/org/apache/hadoop/hbase/tmpl/master/RSGroupListTmpl.jamon    | 2 +-
 .../main/java/org/apache/hadoop/hbase/rsgroup/RSGroupInfoManager.java  | 2 +-
 3 files changed, 4 insertions(+), 3 deletions(-)

diff --git a/hbase-server/src/main/jamon/org/apache/hadoop/hbase/tmpl/master/MasterStatusTmpl.jamon b/hbase-server/src/main/jamon/org/apache/hadoop/hbase/tmpl/master/MasterStatusTmpl.jamon
index 39e0a60..d282f33 100644
--- a/hbase-server/src/main/jamon/org/apache/hadoop/hbase/tmpl/master/MasterStatusTmpl.jamon
+++ b/hbase-server/src/main/jamon/org/apache/hadoop/hbase/tmpl/master/MasterStatusTmpl.jamon
@@ -52,6 +52,7 @@ org.apache.hadoop.hbase.master.RegionState;
 org.apache.hadoop.hbase.master.ServerManager;
 org.apache.hadoop.hbase.protobuf.ProtobufUtil;
 org.apache.hadoop.hbase.quotas.QuotaUtil;
+org.apache.hadoop.hbase.rsgroup.RSGroupInfoManager;
 org.apache.hadoop.hbase.security.access.PermissionStorage;
 org.apache.hadoop.hbase.security.visibility.VisibilityConstants;
 org.apache.hadoop.hbase.shaded.protobuf.generated.SnapshotProtos.SnapshotDescription;
@@ -224,7 +225,7 @@ AssignmentManager assignmentManager = master.getAssignmentManager();
           <& AssignmentManagerStatusTmpl; assignmentManager=master.getAssignmentManager()&>
         </%if>
         <%if !master.isInMaintenanceMode() %>
-          <%if master.getMasterCoprocessorHost().findCoprocessor("RSGroupAdminEndpoint") != null &&
+          <%if master.getConfiguration().getBoolean(RSGroupInfoManager.RS_GROUP_ENABLED, false) &&
             serverManager.getOnlineServersList().size() > 0 %>
             <section>
               <h2><a name="rsgroup">RSGroup</a></h2>
diff --git a/hbase-server/src/main/jamon/org/apache/hadoop/hbase/tmpl/master/RSGroupListTmpl.jamon b/hbase-server/src/main/jamon/org/apache/hadoop/hbase/tmpl/master/RSGroupListTmpl.jamon
index 7f15aa6..765eab9 100644
--- a/hbase-server/src/main/jamon/org/apache/hadoop/hbase/tmpl/master/RSGroupListTmpl.jamon
+++ b/hbase-server/src/main/jamon/org/apache/hadoop/hbase/tmpl/master/RSGroupListTmpl.jamon
@@ -41,7 +41,7 @@ ServerManager serverManager;
     org.apache.hadoop.util.StringUtils.TraditionalBinaryPrefix;
 </%import>
 <%java>
-List<RSGroupInfo> groups = RSGroupTableAccessor.getAllRSGroupInfo(master.getConnection());
+List<RSGroupInfo> groups = master.getRSGroupInfoManager().listRSGroups();
 </%java>
 <%if (groups != null && groups.size() > 0)%>
 
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupInfoManager.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupInfoManager.java
index 3f73f78..0eb15e9 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupInfoManager.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupInfoManager.java
@@ -32,7 +32,7 @@ import org.apache.yetus.audience.InterfaceAudience;
 @InterfaceAudience.Private
 public interface RSGroupInfoManager {
 
-  static final String RS_GROUP_ENABLED = "hbase.balancer.rsgroup.enabled";
+  public static final String RS_GROUP_ENABLED = "hbase.balancer.rsgroup.enabled";
 
   void start();
 


[hbase] 04/08: HBASE-22932 Add rs group management methods in Admin and AsyncAdmin (#657)

Posted by zh...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

zhangduo pushed a commit to branch HBASE-22514
in repository https://gitbox.apache.org/repos/asf/hbase.git

commit 6b27ceff5e9615f891d99ac534e820fb6f14cff8
Author: linkaline <li...@gmail.com>
AuthorDate: Tue Nov 5 14:15:51 2019 +0800

    HBASE-22932 Add rs group management methods in Admin and AsyncAdmin (#657)
    
    Signed-off-by: Duo Zhang <zh...@apache.org>
---
 .../java/org/apache/hadoop/hbase/client/Admin.java |   78 ++
 .../hadoop/hbase/client/AdminOverAsyncAdmin.java   |   52 +
 .../org/apache/hadoop/hbase/client/AsyncAdmin.java |   80 +-
 .../hadoop/hbase/client/AsyncHBaseAdmin.java       |   51 +
 .../hadoop/hbase/client/RawAsyncHBaseAdmin.java    |  209 +++-
 .../hbase/shaded/protobuf/RequestConverter.java    |   37 +-
 .../hbase/rsgroup/IntegrationTestRSGroup.java      |    4 +-
 .../src/main/protobuf/Master.proto                 |   19 +
 .../org/apache/hadoop/hbase/master/HMaster.java    |    2 +-
 .../hadoop/hbase/master/MasterCoprocessorHost.java |    6 +-
 .../hadoop/hbase/master/MasterRpcServices.java     |  169 +++-
 .../apache/hadoop/hbase/master/MasterServices.java |    9 +
 .../hbase/rsgroup/DisabledRSGroupInfoManager.java  |   13 +-
 .../apache/hadoop/hbase/rsgroup/RSGroupAdmin.java  |    4 +
 .../hadoop/hbase/rsgroup/RSGroupAdminClient.java   | 1041 ++++++++++++++++++--
 .../hadoop/hbase/rsgroup/RSGroupAdminEndpoint.java |   25 +-
 .../hadoop/hbase/rsgroup/RSGroupAdminServer.java   |  496 ----------
 .../hbase/rsgroup/RSGroupAdminServiceImpl.java     |   83 +-
 .../hadoop/hbase/rsgroup/RSGroupInfoManager.java   |   20 +-
 .../hbase/rsgroup/RSGroupInfoManagerImpl.java      |  444 ++++++++-
 .../hbase/rsgroup/RSGroupMajorCompactionTTL.java   |    7 +-
 .../apache/hadoop/hbase/rsgroup/RSGroupUtil.java   |    8 +-
 .../hbase/security/access/AccessController.java    |   81 ++
 .../hbase/master/MockNoopMasterServices.java       |    5 +
 .../hbase/rsgroup/TestMigrateRSGroupInfo.java      |   28 +-
 .../hadoop/hbase/rsgroup/TestRSGroupsAdmin1.java   |  131 ++-
 .../hadoop/hbase/rsgroup/TestRSGroupsAdmin2.java   |  130 +--
 .../hadoop/hbase/rsgroup/TestRSGroupsBalance.java  |   15 +-
 .../hadoop/hbase/rsgroup/TestRSGroupsBase.java     |   97 +-
 .../hadoop/hbase/rsgroup/TestRSGroupsBasics.java   |   43 +-
 .../hadoop/hbase/rsgroup/TestRSGroupsKillRS.java   |   32 +-
 .../hbase/rsgroup/TestRSGroupsOfflineMode.java     |    6 +-
 .../hadoop/hbase/rsgroup/TestRSGroupsWithACL.java  |   47 +-
 .../hbase/rsgroup/VerifyingRSGroupAdminClient.java |   29 +-
 .../hadoop/hbase/thrift2/client/ThriftAdmin.java   |   52 +
 35 files changed, 2578 insertions(+), 975 deletions(-)

diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/Admin.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/Admin.java
index 5419c16..2b4eaf8 100644
--- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/Admin.java
+++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/Admin.java
@@ -45,6 +45,7 @@ import org.apache.hadoop.hbase.client.replication.ReplicationPeerConfigUtil;
 import org.apache.hadoop.hbase.client.replication.TableCFs;
 import org.apache.hadoop.hbase.client.security.SecurityCapability;
 import org.apache.hadoop.hbase.ipc.CoprocessorRpcChannel;
+import org.apache.hadoop.hbase.net.Address;
 import org.apache.hadoop.hbase.quotas.QuotaFilter;
 import org.apache.hadoop.hbase.quotas.QuotaSettings;
 import org.apache.hadoop.hbase.quotas.SpaceQuotaSnapshotView;
@@ -53,6 +54,7 @@ import org.apache.hadoop.hbase.replication.ReplicationException;
 import org.apache.hadoop.hbase.replication.ReplicationPeerConfig;
 import org.apache.hadoop.hbase.replication.ReplicationPeerDescription;
 import org.apache.hadoop.hbase.replication.SyncReplicationState;
+import org.apache.hadoop.hbase.rsgroup.RSGroupInfo;
 import org.apache.hadoop.hbase.security.access.GetUserPermissionsRequest;
 import org.apache.hadoop.hbase.security.access.Permission;
 import org.apache.hadoop.hbase.security.access.UserPermission;
@@ -2262,4 +2264,80 @@ public interface Admin extends Abortable, Closeable {
    */
   boolean isSnapshotCleanupEnabled() throws IOException;
 
+  /**
+   * Creates a new RegionServer group with the given name
+   * @param groupName the name of the group
+   * @throws IOException if a remote or network exception occurs
+   */
+  void addRSGroup(String groupName) throws IOException;
+
+  /**
+   * Get group info for the given group name
+   * @param groupName the group name
+   * @return group info
+   * @throws IOException if a remote or network exception occurs
+   */
+  RSGroupInfo getRSGroup(String groupName) throws IOException;
+
+  /**
+   * Get group info for the given hostPort
+   * @param hostPort HostPort to get RSGroupInfo for
+   * @throws IOException if a remote or network exception occurs
+   */
+  RSGroupInfo getRSGroup(Address hostPort) throws IOException;
+
+  /**
+   * Get group info for the given table
+   * @param tableName table name to get RSGroupInfo for
+   * @throws IOException if a remote or network exception occurs
+   */
+  RSGroupInfo getRSGroup(TableName tableName) throws IOException;
+
+  /**
+   * Lists current set of RegionServer groups
+   * @throws IOException if a remote or network exception occurs
+   */
+  List<RSGroupInfo> listRSGroups() throws IOException;
+
+  /**
+   * Remove RegionServer group associated with the given name
+   * @param groupName the group name
+   * @throws IOException if a remote or network exception occurs
+   */
+  void removeRSGroup(String groupName) throws IOException;
+
+  /**
+   * Remove decommissioned servers from group
+   *  1. Sometimes we may find the server aborted due to some hardware failure and we must offline
+   *     the server for repairing. Or we need to move some servers to join other clusters.
+   *     So we need to remove these servers from the group.
+   *  2. Dead/recovering/live servers will be disallowed.
+   * @param servers set of servers to remove
+   * @throws IOException if a remote or network exception occurs
+   */
+  void removeRSGroup(Set<Address> servers) throws IOException;
+
+  /**
+   * Move given set of servers to the specified target RegionServer group
+   * @param servers set of servers to move
+   * @param targetGroup the group to move servers to
+   * @throws IOException if a remote or network exception occurs
+   */
+  void moveToRSGroup(Set<Address> servers, String targetGroup) throws IOException;
+
+  /**
+   * Set the RegionServer group for tables
+   * @param tables tables to set group for
+   * @param groupName group name for tables
+   * @throws IOException if a remote or network exception occurs
+   */
+  void setRSGroup(Set<TableName> tables, String groupName) throws IOException;
+
+  /**
+   * Balance regions in the given RegionServer group
+   * @param groupName the group name
+   * @return boolean Whether balance ran or not
+   * @throws IOException if a remote or network exception occurs
+   */
+  boolean balanceRSGroup(String groupName) throws IOException;
 }
diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AdminOverAsyncAdmin.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AdminOverAsyncAdmin.java
index f1f5b2a..060170c 100644
--- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AdminOverAsyncAdmin.java
+++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AdminOverAsyncAdmin.java
@@ -49,6 +49,7 @@ import org.apache.hadoop.hbase.TableNotFoundException;
 import org.apache.hadoop.hbase.client.replication.TableCFs;
 import org.apache.hadoop.hbase.client.security.SecurityCapability;
 import org.apache.hadoop.hbase.ipc.CoprocessorRpcChannel;
+import org.apache.hadoop.hbase.net.Address;
 import org.apache.hadoop.hbase.quotas.QuotaFilter;
 import org.apache.hadoop.hbase.quotas.QuotaSettings;
 import org.apache.hadoop.hbase.quotas.SpaceQuotaSnapshotView;
@@ -56,6 +57,7 @@ import org.apache.hadoop.hbase.regionserver.wal.FailedLogCloseException;
 import org.apache.hadoop.hbase.replication.ReplicationPeerConfig;
 import org.apache.hadoop.hbase.replication.ReplicationPeerDescription;
 import org.apache.hadoop.hbase.replication.SyncReplicationState;
+import org.apache.hadoop.hbase.rsgroup.RSGroupInfo;
 import org.apache.hadoop.hbase.security.access.GetUserPermissionsRequest;
 import org.apache.hadoop.hbase.security.access.Permission;
 import org.apache.hadoop.hbase.security.access.UserPermission;
@@ -960,4 +962,54 @@ class AdminOverAsyncAdmin implements Admin {
     return get(admin.isSnapshotCleanupEnabled());
   }
 
+  @Override
+  public RSGroupInfo getRSGroup(String groupName) throws IOException {
+    return get(admin.getRSGroup(groupName));
+  }
+
+  @Override
+  public void moveToRSGroup(Set<Address> servers, String groupName) throws IOException {
+    get(admin.moveToRSGroup(servers, groupName));
+  }
+
+  @Override
+  public void addRSGroup(String groupName) throws IOException {
+    get(admin.addRSGroup(groupName));
+  }
+
+  @Override
+  public void removeRSGroup(String groupName) throws IOException {
+    get(admin.removeRSGroup(groupName));
+  }
+
+  @Override
+  public boolean balanceRSGroup(String groupName) throws IOException {
+    return get(admin.balanceRSGroup(groupName));
+  }
+
+  @Override
+  public List<RSGroupInfo> listRSGroups() throws IOException {
+    return get(admin.listRSGroups());
+  }
+
+  @Override
+  public RSGroupInfo getRSGroup(Address hostPort) throws IOException {
+    return get(admin.getRSGroup(hostPort));
+  }
+
+  @Override
+  public void removeRSGroup(Set<Address> servers) throws IOException {
+    get(admin.removeRSGroup(servers));
+  }
+
+  @Override
+  public RSGroupInfo getRSGroup(TableName tableName) throws IOException {
+    return get(admin.getRSGroup(tableName));
+  }
+
+  @Override
+  public void setRSGroup(Set<TableName> tables, String groupName) throws IOException {
+    get(admin.setRSGroup(tables, groupName));
+  }
+
 }
diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AsyncAdmin.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AsyncAdmin.java
index a53646d..12926cc 100644
--- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AsyncAdmin.java
+++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AsyncAdmin.java
@@ -40,12 +40,14 @@ import org.apache.hadoop.hbase.ServerName;
 import org.apache.hadoop.hbase.TableName;
 import org.apache.hadoop.hbase.client.replication.TableCFs;
 import org.apache.hadoop.hbase.client.security.SecurityCapability;
+import org.apache.hadoop.hbase.net.Address;
 import org.apache.hadoop.hbase.quotas.QuotaFilter;
 import org.apache.hadoop.hbase.quotas.QuotaSettings;
 import org.apache.hadoop.hbase.quotas.SpaceQuotaSnapshotView;
 import org.apache.hadoop.hbase.replication.ReplicationPeerConfig;
 import org.apache.hadoop.hbase.replication.ReplicationPeerDescription;
 import org.apache.hadoop.hbase.replication.SyncReplicationState;
+import org.apache.hadoop.hbase.rsgroup.RSGroupInfo;
 import org.apache.hadoop.hbase.security.access.GetUserPermissionsRequest;
 import org.apache.hadoop.hbase.security.access.Permission;
 import org.apache.hadoop.hbase.security.access.UserPermission;
@@ -1381,7 +1383,7 @@ public interface AsyncAdmin {
    * @param newTableName name of the new table where the table will be created
    * @param preserveSplits True if the splits should be preserved
    */
-  CompletableFuture<Void>  cloneTableSchema(final TableName tableName,
+  CompletableFuture<Void> cloneTableSchema(final TableName tableName,
       final TableName newTableName, final boolean preserveSplits);
 
   /**
@@ -1507,4 +1509,80 @@ public interface AsyncAdmin {
    */
   CompletableFuture<Boolean> isSnapshotCleanupEnabled();
 
+  /**
+   * Creates a new RegionServer group with the given name
+   * @param groupName the name of the group
+   * @throws IOException if a remote or network exception occurs
+   */
+  CompletableFuture<Void> addRSGroup(String groupName);
+
+  /**
+   * Get group info for the given group name
+   * @param groupName the group name
+   * @return group info
+   * @throws IOException if a remote or network exception occurs
+   */
+  CompletableFuture<RSGroupInfo> getRSGroup(String groupName);
+
+  /**
+   * Get group info for the given hostPort
+   * @param hostPort HostPort to get RSGroupInfo for
+   * @throws IOException if a remote or network exception occurs
+   */
+  CompletableFuture<RSGroupInfo> getRSGroup(Address hostPort);
+
+  /**
+   * Get group info for the given table
+   * @param tableName table name to get RSGroupInfo for
+   * @throws IOException if a remote or network exception occurs
+   */
+  CompletableFuture<RSGroupInfo> getRSGroup(TableName tableName);
+
+  /**
+   * Lists current set of RegionServer groups
+   * @throws IOException if a remote or network exception occurs
+   */
+  CompletableFuture<List<RSGroupInfo>> listRSGroups();
+
+  /**
+   * Remove RegionServer group associated with the given name
+   * @param groupName the group name
+   * @throws IOException if a remote or network exception occurs
+   */
+  CompletableFuture<Void> removeRSGroup(String groupName);
+
+  /**
+   * Remove decommissioned servers from group
+   *  1. Sometimes we may find the server aborted due to some hardware failure and we must offline
+   *     the server for repairing. Or we need to move some servers to join other clusters.
+   *     So we need to remove these servers from the group.
+   *  2. Dead/recovering/live servers will be disallowed.
+   * @param servers set of servers to remove
+   * @throws IOException if a remote or network exception occurs
+   */
+  CompletableFuture<Void> removeRSGroup(Set<Address> servers);
+
+  /**
+   * Move given set of servers to the specified target RegionServer group
+   * @param servers set of servers to move
+   * @param groupName the group to move servers to
+   * @throws IOException if a remote or network exception occurs
+   */
+  CompletableFuture<Void> moveToRSGroup(Set<Address> servers, String groupName);
+
+  /**
+   * Set the RegionServer group for tables
+   * @param tables tables to set group for
+   * @param groupName group name for tables
+   * @throws IOException if a remote or network exception occurs
+   */
+  CompletableFuture<Void> setRSGroup(Set<TableName> tables, String groupName);
+
+  /**
+   * Balance regions in the given RegionServer group
+   * @param groupName the group name
+   * @return boolean Whether balance ran or not
+   * @throws IOException if a remote or network exception occurs
+   */
+  CompletableFuture<Boolean> balanceRSGroup(String groupName);
 }
diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AsyncHBaseAdmin.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AsyncHBaseAdmin.java
index ffb484c..459c225 100644
--- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AsyncHBaseAdmin.java
+++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AsyncHBaseAdmin.java
@@ -36,12 +36,14 @@ import org.apache.hadoop.hbase.ServerName;
 import org.apache.hadoop.hbase.TableName;
 import org.apache.hadoop.hbase.client.replication.TableCFs;
 import org.apache.hadoop.hbase.client.security.SecurityCapability;
+import org.apache.hadoop.hbase.net.Address;
 import org.apache.hadoop.hbase.quotas.QuotaFilter;
 import org.apache.hadoop.hbase.quotas.QuotaSettings;
 import org.apache.hadoop.hbase.quotas.SpaceQuotaSnapshot;
 import org.apache.hadoop.hbase.replication.ReplicationPeerConfig;
 import org.apache.hadoop.hbase.replication.ReplicationPeerDescription;
 import org.apache.hadoop.hbase.replication.SyncReplicationState;
+import org.apache.hadoop.hbase.rsgroup.RSGroupInfo;
 import org.apache.hadoop.hbase.security.access.GetUserPermissionsRequest;
 import org.apache.hadoop.hbase.security.access.Permission;
 import org.apache.hadoop.hbase.security.access.UserPermission;
@@ -838,4 +840,53 @@ class AsyncHBaseAdmin implements AsyncAdmin {
     return wrap(rawAdmin.isSnapshotCleanupEnabled());
   }
 
+  public CompletableFuture<RSGroupInfo> getRSGroup(String groupName) {
+    return wrap(rawAdmin.getRSGroup(groupName));
+  }
+
+  @Override
+  public CompletableFuture<Void> moveToRSGroup(Set<Address> servers, String groupName) {
+    return wrap(rawAdmin.moveToRSGroup(servers, groupName));
+  }
+
+  @Override
+  public CompletableFuture<Void> addRSGroup(String groupName) {
+    return wrap(rawAdmin.addRSGroup(groupName));
+  }
+
+  @Override
+  public CompletableFuture<Void> removeRSGroup(String groupName) {
+    return wrap(rawAdmin.removeRSGroup(groupName));
+  }
+
+  @Override
+  public CompletableFuture<Boolean> balanceRSGroup(String groupName) {
+    return wrap(rawAdmin.balanceRSGroup(groupName));
+  }
+
+  @Override
+  public CompletableFuture<List<RSGroupInfo>> listRSGroups() {
+    return wrap(rawAdmin.listRSGroups());
+  }
+
+  @Override
+  public CompletableFuture<RSGroupInfo> getRSGroup(Address hostPort) {
+    return wrap(rawAdmin.getRSGroup(hostPort));
+  }
+
+  @Override
+  public CompletableFuture<Void> removeRSGroup(Set<Address> servers) {
+    return wrap(rawAdmin.removeRSGroup(servers));
+  }
+
+  @Override
+  public CompletableFuture<RSGroupInfo> getRSGroup(TableName tableName) {
+    return wrap(rawAdmin.getRSGroup(tableName));
+  }
+
+  @Override
+  public CompletableFuture<Void> setRSGroup(Set<TableName> tables, String groupName) {
+    return wrap(rawAdmin.setRSGroup(tables, groupName));
+  }
+
 }
diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/RawAsyncHBaseAdmin.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/RawAsyncHBaseAdmin.java
index 88022bb..8360c6f 100644
--- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/RawAsyncHBaseAdmin.java
+++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/RawAsyncHBaseAdmin.java
@@ -78,6 +78,7 @@ import org.apache.hadoop.hbase.client.replication.TableCFs;
 import org.apache.hadoop.hbase.client.security.SecurityCapability;
 import org.apache.hadoop.hbase.exceptions.DeserializationException;
 import org.apache.hadoop.hbase.ipc.HBaseRpcController;
+import org.apache.hadoop.hbase.net.Address;
 import org.apache.hadoop.hbase.quotas.QuotaFilter;
 import org.apache.hadoop.hbase.quotas.QuotaSettings;
 import org.apache.hadoop.hbase.quotas.QuotaTableUtil;
@@ -86,6 +87,7 @@ import org.apache.hadoop.hbase.replication.ReplicationException;
 import org.apache.hadoop.hbase.replication.ReplicationPeerConfig;
 import org.apache.hadoop.hbase.replication.ReplicationPeerDescription;
 import org.apache.hadoop.hbase.replication.SyncReplicationState;
+import org.apache.hadoop.hbase.rsgroup.RSGroupInfo;
 import org.apache.hadoop.hbase.security.access.GetUserPermissionsRequest;
 import org.apache.hadoop.hbase.security.access.Permission;
 import org.apache.hadoop.hbase.security.access.ShadedAccessControlUtil;
@@ -206,8 +208,7 @@ import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.IsProcedur
 import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.IsProcedureDoneResponse;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.IsRpcThrottleEnabledRequest;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.IsRpcThrottleEnabledResponse;
-import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos
-    .IsSnapshotCleanupEnabledResponse;
+import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.IsSnapshotCleanupEnabledResponse;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.IsSnapshotDoneRequest;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.IsSnapshotDoneResponse;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.IsSplitOrMergeEnabledRequest;
@@ -258,8 +259,7 @@ import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.SetNormali
 import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.SetNormalizerRunningResponse;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.SetQuotaRequest;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.SetQuotaResponse;
-import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos
-    .SetSnapshotCleanupResponse;
+import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.SetSnapshotCleanupResponse;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.SetSplitOrMergeEnabledRequest;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.SetSplitOrMergeEnabledResponse;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.ShutdownRequest;
@@ -285,6 +285,18 @@ import org.apache.hadoop.hbase.shaded.protobuf.generated.QuotaProtos.GetSpaceQuo
 import org.apache.hadoop.hbase.shaded.protobuf.generated.QuotaProtos.GetSpaceQuotaRegionSizesResponse.RegionSizes;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.QuotaProtos.GetSpaceQuotaSnapshotsRequest;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.QuotaProtos.GetSpaceQuotaSnapshotsResponse;
+import org.apache.hadoop.hbase.shaded.protobuf.generated.RSGroupAdminProtos.AddRSGroupRequest;
+import org.apache.hadoop.hbase.shaded.protobuf.generated.RSGroupAdminProtos.AddRSGroupResponse;
+import org.apache.hadoop.hbase.shaded.protobuf.generated.RSGroupAdminProtos.BalanceRSGroupRequest;
+import org.apache.hadoop.hbase.shaded.protobuf.generated.RSGroupAdminProtos.BalanceRSGroupResponse;
+import org.apache.hadoop.hbase.shaded.protobuf.generated.RSGroupAdminProtos.ListRSGroupInfosRequest;
+import org.apache.hadoop.hbase.shaded.protobuf.generated.RSGroupAdminProtos.ListRSGroupInfosResponse;
+import org.apache.hadoop.hbase.shaded.protobuf.generated.RSGroupAdminProtos.MoveServersRequest;
+import org.apache.hadoop.hbase.shaded.protobuf.generated.RSGroupAdminProtos.MoveServersResponse;
+import org.apache.hadoop.hbase.shaded.protobuf.generated.RSGroupAdminProtos.RemoveRSGroupRequest;
+import org.apache.hadoop.hbase.shaded.protobuf.generated.RSGroupAdminProtos.RemoveRSGroupResponse;
+import org.apache.hadoop.hbase.shaded.protobuf.generated.RSGroupAdminProtos.RemoveServersRequest;
+import org.apache.hadoop.hbase.shaded.protobuf.generated.RSGroupAdminProtos.RemoveServersResponse;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.ReplicationProtos.AddReplicationPeerRequest;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.ReplicationProtos.AddReplicationPeerResponse;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.ReplicationProtos.DisableReplicationPeerRequest;
@@ -3869,24 +3881,189 @@ class RawAsyncHBaseAdmin implements AsyncAdmin {
   @Override
   public CompletableFuture<Boolean> snapshotCleanupSwitch(final boolean on,
       final boolean sync) {
-    return this.<Boolean>newMasterCaller()
-        .action((controller, stub) -> this
-            .call(controller, stub,
-                RequestConverter.buildSetSnapshotCleanupRequest(on, sync),
-                MasterService.Interface::switchSnapshotCleanup,
-                SetSnapshotCleanupResponse::getPrevSnapshotCleanup))
-        .call();
+    return this.<Boolean>newMasterCaller().action((controller, stub) -> this
+        .call(controller, stub, RequestConverter.buildSetSnapshotCleanupRequest(on, sync),
+            MasterService.Interface::switchSnapshotCleanup,
+            SetSnapshotCleanupResponse::getPrevSnapshotCleanup)).call();
   }
 
   @Override
   public CompletableFuture<Boolean> isSnapshotCleanupEnabled() {
-    return this.<Boolean>newMasterCaller()
+    return this.<Boolean>newMasterCaller().action((controller, stub) -> this
+        .call(controller, stub, RequestConverter.buildIsSnapshotCleanupEnabledRequest(),
+            MasterService.Interface::isSnapshotCleanupEnabled,
+            IsSnapshotCleanupEnabledResponse::getEnabled)).call();
+  }
+
+  @Override
+  public CompletableFuture<Void> moveToRSGroup(Set<Address> servers, String groupName) {
+    return this.<Void> newMasterCaller()
+        .action((controller, stub) -> this.
+            <MoveServersRequest, MoveServersResponse, Void> call(controller, stub,
+                RequestConverter.buildMoveServersRequest(servers, groupName),
+              (s, c, req, done) -> s.moveServers(c, req, done), resp -> null))
+        .call();
+  }
+
+  @Override
+  public CompletableFuture<Void> addRSGroup(String groupName) {
+    return this.<Void> newMasterCaller()
+        .action(((controller, stub) -> this.
+            <AddRSGroupRequest, AddRSGroupResponse, Void> call(controller, stub,
+                AddRSGroupRequest.newBuilder().setRSGroupName(groupName).build(),
+              (s, c, req, done) -> s.addRSGroup(c, req, done), resp -> null)))
+        .call();
+  }
+
+  @Override
+  public CompletableFuture<Void> removeRSGroup(String groupName) {
+    return this.<Void> newMasterCaller()
+        .action((controller, stub) -> this.
+            <RemoveRSGroupRequest, RemoveRSGroupResponse, Void> call(controller, stub,
+                RemoveRSGroupRequest.newBuilder().setRSGroupName(groupName).build(),
+              (s, c, req, done) -> s.removeRSGroup(c, req, done), resp -> null))
+        .call();
+  }
+
+  @Override
+  public CompletableFuture<Boolean> balanceRSGroup(String groupName) {
+    return this.<Boolean> newMasterCaller()
+        .action((controller, stub) -> this.
+            <BalanceRSGroupRequest, BalanceRSGroupResponse, Boolean> call(controller, stub,
+                BalanceRSGroupRequest.newBuilder().setRSGroupName(groupName).build(),
+              (s, c, req, done) -> s.balanceRSGroup(c, req, done), resp -> resp.getBalanceRan()))
+        .call();
+  }
+
+  @Override
+  public CompletableFuture<List<RSGroupInfo>> listRSGroups() {
+    return this.<List<RSGroupInfo>> newMasterCaller()
         .action((controller, stub) -> this
-            .call(controller, stub,
-                RequestConverter.buildIsSnapshotCleanupEnabledRequest(),
-                MasterService.Interface::isSnapshotCleanupEnabled,
-                IsSnapshotCleanupEnabledResponse::getEnabled))
+            .<ListRSGroupInfosRequest, ListRSGroupInfosResponse, List<RSGroupInfo>> call(
+                controller, stub, ListRSGroupInfosRequest.getDefaultInstance(),
+              (s, c, req, done) -> s.listRSGroupInfos(c, req, done),
+              resp -> resp.getRSGroupInfoList().stream()
+                  .map(r -> ProtobufUtil.toGroupInfo(r))
+                  .collect(Collectors.toList())))
         .call();
   }
 
+  @Override
+  public CompletableFuture<RSGroupInfo> getRSGroup(Address hostPort) {
+    CompletableFuture<RSGroupInfo> future = new CompletableFuture<>();
+    addListener(listRSGroups(), (groups, err) -> {
+      if (err != null) {
+        future.completeExceptionally(err);
+        return;
+      }
+      for (RSGroupInfo rsGroupInfo : groups) {
+        if (rsGroupInfo.getServers().contains(hostPort)){
+          future.complete(rsGroupInfo);
+          return;
+        }
+      }
+      future.complete(null);
+    });
+    return future;
+  }
+
+  @Override
+  public CompletableFuture<Void> removeRSGroup(Set<Address> servers) {
+    return this.<Void> newMasterCaller()
+        .action((controller, stub) -> this.
+            <RemoveServersRequest, RemoveServersResponse, Void> call(controller, stub,
+                RequestConverter.buildRemoveServersRequest(servers),
+              (s, c, req, done) -> s.removeServers(c, req, done), resp -> null))
+        .call();
+  }
+
+  @Override
+  public CompletableFuture<Void> setRSGroup(Set<TableName> tables, String groupName) {
+    CompletableFuture<Void> future = new CompletableFuture<>();
+    for (TableName tableName : tables) {
+      addListener(tableExists(tableName), (exist, err) -> {
+        if (err != null) {
+          future.completeExceptionally(err);
+          return;
+        }
+        if (!exist) {
+          future.completeExceptionally(new TableNotFoundException(tableName));
+          return;
+        }
+      });
+    }
+    addListener(listTableDescriptors(new ArrayList<>(tables)), ((tableDescriptions, err) -> {
+      if (err != null) {
+        future.completeExceptionally(err);
+        return;
+      }
+      if (tableDescriptions == null || tableDescriptions.isEmpty()) {
+        future.complete(null);
+        return;
+      }
+      List<TableDescriptor> newTableDescriptors = new ArrayList<>();
+      for (TableDescriptor td : tableDescriptions) {
+        newTableDescriptors
+            .add(TableDescriptorBuilder.newBuilder(td).setRegionServerGroup(groupName).build());
+      }
+      addListener(CompletableFuture.allOf(
+        newTableDescriptors.stream().map(this::modifyTable).toArray(CompletableFuture[]::new)),
+        (v, e) -> {
+          if (e != null) {
+            future.completeExceptionally(e);
+          } else {
+            future.complete(v);
+          }
+        });
+    }));
+    return future;
+  }
+
+  @Override
+  public CompletableFuture<RSGroupInfo> getRSGroup(TableName table) {
+    CompletableFuture<RSGroupInfo> future = new CompletableFuture<>();
+    addListener(getDescriptor(table), (td, err) -> {
+      if (err != null) {
+        // return null instead of err to keep compatible with old semantics
+        // todo: need to change both this and UTs
+        future.complete(null);
+        return;
+      }
+      addListener(listRSGroups(), (groups, err2) -> {
+        if (err2 != null) {
+          future.completeExceptionally(err2);
+          return;
+        }
+        for (RSGroupInfo rsGroupInfo : groups) {
+          if (rsGroupInfo.getTables().contains(table)) {
+            future.complete(rsGroupInfo);
+            return;
+          }
+        }
+        future.complete(null);
+      });
+    });
+    return future;
+  }
+
+  @Override
+  public CompletableFuture<RSGroupInfo> getRSGroup(String groupName) {
+    CompletableFuture<RSGroupInfo> future = new CompletableFuture<>();
+    addListener(listRSGroups(), (groups, err) -> {
+      if (err != null) {
+        future.completeExceptionally(err);
+        return;
+      }
+      for (RSGroupInfo rsGroupInfo : groups) {
+        if (rsGroupInfo.getName().equals(groupName)){
+          future.complete(rsGroupInfo);
+          return;
+        }
+      }
+      future.complete(null);
+    });
+    return future;
+  }
+
+
 }
diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/shaded/protobuf/RequestConverter.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/shaded/protobuf/RequestConverter.java
index 075829c..287867d 100644
--- a/hbase-client/src/main/java/org/apache/hadoop/hbase/shaded/protobuf/RequestConverter.java
+++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/shaded/protobuf/RequestConverter.java
@@ -26,7 +26,6 @@ import java.util.Map;
 import java.util.Set;
 import java.util.regex.Pattern;
 import java.util.stream.Collectors;
-
 import org.apache.hadoop.hbase.CellScannable;
 import org.apache.hadoop.hbase.ClusterMetrics.Option;
 import org.apache.hadoop.hbase.ClusterMetricsBuilder;
@@ -56,6 +55,7 @@ import org.apache.hadoop.hbase.exceptions.DeserializationException;
 import org.apache.hadoop.hbase.filter.ByteArrayComparable;
 import org.apache.hadoop.hbase.io.TimeRange;
 import org.apache.hadoop.hbase.master.RegionState;
+import org.apache.hadoop.hbase.net.Address;
 import org.apache.hadoop.hbase.replication.ReplicationPeerConfig;
 import org.apache.hadoop.hbase.replication.SyncReplicationState;
 import org.apache.hadoop.hbase.util.Bytes;
@@ -64,7 +64,9 @@ import org.apache.hadoop.hbase.util.Pair;
 import org.apache.hadoop.security.token.Token;
 import org.apache.yetus.audience.InterfaceAudience;
 
+import org.apache.hbase.thirdparty.com.google.common.collect.Sets;
 import org.apache.hbase.thirdparty.com.google.protobuf.UnsafeByteOperations;
+
 import org.apache.hadoop.hbase.shaded.protobuf.generated.AdminProtos.ClearCompactionQueuesRequest;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.AdminProtos.ClearRegionBlockCacheRequest;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.AdminProtos.CompactRegionRequest;
@@ -117,8 +119,7 @@ import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.IsCatalogJ
 import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.IsCleanerChoreEnabledRequest;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.IsMasterRunningRequest;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.IsNormalizerEnabledRequest;
-import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos
-    .IsSnapshotCleanupEnabledRequest;
+import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.IsSnapshotCleanupEnabledRequest;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.IsSplitOrMergeEnabledRequest;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.MergeTableRegionsRequest;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.ModifyColumnRequest;
@@ -134,8 +135,7 @@ import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.SetBalance
 import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.SetCleanerChoreRunningRequest;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.SetNormalizerRunningRequest;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.SetRegionStateInMetaRequest;
-import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos
-    .SetSnapshotCleanupRequest;
+import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.SetSnapshotCleanupRequest;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.SetSplitOrMergeEnabledRequest;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.SetTableStateInMetaRequest;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.SplitTableRegionRequest;
@@ -144,6 +144,8 @@ import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.UnassignRe
 import org.apache.hadoop.hbase.shaded.protobuf.generated.QuotaProtos.GetQuotaStatesRequest;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.QuotaProtos.GetSpaceQuotaRegionSizesRequest;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.QuotaProtos.GetSpaceQuotaSnapshotsRequest;
+import org.apache.hadoop.hbase.shaded.protobuf.generated.RSGroupAdminProtos.MoveServersRequest;
+import org.apache.hadoop.hbase.shaded.protobuf.generated.RSGroupAdminProtos.RemoveServersRequest;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.RegionServerStatusProtos.GetLastFlushedSequenceIdRequest;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.ReplicationProtos;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.ReplicationProtos.AddReplicationPeerRequest;
@@ -1762,4 +1764,29 @@ public final class RequestConverter {
     return IsSnapshotCleanupEnabledRequest.newBuilder().build();
   }
 
+  public static MoveServersRequest buildMoveServersRequest(Set<Address> servers,
+      String targetGroup) {
+    Set<HBaseProtos.ServerName> hostPorts = Sets.newHashSet();
+    for (Address el : servers) {
+      hostPorts.add(
+          HBaseProtos.ServerName.newBuilder().setHostName(el.getHostname()).setPort(el.getPort())
+              .build());
+    }
+    return MoveServersRequest.newBuilder().setTargetGroup(targetGroup).addAllServers(hostPorts)
+            .build();
+  }
+
+  public static RemoveServersRequest buildRemoveServersRequest(Set<Address> servers) {
+    Set<HBaseProtos.ServerName> hostPorts = Sets.newHashSet();
+    for(Address el: servers) {
+      hostPorts.add(HBaseProtos.ServerName.newBuilder()
+          .setHostName(el.getHostname())
+          .setPort(el.getPort())
+          .build());
+    }
+    return RemoveServersRequest.newBuilder()
+        .addAllServers(hostPorts)
+        .build();
+  }
+
 }
diff --git a/hbase-it/src/test/java/org/apache/hadoop/hbase/rsgroup/IntegrationTestRSGroup.java b/hbase-it/src/test/java/org/apache/hadoop/hbase/rsgroup/IntegrationTestRSGroup.java
index 4f8b3ad..ef3a93a 100644
--- a/hbase-it/src/test/java/org/apache/hadoop/hbase/rsgroup/IntegrationTestRSGroup.java
+++ b/hbase-it/src/test/java/org/apache/hadoop/hbase/rsgroup/IntegrationTestRSGroup.java
@@ -82,7 +82,7 @@ public class IntegrationTestRSGroup extends TestRSGroupsBase {
         LOG.info("Waiting for cleanup to finish "+ rsGroupAdmin.listRSGroups());
         //Might be greater since moving servers back to default
         //is after starting a server
-        return rsGroupAdmin.getRSGroupInfo(RSGroupInfo.DEFAULT_GROUP).getServers().size()
+        return rsGroupAdmin.getRSGroup(RSGroupInfo.DEFAULT_GROUP).getServers().size()
             >= NUM_SLAVES_BASE;
       }
     });
@@ -93,7 +93,7 @@ public class IntegrationTestRSGroup extends TestRSGroupsBase {
         LOG.info("Waiting for regionservers to be registered "+ rsGroupAdmin.listRSGroups());
         //Might be greater since moving servers back to default
         //is after starting a server
-        return rsGroupAdmin.getRSGroupInfo(RSGroupInfo.DEFAULT_GROUP).getServers().size()
+        return rsGroupAdmin.getRSGroup(RSGroupInfo.DEFAULT_GROUP).getServers().size()
             == getNumServers();
       }
     });
diff --git a/hbase-protocol-shaded/src/main/protobuf/Master.proto b/hbase-protocol-shaded/src/main/protobuf/Master.proto
index 69377a6..0eb7a3b 100644
--- a/hbase-protocol-shaded/src/main/protobuf/Master.proto
+++ b/hbase-protocol-shaded/src/main/protobuf/Master.proto
@@ -37,6 +37,7 @@ import "Quota.proto";
 import "Replication.proto";
 import "Snapshot.proto";
 import "AccessControl.proto";
+import "RSGroupAdmin.proto";
 
 /* Column-level protobufs */
 
@@ -1085,6 +1086,24 @@ service MasterService {
   /** returns a list of namespace names */
   rpc ListNamespaces(ListNamespacesRequest)
     returns(ListNamespacesResponse);
+
+  rpc MoveServers(MoveServersRequest)
+    returns (MoveServersResponse);
+
+  rpc AddRSGroup(AddRSGroupRequest)
+    returns (AddRSGroupResponse);
+
+  rpc RemoveRSGroup(RemoveRSGroupRequest)
+    returns (RemoveRSGroupResponse);
+
+  rpc BalanceRSGroup(BalanceRSGroupRequest)
+    returns (BalanceRSGroupResponse);
+
+  rpc ListRSGroupInfos(ListRSGroupInfosRequest)
+    returns (ListRSGroupInfosResponse);
+
+  rpc RemoveServers(RemoveServersRequest)
+    returns (RemoveServersResponse);
 }
 
 // HBCK Service definitions.
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 59a1c81..bfcd903 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
@@ -3800,7 +3800,7 @@ public class HMaster extends HRegionServer implements MasterServices {
   public static void decorateMasterConfiguration(Configuration conf) {
     String plugins = conf.get(HBASE_MASTER_LOGCLEANER_PLUGINS);
     String cleanerClass = ReplicationLogCleaner.class.getCanonicalName();
-    if (!plugins.contains(cleanerClass)) {
+    if (plugins == null || !plugins.contains(cleanerClass)) {
       conf.set(HBASE_MASTER_LOGCLEANER_PLUGINS, plugins + "," + cleanerClass);
     }
     if (ReplicationUtils.isReplicationForBulkLoadDataEnabled(conf)) {
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 47ef3d0..abd297c 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
@@ -1772,7 +1772,8 @@ public class MasterCoprocessorHost
     });
   }
 
-  public void preDecommissionRegionServers(List<ServerName> servers, boolean offload) throws IOException {
+  public void preDecommissionRegionServers(List<ServerName> servers, boolean offload)
+      throws IOException {
     execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
       @Override
       public void call(MasterObserver observer) throws IOException {
@@ -1781,7 +1782,8 @@ public class MasterCoprocessorHost
     });
   }
 
-  public void postDecommissionRegionServers(List<ServerName> servers, boolean offload) throws IOException {
+  public void postDecommissionRegionServers(List<ServerName> servers, boolean offload)
+      throws IOException {
     execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
       @Override
       public void call(MasterObserver observer) throws IOException {
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/MasterRpcServices.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/MasterRpcServices.java
index 61d3726..51cba6f 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/MasterRpcServices.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/MasterRpcServices.java
@@ -94,6 +94,7 @@ import org.apache.hadoop.hbase.regionserver.RpcSchedulerFactory;
 import org.apache.hadoop.hbase.replication.ReplicationException;
 import org.apache.hadoop.hbase.replication.ReplicationPeerConfig;
 import org.apache.hadoop.hbase.replication.ReplicationPeerDescription;
+import org.apache.hadoop.hbase.rsgroup.RSGroupInfo;
 import org.apache.hadoop.hbase.security.Superusers;
 import org.apache.hadoop.hbase.security.User;
 import org.apache.hadoop.hbase.security.access.AccessChecker;
@@ -118,6 +119,7 @@ import org.apache.zookeeper.KeeperException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import org.apache.hbase.thirdparty.com.google.common.collect.Sets;
 import org.apache.hbase.thirdparty.com.google.protobuf.RpcController;
 import org.apache.hbase.thirdparty.com.google.protobuf.ServiceException;
 import org.apache.hbase.thirdparty.com.google.protobuf.UnsafeByteOperations;
@@ -305,6 +307,18 @@ import org.apache.hadoop.hbase.shaded.protobuf.generated.QuotaProtos.GetQuotaSta
 import org.apache.hadoop.hbase.shaded.protobuf.generated.QuotaProtos.GetSpaceQuotaRegionSizesRequest;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.QuotaProtos.GetSpaceQuotaRegionSizesResponse;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.QuotaProtos.GetSpaceQuotaRegionSizesResponse.RegionSizes;
+import org.apache.hadoop.hbase.shaded.protobuf.generated.RSGroupAdminProtos.AddRSGroupRequest;
+import org.apache.hadoop.hbase.shaded.protobuf.generated.RSGroupAdminProtos.AddRSGroupResponse;
+import org.apache.hadoop.hbase.shaded.protobuf.generated.RSGroupAdminProtos.BalanceRSGroupRequest;
+import org.apache.hadoop.hbase.shaded.protobuf.generated.RSGroupAdminProtos.BalanceRSGroupResponse;
+import org.apache.hadoop.hbase.shaded.protobuf.generated.RSGroupAdminProtos.ListRSGroupInfosRequest;
+import org.apache.hadoop.hbase.shaded.protobuf.generated.RSGroupAdminProtos.ListRSGroupInfosResponse;
+import org.apache.hadoop.hbase.shaded.protobuf.generated.RSGroupAdminProtos.MoveServersRequest;
+import org.apache.hadoop.hbase.shaded.protobuf.generated.RSGroupAdminProtos.MoveServersResponse;
+import org.apache.hadoop.hbase.shaded.protobuf.generated.RSGroupAdminProtos.RemoveRSGroupRequest;
+import org.apache.hadoop.hbase.shaded.protobuf.generated.RSGroupAdminProtos.RemoveRSGroupResponse;
+import org.apache.hadoop.hbase.shaded.protobuf.generated.RSGroupAdminProtos.RemoveServersRequest;
+import org.apache.hadoop.hbase.shaded.protobuf.generated.RSGroupAdminProtos.RemoveServersResponse;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.RegionServerStatusProtos.FileArchiveNotificationRequest;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.RegionServerStatusProtos.FileArchiveNotificationResponse;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.RegionServerStatusProtos.GetLastFlushedSequenceIdRequest;
@@ -348,9 +362,9 @@ import org.apache.hadoop.hbase.shaded.protobuf.generated.SnapshotProtos.Snapshot
  */
 @InterfaceAudience.Private
 @SuppressWarnings("deprecation")
-public class MasterRpcServices extends RSRpcServices
-      implements MasterService.BlockingInterface, RegionServerStatusService.BlockingInterface,
-        LockService.BlockingInterface, HbckService.BlockingInterface {
+public class MasterRpcServices extends RSRpcServices implements MasterService.BlockingInterface,
+    RegionServerStatusService.BlockingInterface,
+    LockService.BlockingInterface, HbckService.BlockingInterface {
   private static final Logger LOG = LoggerFactory.getLogger(MasterRpcServices.class.getName());
   private static final Logger AUDITLOG =
       LoggerFactory.getLogger("SecurityLogger."+MasterRpcServices.class.getName());
@@ -2899,4 +2913,153 @@ public class MasterRpcServices extends RSRpcServices
     return true;
   }
 
+  @Override
+  public MoveServersResponse moveServers(RpcController controller, MoveServersRequest request)
+      throws ServiceException {
+    Set<Address> hostPorts = Sets.newHashSet();
+    MoveServersResponse.Builder builder = MoveServersResponse.newBuilder();
+    for (HBaseProtos.ServerName el : request.getServersList()) {
+      hostPorts.add(Address.fromParts(el.getHostName(), el.getPort()));
+    }
+    LOG.info(master.getClientIdAuditPrefix() + " move servers " + hostPorts + " to rsgroup " +
+        request.getTargetGroup());
+    try {
+      if (master.getMasterCoprocessorHost() != null) {
+        master.getMasterCoprocessorHost().preMoveServers(hostPorts, request.getTargetGroup());
+      }
+      master.getRSGroupInfoManager().moveServers(hostPorts, request.getTargetGroup());
+      if (master.getMasterCoprocessorHost() != null) {
+        master.getMasterCoprocessorHost().postMoveServers(hostPorts, request.getTargetGroup());
+      }
+    } catch (IOException e) {
+      throw new ServiceException(e);
+    }
+    return builder.build();
+  }
+
+  @Override
+  public AddRSGroupResponse addRSGroup(RpcController controller, AddRSGroupRequest request)
+      throws ServiceException {
+    AddRSGroupResponse.Builder builder = AddRSGroupResponse.newBuilder();
+    LOG.info(master.getClientIdAuditPrefix() + " add rsgroup " + request.getRSGroupName());
+    try {
+      if (master.getMasterCoprocessorHost() != null) {
+        master.getMasterCoprocessorHost().preAddRSGroup(request.getRSGroupName());
+      }
+      master.getRSGroupInfoManager().addRSGroup(new RSGroupInfo(request.getRSGroupName()));
+      if (master.getMasterCoprocessorHost() != null) {
+        master.getMasterCoprocessorHost().postAddRSGroup(request.getRSGroupName());
+      }
+    } catch (IOException e) {
+      throw new ServiceException(e);
+    }
+    return builder.build();
+  }
+
+  @Override
+  public RemoveRSGroupResponse removeRSGroup(RpcController controller, RemoveRSGroupRequest request)
+      throws ServiceException {
+    RemoveRSGroupResponse.Builder builder = RemoveRSGroupResponse.newBuilder();
+    LOG.info(master.getClientIdAuditPrefix() + " remove rsgroup " + request.getRSGroupName());
+    try {
+      if (master.getMasterCoprocessorHost() != null) {
+        master.getMasterCoprocessorHost().preRemoveRSGroup(request.getRSGroupName());
+      }
+      master.getRSGroupInfoManager().removeRSGroup(request.getRSGroupName());
+      if (master.getMasterCoprocessorHost() != null) {
+        master.getMasterCoprocessorHost().postRemoveRSGroup(request.getRSGroupName());
+      }
+    } catch (IOException e) {
+      throw new ServiceException(e);
+    }
+    return builder.build();
+  }
+
+  @Override
+  public BalanceRSGroupResponse balanceRSGroup(RpcController controller,
+      BalanceRSGroupRequest request) throws ServiceException {
+    BalanceRSGroupResponse.Builder builder = BalanceRSGroupResponse.newBuilder();
+    LOG.info(
+        master.getClientIdAuditPrefix() + " balance rsgroup, group=" + request.getRSGroupName());
+    try {
+      if (master.getMasterCoprocessorHost() != null) {
+        master.getMasterCoprocessorHost().preBalanceRSGroup(request.getRSGroupName());
+      }
+      boolean balancerRan =
+          master.getRSGroupInfoManager().balanceRSGroup(request.getRSGroupName());
+      builder.setBalanceRan(balancerRan);
+      if (master.getMasterCoprocessorHost() != null) {
+        master.getMasterCoprocessorHost().postBalanceRSGroup(request.getRSGroupName(), balancerRan);
+      }
+    } catch (IOException e) {
+      throw new ServiceException(e);
+    }
+    return builder.build();
+  }
+
+  @Override
+  public ListRSGroupInfosResponse listRSGroupInfos(RpcController controller,
+      ListRSGroupInfosRequest request) throws ServiceException {
+    ListRSGroupInfosResponse.Builder builder = ListRSGroupInfosResponse.newBuilder();
+    LOG.info(master.getClientIdAuditPrefix() + " list rsgroup");
+    try {
+      if (master.getMasterCoprocessorHost() != null) {
+        master.getMasterCoprocessorHost().preListRSGroups();
+      }
+      List<RSGroupInfo> rsGroupInfos = master.getRSGroupInfoManager().listRSGroups().stream()
+          .map(RSGroupInfo::new).collect(Collectors.toList());
+      Map<String, RSGroupInfo> name2Info = new HashMap<>();
+      List<TableDescriptor> needToFill =
+          new ArrayList<>(master.getTableDescriptors().getAll().values());
+      for (RSGroupInfo rsGroupInfo : rsGroupInfos) {
+        name2Info.put(rsGroupInfo.getName(), rsGroupInfo);
+        for (TableDescriptor td : master.getTableDescriptors().getAll().values()) {
+          if (rsGroupInfo.containsTable(td.getTableName())){
+            needToFill.remove(td);
+          }
+        }
+      }
+      for (TableDescriptor td : needToFill) {
+        String groupName = td.getRegionServerGroup().orElse(RSGroupInfo.DEFAULT_GROUP);
+        RSGroupInfo rsGroupInfo = name2Info.get(groupName);
+        if (rsGroupInfo != null) {
+          rsGroupInfo.addTable(td.getTableName());
+        }
+      }
+      for (RSGroupInfo rsGroupInfo : rsGroupInfos) {
+        // TODO: this can be done at once outside this loop, do not need to scan all every time.
+        builder.addRSGroupInfo(ProtobufUtil.toProtoGroupInfo(rsGroupInfo));
+      }
+      if (master.getMasterCoprocessorHost() != null) {
+        master.getMasterCoprocessorHost().postListRSGroups();
+      }
+    } catch (IOException e) {
+      throw new ServiceException(e);
+    }
+    return builder.build();
+  }
+
+  @Override
+  public RemoveServersResponse removeServers(RpcController controller,
+      RemoveServersRequest request) throws ServiceException {
+    RemoveServersResponse.Builder builder = RemoveServersResponse.newBuilder();
+    Set<Address> servers = Sets.newHashSet();
+    for (HBaseProtos.ServerName el : request.getServersList()) {
+      servers.add(Address.fromParts(el.getHostName(), el.getPort()));
+    }
+    LOG.info(master.getClientIdAuditPrefix() + " remove decommissioned servers from rsgroup: " +
+        servers);
+    try {
+      if (master.getMasterCoprocessorHost() != null) {
+        master.getMasterCoprocessorHost().preRemoveServers(servers);
+      }
+      master.getRSGroupInfoManager().removeServers(servers);
+      if (master.getMasterCoprocessorHost() != null) {
+        master.getMasterCoprocessorHost().postRemoveServers(servers);
+      }
+    } catch (IOException e) {
+      throw new ServiceException(e);
+    }
+    return builder.build();
+  }
 }
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/MasterServices.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/MasterServices.java
index 16a3e4c..1b8c67a 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/MasterServices.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/MasterServices.java
@@ -52,6 +52,7 @@ import org.apache.hadoop.hbase.replication.SyncReplicationState;
 import org.apache.hadoop.hbase.rsgroup.RSGroupInfoManager;
 import org.apache.hadoop.hbase.security.access.AccessChecker;
 import org.apache.hadoop.hbase.security.access.ZKPermissionWatcher;
+import org.apache.hadoop.hbase.zookeeper.LoadBalancerTracker;
 import org.apache.yetus.audience.InterfaceAudience;
 
 import org.apache.hbase.thirdparty.com.google.common.annotations.VisibleForTesting;
@@ -545,4 +546,12 @@ public interface MasterServices extends Server {
    * @return the {@link RSGroupInfoManager}
    */
   RSGroupInfoManager getRSGroupInfoManager();
+
+  /**
+   * Queries the state of the {@link LoadBalancerTracker}. If the balancer is not initialized,
+   * false is returned.
+   *
+   * @return The state of the load balancer, or false if the load balancer isn't defined.
+   */
+  boolean isBalancerOn();
 }
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/rsgroup/DisabledRSGroupInfoManager.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/rsgroup/DisabledRSGroupInfoManager.java
index 0762750..e9bd6f8 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/rsgroup/DisabledRSGroupInfoManager.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/rsgroup/DisabledRSGroupInfoManager.java
@@ -57,8 +57,7 @@ class DisabledRSGroupInfoManager implements RSGroupInfoManager {
   }
 
   @Override
-  public Set<Address> moveServers(Set<Address> servers, String srcGroup, String dstGroup)
-    throws IOException {
+  public void moveServers(Set<Address> servers, String targetGroupName) throws IOException {
     throw new DoNotRetryIOException("RSGroup is disabled");
   }
 
@@ -106,4 +105,14 @@ class DisabledRSGroupInfoManager implements RSGroupInfoManager {
   public RSGroupInfo getRSGroupForTable(TableName tableName) throws IOException {
     return null;
   }
+
+  @Override
+  public boolean balanceRSGroup(String groupName) throws IOException {
+    throw new DoNotRetryIOException("RSGroup is disabled");
+  }
+
+  @Override
+  public void setRSGroup(Set<TableName> tables, String groupName) throws IOException {
+    throw new DoNotRetryIOException("RSGroup is disabled");
+  }
 }
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupAdmin.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupAdmin.java
index 344d0b3..3de6965 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupAdmin.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupAdmin.java
@@ -20,12 +20,16 @@ package org.apache.hadoop.hbase.rsgroup;
 import java.io.IOException;
 import java.util.List;
 import java.util.Set;
+import org.apache.hadoop.hbase.client.Admin;
 import org.apache.hadoop.hbase.net.Address;
 import org.apache.yetus.audience.InterfaceAudience;
 
 /**
  * Group user API interface used between client and server.
+ *
+ * @deprecated Keep it here only for tests, using {@link Admin} instead.
  */
+@Deprecated
 @InterfaceAudience.Private
 public interface RSGroupAdmin {
   /**
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupAdminClient.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupAdminClient.java
index 07f0efd..7cf4ed1 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupAdminClient.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupAdminClient.java
@@ -20,12 +20,33 @@ package org.apache.hadoop.hbase.rsgroup;
 import com.google.protobuf.ServiceException;
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.EnumSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.Future;
+import java.util.regex.Pattern;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.hbase.CacheEvictionStats;
+import org.apache.hadoop.hbase.ClusterMetrics;
+import org.apache.hadoop.hbase.NamespaceDescriptor;
+import org.apache.hadoop.hbase.NamespaceNotFoundException;
+import org.apache.hadoop.hbase.RegionMetrics;
+import org.apache.hadoop.hbase.ServerName;
+import org.apache.hadoop.hbase.TableExistsException;
 import org.apache.hadoop.hbase.TableName;
 import org.apache.hadoop.hbase.TableNotFoundException;
 import org.apache.hadoop.hbase.client.Admin;
+import org.apache.hadoop.hbase.client.ColumnFamilyDescriptor;
+import org.apache.hadoop.hbase.client.CompactType;
+import org.apache.hadoop.hbase.client.CompactionState;
 import org.apache.hadoop.hbase.client.Connection;
+import org.apache.hadoop.hbase.client.RegionInfo;
+import org.apache.hadoop.hbase.client.SnapshotDescription;
+import org.apache.hadoop.hbase.client.TableDescriptor;
+import org.apache.hadoop.hbase.client.replication.TableCFs;
+import org.apache.hadoop.hbase.client.security.SecurityCapability;
+import org.apache.hadoop.hbase.ipc.CoprocessorRpcChannel;
 import org.apache.hadoop.hbase.net.Address;
 import org.apache.hadoop.hbase.protobuf.ProtobufUtil;
 import org.apache.hadoop.hbase.protobuf.generated.HBaseProtos;
@@ -38,13 +59,26 @@ import org.apache.hadoop.hbase.protobuf.generated.RSGroupAdminProtos.GetRSGroupI
 import org.apache.hadoop.hbase.protobuf.generated.RSGroupAdminProtos.GetRSGroupInfoRequest;
 import org.apache.hadoop.hbase.protobuf.generated.RSGroupAdminProtos.GetRSGroupInfoResponse;
 import org.apache.hadoop.hbase.protobuf.generated.RSGroupAdminProtos.ListRSGroupInfosRequest;
-import org.apache.hadoop.hbase.protobuf.generated.RSGroupAdminProtos.MoveServersAndTablesRequest;
 import org.apache.hadoop.hbase.protobuf.generated.RSGroupAdminProtos.MoveServersRequest;
 import org.apache.hadoop.hbase.protobuf.generated.RSGroupAdminProtos.MoveTablesRequest;
 import org.apache.hadoop.hbase.protobuf.generated.RSGroupAdminProtos.RSGroupAdminService;
 import org.apache.hadoop.hbase.protobuf.generated.RSGroupAdminProtos.RemoveRSGroupRequest;
 import org.apache.hadoop.hbase.protobuf.generated.RSGroupAdminProtos.RemoveServersRequest;
 import org.apache.hadoop.hbase.protobuf.generated.RSGroupProtos;
+import org.apache.hadoop.hbase.quotas.QuotaFilter;
+import org.apache.hadoop.hbase.quotas.QuotaSettings;
+import org.apache.hadoop.hbase.quotas.SpaceQuotaSnapshotView;
+import org.apache.hadoop.hbase.regionserver.wal.FailedLogCloseException;
+import org.apache.hadoop.hbase.replication.ReplicationPeerConfig;
+import org.apache.hadoop.hbase.replication.ReplicationPeerDescription;
+import org.apache.hadoop.hbase.replication.SyncReplicationState;
+import org.apache.hadoop.hbase.security.access.GetUserPermissionsRequest;
+import org.apache.hadoop.hbase.security.access.Permission;
+import org.apache.hadoop.hbase.security.access.UserPermission;
+import org.apache.hadoop.hbase.snapshot.HBaseSnapshotException;
+import org.apache.hadoop.hbase.snapshot.RestoreSnapshotException;
+import org.apache.hadoop.hbase.snapshot.SnapshotCreationException;
+import org.apache.hadoop.hbase.snapshot.UnknownSnapshotException;
 import org.apache.yetus.audience.InterfaceAudience;
 
 import org.apache.hbase.thirdparty.com.google.common.annotations.VisibleForTesting;
@@ -52,27 +86,866 @@ import org.apache.hbase.thirdparty.com.google.common.collect.Sets;
 
 /**
  * Client used for managing region server group information.
+ *
+ * @deprecated Keep it here only for tests, using {@link Admin} instead.
  */
+@Deprecated
 @InterfaceAudience.Private
-public class RSGroupAdminClient implements RSGroupAdmin {
+public class RSGroupAdminClient implements RSGroupAdmin, Admin {
   private RSGroupAdminService.BlockingInterface stub;
   private Admin admin;
 
-  public RSGroupAdminClient(Connection conn) throws IOException {
-    admin = conn.getAdmin();
-    stub = RSGroupAdminService.newBlockingStub(admin.coprocessorService());
+  public RSGroupAdminClient(Connection conn) throws IOException {
+    admin = conn.getAdmin();
+    stub = RSGroupAdminService.newBlockingStub(admin.coprocessorService());
+  }
+
+  // for writing UTs
+  @VisibleForTesting
+  protected RSGroupAdminClient() {
+  }
+
+  @Override
+  public int getOperationTimeout() {
+    return 0;
+  }
+
+  @Override
+  public int getSyncWaitTimeout() {
+    return 0;
+  }
+
+  @Override
+  public void abort(String why, Throwable e) {
+
+  }
+
+  @Override
+  public boolean isAborted() {
+    return false;
+  }
+
+  @Override
+  public Connection getConnection() {
+    return null;
+  }
+
+  @Override
+  public boolean tableExists(TableName tableName) throws IOException {
+    return false;
+  }
+
+  @Override
+  public List<TableDescriptor> listTableDescriptors() throws IOException {
+    return null;
+  }
+
+  @Override
+  public List<TableDescriptor> listTableDescriptors(boolean includeSysTables) throws IOException {
+    return null;
+  }
+
+  @Override
+  public List<TableDescriptor> listTableDescriptors(Pattern pattern) throws IOException {
+    return null;
+  }
+
+  @Override
+  public List<TableDescriptor> listTableDescriptors(Pattern pattern, boolean includeSysTables)
+      throws IOException {
+    return null;
+  }
+
+  @Override
+  public TableName[] listTableNames() throws IOException {
+    return new TableName[0];
+  }
+
+  @Override
+  public TableName[] listTableNames(Pattern pattern, boolean includeSysTables) throws IOException {
+    return new TableName[0];
+  }
+
+  @Override
+  public TableDescriptor getDescriptor(TableName tableName)
+      throws TableNotFoundException, IOException {
+    return null;
+  }
+
+  @Override
+  public void createTable(TableDescriptor desc, byte[] startKey, byte[] endKey, int numRegions)
+      throws IOException {
+
+  }
+
+  @Override
+  public Future<Void> createTableAsync(TableDescriptor desc) throws IOException {
+    return null;
+  }
+
+  @Override
+  public Future<Void> createTableAsync(TableDescriptor desc, byte[][] splitKeys)
+      throws IOException {
+    return null;
+  }
+
+  @Override
+  public Future<Void> deleteTableAsync(TableName tableName) throws IOException {
+    return null;
+  }
+
+  @Override
+  public Future<Void> truncateTableAsync(TableName tableName, boolean preserveSplits)
+      throws IOException {
+    return null;
+  }
+
+  @Override
+  public Future<Void> enableTableAsync(TableName tableName) throws IOException {
+    return null;
+  }
+
+  @Override
+  public Future<Void> disableTableAsync(TableName tableName) throws IOException {
+    return null;
+  }
+
+  @Override
+  public boolean isTableEnabled(TableName tableName) throws IOException {
+    return false;
+  }
+
+  @Override
+  public boolean isTableDisabled(TableName tableName) throws IOException {
+    return false;
+  }
+
+  @Override
+  public boolean isTableAvailable(TableName tableName) throws IOException {
+    return false;
+  }
+
+  @Override
+  public Future<Void> addColumnFamilyAsync(TableName tableName, ColumnFamilyDescriptor columnFamily)
+      throws IOException {
+    return null;
+  }
+
+  @Override
+  public Future<Void> deleteColumnFamilyAsync(TableName tableName, byte[] columnFamily)
+      throws IOException {
+    return null;
+  }
+
+  @Override
+  public Future<Void> modifyColumnFamilyAsync(TableName tableName,
+      ColumnFamilyDescriptor columnFamily) throws IOException {
+    return null;
+  }
+
+  @Override
+  public List<RegionInfo> getRegions(ServerName serverName) throws IOException {
+    return null;
+  }
+
+  @Override
+  public void flush(TableName tableName) throws IOException {
+
+  }
+
+  @Override
+  public void flushRegion(byte[] regionName) throws IOException {
+
+  }
+
+  @Override
+  public void flushRegionServer(ServerName serverName) throws IOException {
+
+  }
+
+  @Override
+  public void compact(TableName tableName) throws IOException {
+
+  }
+
+  @Override
+  public void compactRegion(byte[] regionName) throws IOException {
+
+  }
+
+  @Override
+  public void compact(TableName tableName, byte[] columnFamily) throws IOException {
+
+  }
+
+  @Override
+  public void compactRegion(byte[] regionName, byte[] columnFamily) throws IOException {
+
+  }
+
+  @Override
+  public void compact(TableName tableName, CompactType compactType)
+      throws IOException, InterruptedException {
+
+  }
+
+  @Override
+  public void compact(TableName tableName, byte[] columnFamily, CompactType compactType)
+      throws IOException, InterruptedException {
+
+  }
+
+  @Override
+  public void majorCompact(TableName tableName) throws IOException {
+
+  }
+
+  @Override
+  public void majorCompactRegion(byte[] regionName) throws IOException {
+
+  }
+
+  @Override
+  public void majorCompact(TableName tableName, byte[] columnFamily) throws IOException {
+
+  }
+
+  @Override
+  public void majorCompactRegion(byte[] regionName, byte[] columnFamily) throws IOException {
+
+  }
+
+  @Override
+  public void majorCompact(TableName tableName, CompactType compactType)
+      throws IOException, InterruptedException {
+
+  }
+
+  @Override
+  public void majorCompact(TableName tableName, byte[] columnFamily, CompactType compactType)
+      throws IOException, InterruptedException {
+
+  }
+
+  @Override
+  public Map<ServerName, Boolean> compactionSwitch(boolean switchState,
+      List<String> serverNamesList) throws IOException {
+    return null;
+  }
+
+  @Override
+  public void compactRegionServer(ServerName serverName) throws IOException {
+
+  }
+
+  @Override
+  public void majorCompactRegionServer(ServerName serverName) throws IOException {
+
+  }
+
+  @Override
+  public void move(byte[] encodedRegionName) throws IOException {
+
+  }
+
+  @Override
+  public void move(byte[] encodedRegionName, ServerName destServerName) throws IOException {
+
+  }
+
+  @Override
+  public void assign(byte[] regionName) throws IOException {
+
+  }
+
+  @Override
+  public void unassign(byte[] regionName, boolean force) throws IOException {
+
+  }
+
+  @Override
+  public void offline(byte[] regionName) throws IOException {
+
+  }
+
+  @Override
+  public boolean balancerSwitch(boolean onOrOff, boolean synchronous) throws IOException {
+    return false;
+  }
+
+  @Override
+  public boolean balance() throws IOException {
+    return false;
+  }
+
+  @Override
+  public boolean balance(boolean force) throws IOException {
+    return false;
+  }
+
+  @Override
+  public boolean isBalancerEnabled() throws IOException {
+    return false;
+  }
+
+  @Override
+  public CacheEvictionStats clearBlockCache(TableName tableName) throws IOException {
+    return null;
+  }
+
+  @Override
+  public boolean normalize() throws IOException {
+    return false;
+  }
+
+  @Override
+  public boolean isNormalizerEnabled() throws IOException {
+    return false;
+  }
+
+  @Override
+  public boolean normalizerSwitch(boolean on) throws IOException {
+    return false;
+  }
+
+  @Override
+  public boolean catalogJanitorSwitch(boolean onOrOff) throws IOException {
+    return false;
+  }
+
+  @Override
+  public int runCatalogJanitor() throws IOException {
+    return 0;
+  }
+
+  @Override
+  public boolean isCatalogJanitorEnabled() throws IOException {
+    return false;
+  }
+
+  @Override
+  public boolean cleanerChoreSwitch(boolean onOrOff) throws IOException {
+    return false;
+  }
+
+  @Override
+  public boolean runCleanerChore() throws IOException {
+    return false;
+  }
+
+  @Override
+  public boolean isCleanerChoreEnabled() throws IOException {
+    return false;
+  }
+
+  @Override
+  public Future<Void> mergeRegionsAsync(byte[][] nameofRegionsToMerge, boolean forcible)
+      throws IOException {
+    return null;
+  }
+
+  @Override
+  public void split(TableName tableName) throws IOException {
+
+  }
+
+  @Override
+  public void split(TableName tableName, byte[] splitPoint) throws IOException {
+
+  }
+
+  @Override
+  public Future<Void> splitRegionAsync(byte[] regionName) throws IOException {
+    return null;
+  }
+
+  @Override
+  public Future<Void> splitRegionAsync(byte[] regionName, byte[] splitPoint) throws IOException {
+    return null;
+  }
+
+  @Override
+  public Future<Void> modifyTableAsync(TableDescriptor td) throws IOException {
+    return null;
+  }
+
+  @Override
+  public void shutdown() throws IOException {
+
+  }
+
+  @Override
+  public void stopMaster() throws IOException {
+
+  }
+
+  @Override
+  public boolean isMasterInMaintenanceMode() throws IOException {
+    return false;
+  }
+
+  @Override
+  public void stopRegionServer(String hostnamePort) throws IOException {
+
+  }
+
+  @Override
+  public ClusterMetrics getClusterMetrics(EnumSet<ClusterMetrics.Option> options)
+      throws IOException {
+    return null;
+  }
+
+  @Override
+  public List<RegionMetrics> getRegionMetrics(ServerName serverName) throws IOException {
+    return null;
+  }
+
+  @Override
+  public List<RegionMetrics> getRegionMetrics(ServerName serverName, TableName tableName)
+      throws IOException {
+    return null;
+  }
+
+  @Override
+  public Configuration getConfiguration() {
+    return null;
+  }
+
+  @Override
+  public Future<Void> createNamespaceAsync(NamespaceDescriptor descriptor) throws IOException {
+    return null;
+  }
+
+  @Override
+  public Future<Void> modifyNamespaceAsync(NamespaceDescriptor descriptor) throws IOException {
+    return null;
+  }
+
+  @Override
+  public Future<Void> deleteNamespaceAsync(String name) throws IOException {
+    return null;
+  }
+
+  @Override
+  public NamespaceDescriptor getNamespaceDescriptor(String name)
+      throws NamespaceNotFoundException, IOException {
+    return null;
+  }
+
+  @Override
+  public String[] listNamespaces() throws IOException {
+    return new String[0];
+  }
+
+  @Override
+  public NamespaceDescriptor[] listNamespaceDescriptors() throws IOException {
+    return new NamespaceDescriptor[0];
+  }
+
+  @Override
+  public List<TableDescriptor> listTableDescriptorsByNamespace(byte[] name) throws IOException {
+    return null;
+  }
+
+  @Override
+  public TableName[] listTableNamesByNamespace(String name) throws IOException {
+    return new TableName[0];
+  }
+
+  @Override
+  public List<RegionInfo> getRegions(TableName tableName) throws IOException {
+    return null;
+  }
+
+  @Override
+  public void close() {
+
+  }
+
+  @Override
+  public List<TableDescriptor> listTableDescriptors(List<TableName> tableNames) throws IOException {
+    return null;
+  }
+
+  @Override
+  public Future<Boolean> abortProcedureAsync(long procId, boolean mayInterruptIfRunning)
+      throws IOException {
+    return null;
+  }
+
+  @Override
+  public String getProcedures() throws IOException {
+    return null;
+  }
+
+  @Override
+  public String getLocks() throws IOException {
+    return null;
+  }
+
+  @Override
+  public void rollWALWriter(ServerName serverName) throws IOException, FailedLogCloseException {
+
+  }
+
+  @Override
+  public CompactionState getCompactionState(TableName tableName) throws IOException {
+    return null;
+  }
+
+  @Override
+  public CompactionState getCompactionState(TableName tableName, CompactType compactType)
+      throws IOException {
+    return null;
+  }
+
+  @Override
+  public CompactionState getCompactionStateForRegion(byte[] regionName) throws IOException {
+    return null;
+  }
+
+  @Override
+  public long getLastMajorCompactionTimestamp(TableName tableName) throws IOException {
+    return 0;
+  }
+
+  @Override
+  public long getLastMajorCompactionTimestampForRegion(byte[] regionName) throws IOException {
+    return 0;
+  }
+
+  @Override
+  public void snapshot(SnapshotDescription snapshot)
+      throws IOException, SnapshotCreationException, IllegalArgumentException {
+
+  }
+
+  @Override
+  public Future<Void> snapshotAsync(SnapshotDescription snapshot)
+      throws IOException, SnapshotCreationException {
+    return null;
+  }
+
+  @Override
+  public boolean isSnapshotFinished(SnapshotDescription snapshot)
+      throws IOException, HBaseSnapshotException, UnknownSnapshotException {
+    return false;
+  }
+
+  @Override
+  public void restoreSnapshot(String snapshotName) throws IOException, RestoreSnapshotException {
+
+  }
+
+  @Override
+  public void restoreSnapshot(String snapshotName, boolean takeFailSafeSnapshot, boolean restoreAcl)
+      throws IOException, RestoreSnapshotException {
+
+  }
+
+  @Override
+  public Future<Void> cloneSnapshotAsync(String snapshotName, TableName tableName,
+      boolean restoreAcl) throws IOException, TableExistsException, RestoreSnapshotException {
+    return null;
+  }
+
+  @Override
+  public void execProcedure(String signature, String instance, Map<String, String> props)
+      throws IOException {
+
+  }
+
+  @Override
+  public byte[] execProcedureWithReturn(String signature, String instance,
+      Map<String, String> props) throws IOException {
+    return new byte[0];
+  }
+
+  @Override
+  public boolean isProcedureFinished(String signature, String instance, Map<String, String> props)
+      throws IOException {
+    return false;
+  }
+
+  @Override
+  public List<SnapshotDescription> listSnapshots() throws IOException {
+    return null;
+  }
+
+  @Override
+  public List<SnapshotDescription> listSnapshots(Pattern pattern) throws IOException {
+    return null;
+  }
+
+  @Override
+  public List<SnapshotDescription> listTableSnapshots(Pattern tableNamePattern,
+      Pattern snapshotNamePattern) throws IOException {
+    return null;
+  }
+
+  @Override
+  public void deleteSnapshot(String snapshotName) throws IOException {
+
   }
 
-  // for writing UTs
-  @VisibleForTesting
-  protected RSGroupAdminClient() {
+  @Override
+  public void deleteSnapshots(Pattern pattern) throws IOException {
+
+  }
+
+  @Override
+  public void deleteTableSnapshots(Pattern tableNamePattern, Pattern snapshotNamePattern)
+      throws IOException {
+
+  }
+
+  @Override
+  public void setQuota(QuotaSettings quota) throws IOException {
+
+  }
+
+  @Override
+  public List<QuotaSettings> getQuota(QuotaFilter filter) throws IOException {
+    return null;
+  }
+
+  @Override
+  public CoprocessorRpcChannel coprocessorService() {
+    return null;
+  }
+
+  @Override
+  public CoprocessorRpcChannel coprocessorService(ServerName serverName) {
+    return null;
+  }
+
+  @Override
+  public void updateConfiguration(ServerName server) throws IOException {
+
+  }
+
+  @Override
+  public void updateConfiguration() throws IOException {
+
+  }
+
+  @Override
+  public List<SecurityCapability> getSecurityCapabilities() throws IOException {
+    return null;
+  }
+
+  @Override
+  public boolean splitSwitch(boolean enabled, boolean synchronous) throws IOException {
+    return false;
+  }
+
+  @Override
+  public boolean mergeSwitch(boolean enabled, boolean synchronous) throws IOException {
+    return false;
+  }
+
+  @Override
+  public boolean isSplitEnabled() throws IOException {
+    return false;
+  }
+
+  @Override
+  public boolean isMergeEnabled() throws IOException {
+    return false;
+  }
+
+  @Override
+  public Future<Void> addReplicationPeerAsync(String peerId, ReplicationPeerConfig peerConfig,
+      boolean enabled) throws IOException {
+    return null;
+  }
+
+  @Override
+  public Future<Void> removeReplicationPeerAsync(String peerId) throws IOException {
+    return null;
+  }
+
+  @Override
+  public Future<Void> enableReplicationPeerAsync(String peerId) throws IOException {
+    return null;
+  }
+
+  @Override
+  public Future<Void> disableReplicationPeerAsync(String peerId) throws IOException {
+    return null;
+  }
+
+  @Override
+  public ReplicationPeerConfig getReplicationPeerConfig(String peerId) throws IOException {
+    return null;
+  }
+
+  @Override
+  public Future<Void> updateReplicationPeerConfigAsync(String peerId,
+      ReplicationPeerConfig peerConfig) throws IOException {
+    return null;
+  }
+
+  @Override
+  public List<ReplicationPeerDescription> listReplicationPeers() throws IOException {
+    return null;
+  }
+
+  @Override
+  public List<ReplicationPeerDescription> listReplicationPeers(Pattern pattern) throws IOException {
+    return null;
+  }
+
+  @Override
+  public Future<Void> transitReplicationPeerSyncReplicationStateAsync(String peerId,
+      SyncReplicationState state) throws IOException {
+    return null;
+  }
+
+  @Override
+  public void decommissionRegionServers(List<ServerName> servers, boolean offload)
+      throws IOException {
+
+  }
+
+  @Override
+  public List<ServerName> listDecommissionedRegionServers() throws IOException {
+    return null;
+  }
+
+  @Override
+  public void recommissionRegionServer(ServerName server, List<byte[]> encodedRegionNames)
+      throws IOException {
+
+  }
+
+  @Override
+  public List<TableCFs> listReplicatedTableCFs() throws IOException {
+    return null;
+  }
+
+  @Override
+  public void enableTableReplication(TableName tableName) throws IOException {
+
+  }
+
+  @Override
+  public void disableTableReplication(TableName tableName) throws IOException {
+
+  }
+
+  @Override
+  public void clearCompactionQueues(ServerName serverName, Set<String> queues)
+      throws IOException, InterruptedException {
+
+  }
+
+  @Override
+  public List<ServerName> clearDeadServers(List<ServerName> servers) throws IOException {
+    return null;
+  }
+
+  @Override
+  public void cloneTableSchema(TableName tableName, TableName newTableName, boolean preserveSplits)
+      throws IOException {
+
+  }
+
+  @Override
+  public boolean switchRpcThrottle(boolean enable) throws IOException {
+    return false;
+  }
+
+  @Override
+  public boolean isRpcThrottleEnabled() throws IOException {
+    return false;
+  }
+
+  @Override
+  public boolean exceedThrottleQuotaSwitch(boolean enable) throws IOException {
+    return false;
+  }
+
+  @Override
+  public Map<TableName, Long> getSpaceQuotaTableSizes() throws IOException {
+    return null;
+  }
+
+  @Override
+  public Map<TableName, ? extends SpaceQuotaSnapshotView> getRegionServerSpaceQuotaSnapshots(
+      ServerName serverName) throws IOException {
+    return null;
+  }
+
+  @Override
+  public SpaceQuotaSnapshotView getCurrentSpaceQuotaSnapshot(String namespace) throws IOException {
+    return null;
+  }
+
+  @Override
+  public SpaceQuotaSnapshotView getCurrentSpaceQuotaSnapshot(TableName tableName)
+      throws IOException {
+    return null;
+  }
+
+  @Override
+  public void grant(UserPermission userPermission, boolean mergeExistingPermissions)
+      throws IOException {
+
+  }
+
+  @Override
+  public void revoke(UserPermission userPermission) throws IOException {
+
+  }
+
+  @Override
+  public List<UserPermission> getUserPermissions(
+      GetUserPermissionsRequest getUserPermissionsRequest) throws IOException {
+    return null;
+  }
+
+  @Override
+  public List<Boolean> hasUserPermissions(String userName, List<Permission> permissions)
+      throws IOException {
+    return null;
+  }
+
+  @Override
+  public boolean snapshotCleanupSwitch(boolean on, boolean synchronous) throws IOException {
+    return false;
+  }
+
+  @Override
+  public boolean isSnapshotCleanupEnabled() throws IOException {
+    return false;
   }
 
   @Override
   public RSGroupInfo getRSGroupInfo(String groupName) throws IOException {
+    return getRSGroup(groupName);
+  }
+
+  @Override
+  public void moveServers(Set<Address> servers, String targetGroup) throws IOException {
+    moveToRSGroup(servers, targetGroup);
+  }
+
+  @Override
+  public void addRSGroup(String groupName) throws IOException {
+    AddRSGroupRequest request = AddRSGroupRequest.newBuilder().setRSGroupName(groupName).build();
+    try {
+      stub.addRSGroup(null, request);
+    } catch (ServiceException e) {
+      throw ProtobufUtil.handleRemoteException(e);
+    }
+  }
+
+  @Override
+  public RSGroupInfo getRSGroup(String groupName) throws IOException {
     try {
       GetRSGroupInfoResponse resp = stub.getRSGroupInfo(null,
-        GetRSGroupInfoRequest.newBuilder().setRSGroupName(groupName).build());
+          GetRSGroupInfoRequest.newBuilder().setRSGroupName(groupName).build());
       if (resp.hasRSGroupInfo()) {
         return ProtobufUtil.toGroupInfo(resp.getRSGroupInfo());
       }
@@ -82,11 +955,13 @@ public class RSGroupAdminClient implements RSGroupAdmin {
     }
   }
 
-  public RSGroupInfo getRSGroupInfoOfTable(TableName tableName) throws IOException {
-    GetRSGroupInfoOfTableRequest request = GetRSGroupInfoOfTableRequest.newBuilder().setTableName(
-        ProtobufUtil.toProtoTableName(tableName)).build();
+  @Override
+  public RSGroupInfo getRSGroup(Address hostPort) throws IOException {
+    GetRSGroupInfoOfServerRequest request = GetRSGroupInfoOfServerRequest.newBuilder().setServer(
+        HBaseProtos.ServerName.newBuilder().setHostName(hostPort.getHostname())
+            .setPort(hostPort.getPort()).build()).build();
     try {
-      GetRSGroupInfoOfTableResponse resp = stub.getRSGroupInfoOfTable(null, request);
+      GetRSGroupInfoOfServerResponse resp = stub.getRSGroupInfoOfServer(null, request);
       if (resp.hasRSGroupInfo()) {
         return ProtobufUtil.toGroupInfo(resp.getRSGroupInfo());
       }
@@ -97,55 +972,76 @@ public class RSGroupAdminClient implements RSGroupAdmin {
   }
 
   @Override
-  public void moveServers(Set<Address> servers, String targetGroup) throws IOException {
-    Set<HBaseProtos.ServerName> hostPorts = Sets.newHashSet();
-    for(Address el: servers) {
-      hostPorts.add(HBaseProtos.ServerName.newBuilder()
-        .setHostName(el.getHostname())
-        .setPort(el.getPort())
-        .build());
+  public RSGroupInfo getRSGroup(TableName tableName) throws IOException {
+    GetRSGroupInfoOfTableRequest request = GetRSGroupInfoOfTableRequest.newBuilder()
+        .setTableName(ProtobufUtil.toProtoTableName(tableName)).build();
+    try {
+      GetRSGroupInfoOfTableResponse resp = stub.getRSGroupInfoOfTable(null, request);
+      if (resp.hasRSGroupInfo()) {
+        return ProtobufUtil.toGroupInfo(resp.getRSGroupInfo());
+      }
+      return null;
+    } catch (ServiceException e) {
+      throw ProtobufUtil.handleRemoteException(e);
     }
-    MoveServersRequest request = MoveServersRequest.newBuilder()
-            .setTargetGroup(targetGroup)
-            .addAllServers(hostPorts)
-            .build();
+  }
+
+  @Override
+  public void removeRSGroup(String name) throws IOException {
+    RemoveRSGroupRequest request = RemoveRSGroupRequest.newBuilder().setRSGroupName(name).build();
     try {
-      stub.moveServers(null, request);
+      stub.removeRSGroup(null, request);
     } catch (ServiceException e) {
       throw ProtobufUtil.handleRemoteException(e);
     }
   }
 
-  public void moveTables(Set<TableName> tables, String targetGroup) throws IOException {
-    MoveTablesRequest.Builder builder = MoveTablesRequest.newBuilder().setTargetGroup(targetGroup);
-    for(TableName tableName: tables) {
-      builder.addTableName(ProtobufUtil.toProtoTableName(tableName));
-      if (!admin.tableExists(tableName)) {
-        throw new TableNotFoundException(tableName);
-      }
+  @Override
+  public void removeRSGroup(Set<Address> servers) throws IOException {
+    Set<HBaseProtos.ServerName> hostPorts = Sets.newHashSet();
+    for (Address el : servers) {
+      hostPorts.add(
+          HBaseProtos.ServerName.newBuilder().setHostName(el.getHostname()).setPort(el.getPort())
+              .build());
     }
+    RemoveServersRequest request =
+        RemoveServersRequest.newBuilder().addAllServers(hostPorts).build();
     try {
-      stub.moveTables(null, builder.build());
+      stub.removeServers(null, request);
     } catch (ServiceException e) {
       throw ProtobufUtil.handleRemoteException(e);
     }
   }
 
   @Override
-  public void addRSGroup(String groupName) throws IOException {
-    AddRSGroupRequest request = AddRSGroupRequest.newBuilder().setRSGroupName(groupName).build();
+  public void moveToRSGroup(Set<Address> servers, String targetGroup) throws IOException {
+    Set<HBaseProtos.ServerName> hostPorts = Sets.newHashSet();
+    for (Address el : servers) {
+      hostPorts.add(
+          HBaseProtos.ServerName.newBuilder().setHostName(el.getHostname()).setPort(el.getPort())
+              .build());
+    }
+    MoveServersRequest request =
+        MoveServersRequest.newBuilder().setTargetGroup(targetGroup).addAllServers(hostPorts)
+            .build();
     try {
-      stub.addRSGroup(null, request);
+      stub.moveServers(null, request);
     } catch (ServiceException e) {
       throw ProtobufUtil.handleRemoteException(e);
     }
   }
 
   @Override
-  public void removeRSGroup(String name) throws IOException {
-    RemoveRSGroupRequest request = RemoveRSGroupRequest.newBuilder().setRSGroupName(name).build();
+  public void setRSGroup(Set<TableName> tables, String groupName) throws IOException {
+    MoveTablesRequest.Builder builder = MoveTablesRequest.newBuilder().setTargetGroup(groupName);
+    for (TableName tableName : tables) {
+      builder.addTableName(ProtobufUtil.toProtoTableName(tableName));
+      if (!admin.tableExists(tableName)) {
+        throw new TableNotFoundException(tableName);
+      }
+    }
     try {
-      stub.removeRSGroup(null, request);
+      stub.moveTables(null, builder.build());
     } catch (ServiceException e) {
       throw ProtobufUtil.handleRemoteException(e);
     }
@@ -153,8 +1049,8 @@ public class RSGroupAdminClient implements RSGroupAdmin {
 
   @Override
   public boolean balanceRSGroup(String groupName) throws IOException {
-    BalanceRSGroupRequest request = BalanceRSGroupRequest.newBuilder()
-        .setRSGroupName(groupName).build();
+    BalanceRSGroupRequest request =
+        BalanceRSGroupRequest.newBuilder().setRSGroupName(groupName).build();
     try {
       return stub.balanceRSGroup(null, request).getBalanceRan();
     } catch (ServiceException e) {
@@ -165,10 +1061,11 @@ public class RSGroupAdminClient implements RSGroupAdmin {
   @Override
   public List<RSGroupInfo> listRSGroups() throws IOException {
     try {
-      List<RSGroupProtos.RSGroupInfo> resp = stub.listRSGroupInfos(null,
-          ListRSGroupInfosRequest.getDefaultInstance()).getRSGroupInfoList();
+      List<RSGroupProtos.RSGroupInfo> resp =
+          stub.listRSGroupInfos(null, ListRSGroupInfosRequest.getDefaultInstance())
+              .getRSGroupInfoList();
       List<RSGroupInfo> result = new ArrayList<>(resp.size());
-      for(RSGroupProtos.RSGroupInfo entry : resp) {
+      for (RSGroupProtos.RSGroupInfo entry : resp) {
         result.add(ProtobufUtil.toGroupInfo(entry));
       }
       return result;
@@ -179,62 +1076,12 @@ public class RSGroupAdminClient implements RSGroupAdmin {
 
   @Override
   public RSGroupInfo getRSGroupOfServer(Address hostPort) throws IOException {
-    GetRSGroupInfoOfServerRequest request = GetRSGroupInfoOfServerRequest.newBuilder()
-            .setServer(HBaseProtos.ServerName.newBuilder()
-                .setHostName(hostPort.getHostname())
-                .setPort(hostPort.getPort())
-                .build())
-            .build();
-    try {
-      GetRSGroupInfoOfServerResponse resp = stub.getRSGroupInfoOfServer(null, request);
-      if (resp.hasRSGroupInfo()) {
-        return ProtobufUtil.toGroupInfo(resp.getRSGroupInfo());
-      }
-      return null;
-    } catch (ServiceException e) {
-      throw ProtobufUtil.handleRemoteException(e);
-    }
-  }
-
-  public void moveServersAndTables(Set<Address> servers, Set<TableName> tables, String targetGroup)
-      throws IOException {
-    MoveServersAndTablesRequest.Builder builder =
-            MoveServersAndTablesRequest.newBuilder().setTargetGroup(targetGroup);
-    for(Address el: servers) {
-      builder.addServers(HBaseProtos.ServerName.newBuilder()
-              .setHostName(el.getHostname())
-              .setPort(el.getPort())
-              .build());
-    }
-    for(TableName tableName: tables) {
-      builder.addTableName(ProtobufUtil.toProtoTableName(tableName));
-      if (!admin.tableExists(tableName)) {
-        throw new TableNotFoundException(tableName);
-      }
-    }
-    try {
-      stub.moveServersAndTables(null, builder.build());
-    } catch (ServiceException e) {
-      throw ProtobufUtil.handleRemoteException(e);
-    }
+    return getRSGroup(hostPort);
   }
 
   @Override
   public void removeServers(Set<Address> servers) throws IOException {
-    Set<HBaseProtos.ServerName> hostPorts = Sets.newHashSet();
-    for(Address el: servers) {
-      hostPorts.add(HBaseProtos.ServerName.newBuilder()
-          .setHostName(el.getHostname())
-          .setPort(el.getPort())
-          .build());
-    }
-    RemoveServersRequest request = RemoveServersRequest.newBuilder()
-        .addAllServers(hostPorts)
-        .build();
-    try {
-      stub.removeServers(null, request);
-    } catch (ServiceException e) {
-      throw ProtobufUtil.handleRemoteException(e);
-    }
+    removeRSGroup(servers);
   }
+
 }
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupAdminEndpoint.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupAdminEndpoint.java
index 353b4d2..621e822 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupAdminEndpoint.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupAdminEndpoint.java
@@ -25,12 +25,8 @@ import org.apache.hadoop.hbase.coprocessor.CoreCoprocessor;
 import org.apache.hadoop.hbase.coprocessor.HasMasterServices;
 import org.apache.hadoop.hbase.coprocessor.MasterCoprocessor;
 import org.apache.hadoop.hbase.master.MasterServices;
-import org.apache.hadoop.hbase.security.UserProvider;
-import org.apache.hadoop.hbase.security.access.AccessChecker;
 import org.apache.yetus.audience.InterfaceAudience;
 
-import org.apache.hbase.thirdparty.com.google.common.annotations.VisibleForTesting;
-
 /**
  * @deprecated Keep it here only for compatibility with old client, all the logics have been moved
  *             into core of HBase.
@@ -42,8 +38,6 @@ public class RSGroupAdminEndpoint implements MasterCoprocessor {
   // Only instance of RSGroupInfoManager. RSGroup aware load balancers ask for this instance on
   // their setup.
   private MasterServices master;
-  private RSGroupInfoManager groupInfoManager;
-  private RSGroupAdminServer groupAdminServer;
   private RSGroupAdminServiceImpl groupAdminService = new RSGroupAdminServiceImpl();
 
   @Override
@@ -51,19 +45,8 @@ public class RSGroupAdminEndpoint implements MasterCoprocessor {
     if (!(env instanceof HasMasterServices)) {
       throw new IOException("Does not implement HMasterServices");
     }
-
     master = ((HasMasterServices) env).getMasterServices();
-    groupInfoManager = master.getRSGroupInfoManager();
-    groupAdminServer = new RSGroupAdminServer(master, groupInfoManager);
-    AccessChecker accessChecker = ((HasMasterServices) env).getMasterServices().getAccessChecker();
-
-    // set the user-provider.
-    UserProvider userProvider = UserProvider.instantiate(env.getConfiguration());
-    groupAdminService.initialize(master, groupAdminServer, accessChecker, userProvider);
-  }
-
-  @Override
-  public void stop(CoprocessorEnvironment env) {
+    groupAdminService.initialize(master);
   }
 
   @Override
@@ -72,11 +55,7 @@ public class RSGroupAdminEndpoint implements MasterCoprocessor {
   }
 
   RSGroupInfoManager getGroupInfoManager() {
-    return groupInfoManager;
+    return master.getRSGroupInfoManager();
   }
 
-  @VisibleForTesting
-  RSGroupAdminServiceImpl getGroupAdminService() {
-    return groupAdminService;
-  }
 }
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupAdminServer.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupAdminServer.java
deleted file mode 100644
index f998d94..0000000
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupAdminServer.java
+++ /dev/null
@@ -1,496 +0,0 @@
-/**
- * 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.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.Set;
-import java.util.function.Function;
-import org.apache.commons.lang3.StringUtils;
-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.RegionInfo;
-import org.apache.hadoop.hbase.client.TableDescriptor;
-import org.apache.hadoop.hbase.constraint.ConstraintException;
-import org.apache.hadoop.hbase.master.HMaster;
-import org.apache.hadoop.hbase.master.LoadBalancer;
-import org.apache.hadoop.hbase.master.MasterServices;
-import org.apache.hadoop.hbase.master.RegionPlan;
-import org.apache.hadoop.hbase.master.RegionState;
-import org.apache.hadoop.hbase.master.ServerManager;
-import org.apache.hadoop.hbase.master.assignment.RegionStateNode;
-import org.apache.hadoop.hbase.net.Address;
-import org.apache.yetus.audience.InterfaceAudience;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import org.apache.hbase.thirdparty.com.google.common.collect.Maps;
-
-/**
- * Service to support Region Server Grouping (HBase-6721).
- */
-@InterfaceAudience.Private
-public class RSGroupAdminServer implements RSGroupAdmin {
-  private static final Logger LOG = LoggerFactory.getLogger(RSGroupAdminServer.class);
-  static final String KEEP_ONE_SERVER_IN_DEFAULT_ERROR_MESSAGE = "should keep at least " +
-          "one server in 'default' RSGroup.";
-
-  private MasterServices master;
-  final RSGroupInfoManager rsGroupInfoManager;
-
-  /** Define the config key of retries threshold when movements failed */
-  //made package private for testing
-  static final String FAILED_MOVE_MAX_RETRY = "hbase.rsgroup.move.max.retry";
-
-  /** Define the default number of retries */
-  //made package private for testing
-  static final int DEFAULT_MAX_RETRY_VALUE = 50;
-
-  private int moveMaxRetry;
-
-  public RSGroupAdminServer(MasterServices master, RSGroupInfoManager rsGroupInfoManager) {
-    this.master = master;
-    this.rsGroupInfoManager = rsGroupInfoManager;
-    this.moveMaxRetry = master.getConfiguration().getInt(FAILED_MOVE_MAX_RETRY,
-        DEFAULT_MAX_RETRY_VALUE);
-  }
-
-  @Override
-  public RSGroupInfo getRSGroupInfo(String groupName) throws IOException {
-    return rsGroupInfoManager.getRSGroup(groupName);
-  }
-
-  private void checkOnlineServersOnly(Set<Address> servers) throws ConstraintException {
-    // This uglyness is because we only have Address, not ServerName.
-    // Online servers are keyed by ServerName.
-    Set<Address> onlineServers = new HashSet<>();
-    for(ServerName server: master.getServerManager().getOnlineServers().keySet()) {
-      onlineServers.add(server.getAddress());
-    }
-    for (Address address: servers) {
-      if (!onlineServers.contains(address)) {
-        throw new ConstraintException(
-            "Server " + address + " is not an online server in 'default' RSGroup.");
-      }
-    }
-  }
-
-  /**
-   * Check passed name. Fail if nulls or if corresponding RSGroupInfo not found.
-   * @return The RSGroupInfo named <code>name</code>
-   */
-  private RSGroupInfo getAndCheckRSGroupInfo(String name) throws IOException {
-    if (StringUtils.isEmpty(name)) {
-      throw new ConstraintException("RSGroup cannot be null.");
-    }
-    RSGroupInfo rsGroupInfo = getRSGroupInfo(name);
-    if (rsGroupInfo == null) {
-      throw new ConstraintException("RSGroup does not exist: " + name);
-    }
-    return rsGroupInfo;
-  }
-
-  /**
-   * @return List of Regions associated with this <code>server</code>.
-   */
-  private List<RegionInfo> getRegions(final Address server) {
-    LinkedList<RegionInfo> regions = new LinkedList<>();
-    for (Map.Entry<RegionInfo, ServerName> el :
-        master.getAssignmentManager().getRegionStates().getRegionAssignments().entrySet()) {
-      if (el.getValue() == null) {
-        continue;
-      }
-
-      if (el.getValue().getAddress().equals(server)) {
-        addRegion(regions, el.getKey());
-      }
-    }
-    for (RegionStateNode state : master.getAssignmentManager().getRegionsInTransition()) {
-      if (state.getRegionLocation() != null &&
-          state.getRegionLocation().getAddress().equals(server)) {
-        addRegion(regions, state.getRegionInfo());
-      }
-    }
-    return regions;
-  }
-
-  private void addRegion(final LinkedList<RegionInfo> regions, RegionInfo hri) {
-    // If meta, move it last otherwise other unassigns fail because meta is not
-    // online for them to update state in. This is dodgy. Needs to be made more
-    // robust. See TODO below.
-    if (hri.isMetaRegion()) {
-      regions.addLast(hri);
-    } else {
-      regions.addFirst(hri);
-    }
-  }
-
-  /**
-   * Move every region from servers which are currently located on these servers, but should not be
-   * located there.
-   * @param servers the servers that will move to new group
-   * @param targetGroupName the target group name
-   * @throws IOException if moving the server and tables fail
-   */
-  private void moveServerRegionsFromGroup(Set<Address> servers, String targetGroupName)
-    throws IOException {
-    moveRegionsBetweenGroups(servers, targetGroupName, rs -> getRegions(rs), info -> {
-      try {
-        String groupName = RSGroupUtil.getRSGroupInfo(master, rsGroupInfoManager, info.getTable())
-          .map(RSGroupInfo::getName).orElse(RSGroupInfo.DEFAULT_GROUP);
-        return groupName.equals(targetGroupName);
-      } catch (IOException e) {
-        LOG.warn("Failed to test group for region {} and target group {}", info, targetGroupName);
-        return false;
-      }
-    }, rs -> rs.getHostname());
-  }
-
-  private <T> void moveRegionsBetweenGroups(Set<T> regionsOwners, String targetGroupName,
-      Function<T, List<RegionInfo>> getRegionsInfo, Function<RegionInfo, Boolean> validation,
-      Function<T, String> getOwnerName) throws IOException {
-    boolean hasRegionsToMove;
-    int retry = 0;
-    Set<T> allOwners = new HashSet<>(regionsOwners);
-    Set<String> failedRegions = new HashSet<>();
-    IOException toThrow = null;
-    do {
-      hasRegionsToMove = false;
-      for (Iterator<T> iter = allOwners.iterator(); iter.hasNext(); ) {
-        T owner = iter.next();
-        // Get regions that are associated with this server and filter regions by group tables.
-        for (RegionInfo region : getRegionsInfo.apply(owner)) {
-          if (!validation.apply(region)) {
-            LOG.info("Moving region {}, which do not belong to RSGroup {}",
-                region.getShortNameToLog(), targetGroupName);
-            try {
-              this.master.getAssignmentManager().move(region);
-              failedRegions.remove(region.getRegionNameAsString());
-            } catch (IOException ioe) {
-              LOG.debug("Move region {} from group failed, will retry, current retry time is {}",
-                  region.getShortNameToLog(), retry, ioe);
-              toThrow = ioe;
-              failedRegions.add(region.getRegionNameAsString());
-            }
-            if (master.getAssignmentManager().getRegionStates().
-                getRegionState(region).isFailedOpen()) {
-              continue;
-            }
-            hasRegionsToMove = true;
-          }
-        }
-
-        if (!hasRegionsToMove) {
-          LOG.info("No more regions to move from {} to RSGroup {}", getOwnerName.apply(owner),
-            targetGroupName);
-          iter.remove();
-        }
-      }
-
-      retry++;
-      try {
-        rsGroupInfoManager.wait(1000);
-      } catch (InterruptedException e) {
-        LOG.warn("Sleep interrupted", e);
-        Thread.currentThread().interrupt();
-      }
-    } while (hasRegionsToMove && retry <= moveMaxRetry);
-
-    //has up to max retry time or there are no more regions to move
-    if (hasRegionsToMove) {
-      // print failed moved regions, for later process conveniently
-      String msg = String
-          .format("move regions for group %s failed, failed regions: %s", targetGroupName,
-              failedRegions);
-      LOG.error(msg);
-      throw new DoNotRetryIOException(
-          msg + ", just record the last failed region's cause, more details in server log",
-          toThrow);
-    }
-  }
-
-  @Override
-  public void moveServers(Set<Address> servers, String targetGroupName) throws IOException {
-    if (servers == null) {
-      throw new ConstraintException("The list of servers to move cannot be null.");
-    }
-    if (servers.isEmpty()) {
-      // For some reason this difference between null servers and isEmpty is important distinction.
-      // TODO. Why? Stuff breaks if I equate them.
-      return;
-    }
-    //check target group
-    getAndCheckRSGroupInfo(targetGroupName);
-
-    // Hold a lock on the manager instance while moving servers to prevent
-    // another writer changing our state while we are working.
-    synchronized (rsGroupInfoManager) {
-      // Presume first server's source group. Later ensure all servers are from this group.
-      Address firstServer = servers.iterator().next();
-      RSGroupInfo srcGrp = rsGroupInfoManager.getRSGroupOfServer(firstServer);
-      if (srcGrp == null) {
-        // Be careful. This exception message is tested for in TestRSGroupsBase...
-        throw new ConstraintException("Source RSGroup for server " + firstServer
-            + " does not exist.");
-      }
-      // Only move online servers (when moving from 'default') or servers from other
-      // groups. This prevents bogus servers from entering groups
-      if (RSGroupInfo.DEFAULT_GROUP.equals(srcGrp.getName())) {
-        if (srcGrp.getServers().size() <= servers.size()) {
-          throw new ConstraintException(KEEP_ONE_SERVER_IN_DEFAULT_ERROR_MESSAGE);
-        }
-        checkOnlineServersOnly(servers);
-      }
-      // Ensure all servers are of same rsgroup.
-      for (Address server: servers) {
-        String tmpGroup = rsGroupInfoManager.getRSGroupOfServer(server).getName();
-        if (!tmpGroup.equals(srcGrp.getName())) {
-          throw new ConstraintException("Move server request should only come from one source " +
-              "RSGroup. Expecting only " + srcGrp.getName() + " but contains " + tmpGroup);
-        }
-      }
-      if (srcGrp.getServers().size() <= servers.size()) {
-        // check if there are still tables reference this group
-        for (TableDescriptor td : master.getTableDescriptors().getAll().values()) {
-          Optional<String> optGroupName = td.getRegionServerGroup();
-          if (optGroupName.isPresent() && optGroupName.get().equals(srcGrp.getName())) {
-            throw new ConstraintException(
-                "Cannot leave a RSGroup " + srcGrp.getName() + " that contains tables('" +
-                    td.getTableName() + "' at least) without servers to host them.");
-          }
-        }
-      }
-
-      // MovedServers may be < passed in 'servers'.
-      Set<Address> movedServers = rsGroupInfoManager.moveServers(servers, srcGrp.getName(),
-          targetGroupName);
-      moveServerRegionsFromGroup(movedServers, targetGroupName);
-      LOG.info("Move servers done: {} => {}", srcGrp.getName(), targetGroupName);
-    }
-  }
-
-  @Override
-  public void addRSGroup(String name) throws IOException {
-    rsGroupInfoManager.addRSGroup(new RSGroupInfo(name));
-  }
-
-  @Override
-  public void removeRSGroup(String name) throws IOException {
-    // Hold a lock on the manager instance while moving servers to prevent
-    // another writer changing our state while we are working.
-    synchronized (rsGroupInfoManager) {
-      RSGroupInfo rsGroupInfo = rsGroupInfoManager.getRSGroup(name);
-      if (rsGroupInfo == null) {
-        throw new ConstraintException("RSGroup " + name + " does not exist");
-      }
-      int serverCount = rsGroupInfo.getServers().size();
-      if (serverCount > 0) {
-        throw new ConstraintException("RSGroup " + name + " has " + serverCount +
-          " servers; you must remove these servers from the RSGroup before" +
-          " the RSGroup can be removed.");
-      }
-      for (TableDescriptor td : master.getTableDescriptors().getAll().values()) {
-        if (td.getRegionServerGroup().map(name::equals).orElse(false)) {
-          throw new ConstraintException("RSGroup " + name + " is already referenced by " +
-            td.getTableName() + "; you must remove all the tables from the rsgroup before " +
-            "the rsgroup can be removed.");
-        }
-      }
-      for (NamespaceDescriptor ns : master.getClusterSchema().getNamespaces()) {
-        String nsGroup = ns.getConfigurationValue(RSGroupInfo.NAMESPACE_DESC_PROP_GROUP);
-        if (nsGroup != null && nsGroup.equals(name)) {
-          throw new ConstraintException(
-            "RSGroup " + name + " is referenced by namespace: " + ns.getName());
-        }
-      }
-      rsGroupInfoManager.removeRSGroup(name);
-    }
-  }
-
-  @Override
-  public boolean balanceRSGroup(String groupName) throws IOException {
-    ServerManager serverManager = master.getServerManager();
-    LoadBalancer balancer = master.getLoadBalancer();
-
-    synchronized (balancer) {
-      // If balance not true, don't run balancer.
-      if (!((HMaster) master).isBalancerOn()) {
-        return false;
-      }
-
-      if (getRSGroupInfo(groupName) == null) {
-        throw new ConstraintException("RSGroup does not exist: " + groupName);
-      }
-      // Only allow one balance run at at time.
-      Map<String, RegionState> groupRIT = rsGroupGetRegionsInTransition(groupName);
-      if (groupRIT.size() > 0) {
-        LOG.debug("Not running balancer because {} region(s) in transition: {}", groupRIT.size(),
-          StringUtils.abbreviate(
-            master.getAssignmentManager().getRegionStates().getRegionsInTransition().toString(),
-            256));
-        return false;
-      }
-      if (serverManager.areDeadServersInProgress()) {
-        LOG.debug("Not running balancer because processing dead regionserver(s): {}",
-          serverManager.getDeadServers());
-        return false;
-      }
-
-      // We balance per group instead of per table
-      List<RegionPlan> plans = new ArrayList<>();
-      Map<TableName, Map<ServerName, List<RegionInfo>>> assignmentsByTable =
-        getRSGroupAssignmentsByTable(groupName);
-      for (Map.Entry<TableName, Map<ServerName, List<RegionInfo>>> tableMap : assignmentsByTable
-        .entrySet()) {
-        LOG.info("Creating partial plan for table {} : {}", tableMap.getKey(), tableMap.getValue());
-        List<RegionPlan> partialPlans = balancer.balanceCluster(tableMap.getValue());
-        LOG.info("Partial plan for table {} : {}", tableMap.getKey(), partialPlans);
-        if (partialPlans != null) {
-          plans.addAll(partialPlans);
-        }
-      }
-      boolean balancerRan = !plans.isEmpty();
-      if (balancerRan) {
-        LOG.info("RSGroup balance {} starting with plan count: {}", groupName, plans.size());
-        master.executeRegionPlansWithThrottling(plans);
-        LOG.info("RSGroup balance " + groupName + " completed");
-      }
-      return balancerRan;
-    }
-  }
-
-  @Override
-  public List<RSGroupInfo> listRSGroups() throws IOException {
-    return rsGroupInfoManager.listRSGroups();
-  }
-
-  @Override
-  public RSGroupInfo getRSGroupOfServer(Address hostPort) throws IOException {
-    return rsGroupInfoManager.getRSGroupOfServer(hostPort);
-  }
-
-  @Override
-  public void removeServers(Set<Address> servers) throws IOException {
-    if (servers == null || servers.isEmpty()) {
-      throw new ConstraintException("The set of servers to remove cannot be null or empty.");
-    }
-    // Hold a lock on the manager instance while moving servers to prevent
-    // another writer changing our state while we are working.
-    synchronized (rsGroupInfoManager) {
-      // check the set of servers
-      checkForDeadOrOnlineServers(servers);
-      rsGroupInfoManager.removeServers(servers);
-      LOG.info("Remove decommissioned servers {} from RSGroup done", servers);
-    }
-  }
-
-  private boolean isTableInGroup(TableName tableName, String groupName,
-    Set<TableName> tablesInGroupCache) throws IOException {
-    if (tablesInGroupCache.contains(tableName)) {
-      return true;
-    }
-    if (RSGroupUtil.getRSGroupInfo(master, rsGroupInfoManager, tableName).map(RSGroupInfo::getName)
-      .orElse(RSGroupInfo.DEFAULT_GROUP).equals(groupName)) {
-      tablesInGroupCache.add(tableName);
-      return true;
-    }
-    return false;
-  }
-
-  private Map<String, RegionState> rsGroupGetRegionsInTransition(String groupName)
-    throws IOException {
-    Map<String, RegionState> rit = Maps.newTreeMap();
-    Set<TableName> tablesInGroupCache = new HashSet<>();
-    for (RegionStateNode regionNode : master.getAssignmentManager().getRegionsInTransition()) {
-      TableName tn = regionNode.getTable();
-      if (isTableInGroup(tn, groupName, tablesInGroupCache)) {
-        rit.put(regionNode.getRegionInfo().getEncodedName(), regionNode.toRegionState());
-      }
-    }
-    return rit;
-  }
-
-  private Map<TableName, Map<ServerName, List<RegionInfo>>>
-    getRSGroupAssignmentsByTable(String groupName) throws IOException {
-    Map<TableName, Map<ServerName, List<RegionInfo>>> result = Maps.newHashMap();
-    Set<TableName> tablesInGroupCache = new HashSet<>();
-    for (Map.Entry<RegionInfo, ServerName> entry : master.getAssignmentManager().getRegionStates()
-      .getRegionAssignments().entrySet()) {
-      RegionInfo region = entry.getKey();
-      TableName tn = region.getTable();
-      ServerName server = entry.getValue();
-      if (isTableInGroup(tn, groupName, tablesInGroupCache)) {
-        result.computeIfAbsent(tn, k -> new HashMap<>())
-          .computeIfAbsent(server, k -> new ArrayList<>()).add(region);
-      }
-    }
-    RSGroupInfo rsGroupInfo = getRSGroupInfo(groupName);
-    for (ServerName serverName : master.getServerManager().getOnlineServers().keySet()) {
-      if (rsGroupInfo.containsServer(serverName.getAddress())) {
-        for (Map<ServerName, List<RegionInfo>> map : result.values()) {
-          map.computeIfAbsent(serverName, k -> Collections.emptyList());
-        }
-      }
-    }
-
-    return result;
-  }
-
-  /**
-   * Check if the set of servers are belong to dead servers list or online servers list.
-   * @param servers servers to remove
-   */
-  private void checkForDeadOrOnlineServers(Set<Address> servers) throws ConstraintException {
-    // This uglyness is because we only have Address, not ServerName.
-    Set<Address> onlineServers = new HashSet<>();
-    List<ServerName> drainingServers = master.getServerManager().getDrainingServersList();
-    for (ServerName server : master.getServerManager().getOnlineServers().keySet()) {
-      // Only online but not decommissioned servers are really online
-      if (!drainingServers.contains(server)) {
-        onlineServers.add(server.getAddress());
-      }
-    }
-
-    Set<Address> deadServers = new HashSet<>();
-    for(ServerName server: master.getServerManager().getDeadServers().copyServerNames()) {
-      deadServers.add(server.getAddress());
-    }
-
-    for (Address address: servers) {
-      if (onlineServers.contains(address)) {
-        throw new ConstraintException(
-            "Server " + address + " is an online server, not allowed to remove.");
-      }
-      if (deadServers.contains(address)) {
-        throw new ConstraintException(
-            "Server " + address + " is on the dead servers list,"
-                + " Maybe it will come back again, not allowed to remove.");
-      }
-    }
-  }
-}
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupAdminServiceImpl.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupAdminServiceImpl.java
index 749d353..a00deec 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupAdminServiceImpl.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupAdminServiceImpl.java
@@ -33,7 +33,6 @@ import org.apache.hadoop.hbase.TableName;
 import org.apache.hadoop.hbase.client.TableDescriptor;
 import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
 import org.apache.hadoop.hbase.ipc.CoprocessorRpcUtils;
-import org.apache.hadoop.hbase.ipc.RpcServer;
 import org.apache.hadoop.hbase.master.MasterServices;
 import org.apache.hadoop.hbase.master.procedure.ProcedureSyncWait;
 import org.apache.hadoop.hbase.net.Address;
@@ -63,62 +62,34 @@ import org.apache.hadoop.hbase.protobuf.generated.RSGroupAdminProtos.RemoveRSGro
 import org.apache.hadoop.hbase.protobuf.generated.RSGroupAdminProtos.RemoveRSGroupResponse;
 import org.apache.hadoop.hbase.protobuf.generated.RSGroupAdminProtos.RemoveServersRequest;
 import org.apache.hadoop.hbase.protobuf.generated.RSGroupAdminProtos.RemoveServersResponse;
-import org.apache.hadoop.hbase.security.User;
-import org.apache.hadoop.hbase.security.UserProvider;
-import org.apache.hadoop.hbase.security.access.AccessChecker;
-import org.apache.hadoop.hbase.security.access.Permission.Action;
 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.Sets;
 
 /**
  * Implementation of RSGroupAdminService defined in RSGroupAdmin.proto. This class calls
- * {@link RSGroupAdminServer} for actual work, converts result to protocol buffer response, handles
- * exceptions if any occurred and then calls the {@code RpcCallback} with the response.
+ * {@link RSGroupInfoManagerImpl} for actual work, converts result to protocol buffer response,
+ * handles exceptions if any occurred and then calls the {@code RpcCallback} with the response.
+ *
+ * @deprecated Keep it here only for compatibility with {@link RSGroupAdminClient},
+ *     using {@link org.apache.hadoop.hbase.master.MasterRpcServices} instead.
  */
+@Deprecated
 class RSGroupAdminServiceImpl extends RSGroupAdminProtos.RSGroupAdminService {
 
   private static final Logger LOG = LoggerFactory.getLogger(RSGroupAdminServiceImpl.class);
 
   private MasterServices master;
 
-  private RSGroupAdminServer groupAdminServer;
-
-  private AccessChecker accessChecker;
-
-  /** Provider for mapping principal names to Users */
-  private UserProvider userProvider;
+  private RSGroupInfoManager rsGroupInfoManager;
 
   RSGroupAdminServiceImpl() {
   }
 
-  void initialize(MasterServices master, RSGroupAdminServer groupAdminServer,
-      AccessChecker accessChecker, UserProvider userProvider) {
-    this.master = master;
-    this.groupAdminServer = groupAdminServer;
-    this.accessChecker = accessChecker;
-    this.userProvider = userProvider;
-  }
-
-  @VisibleForTesting
-  void checkPermission(String request) throws IOException {
-    accessChecker.requirePermission(getActiveUser(), request, null, Action.ADMIN);
-  }
-
-  /**
-   * Returns the active user to which authorization checks should be applied. If we are in the
-   * context of an RPC call, the remote user is used, otherwise the currently logged in user is
-   * used.
-   */
-  private User getActiveUser() throws IOException {
-    // for non-rpc handling, fallback to system user
-    Optional<User> optionalUser = RpcServer.getRequestUser();
-    if (optionalUser.isPresent()) {
-      return optionalUser.get();
-    }
-    return userProvider.getCurrent();
+  void initialize(MasterServices masterServices){
+    this.master = masterServices;
+    this.rsGroupInfoManager = masterServices.getRSGroupInfoManager();
   }
 
   // for backward compatible
@@ -137,8 +108,7 @@ class RSGroupAdminServiceImpl extends RSGroupAdminProtos.RSGroupAdminService {
       if (master.getMasterCoprocessorHost() != null) {
         master.getMasterCoprocessorHost().preGetRSGroupInfo(groupName);
       }
-      checkPermission("getRSGroupInfo");
-      RSGroupInfo rsGroupInfo = groupAdminServer.getRSGroupInfo(groupName);
+      RSGroupInfo rsGroupInfo = rsGroupInfoManager.getRSGroup(groupName);
       if (rsGroupInfo != null) {
         builder.setRSGroupInfo(ProtobufUtil.toProtoGroupInfo(fillTables(rsGroupInfo)));
       }
@@ -162,14 +132,13 @@ class RSGroupAdminServiceImpl extends RSGroupAdminProtos.RSGroupAdminService {
       if (master.getMasterCoprocessorHost() != null) {
         master.getMasterCoprocessorHost().preGetRSGroupInfoOfTable(tableName);
       }
-      checkPermission("getRSGroupInfoOfTable");
       Optional<RSGroupInfo> optGroup =
-        RSGroupUtil.getRSGroupInfo(master, groupAdminServer.rsGroupInfoManager, tableName);
+        RSGroupUtil.getRSGroupInfo(master, rsGroupInfoManager, tableName);
       if (optGroup.isPresent()) {
         builder.setRSGroupInfo(ProtobufUtil.toProtoGroupInfo(fillTables(optGroup.get())));
       } else {
         if (master.getTableStateManager().isTablePresent(tableName)) {
-          RSGroupInfo rsGroupInfo = groupAdminServer.getRSGroupInfo(RSGroupInfo.DEFAULT_GROUP);
+          RSGroupInfo rsGroupInfo = rsGroupInfoManager.getRSGroup(RSGroupInfo.DEFAULT_GROUP);
           builder.setRSGroupInfo(ProtobufUtil.toProtoGroupInfo(fillTables(rsGroupInfo)));
         }
       }
@@ -197,8 +166,7 @@ class RSGroupAdminServiceImpl extends RSGroupAdminProtos.RSGroupAdminService {
       if (master.getMasterCoprocessorHost() != null) {
         master.getMasterCoprocessorHost().preMoveServers(hostPorts, request.getTargetGroup());
       }
-      checkPermission("moveServers");
-      groupAdminServer.moveServers(hostPorts, request.getTargetGroup());
+      rsGroupInfoManager.moveServers(hostPorts, request.getTargetGroup());
       if (master.getMasterCoprocessorHost() != null) {
         master.getMasterCoprocessorHost().postMoveServers(hostPorts, request.getTargetGroup());
       }
@@ -243,7 +211,6 @@ class RSGroupAdminServiceImpl extends RSGroupAdminProtos.RSGroupAdminService {
       if (master.getMasterCoprocessorHost() != null) {
         master.getMasterCoprocessorHost().preMoveTables(tables, request.getTargetGroup());
       }
-      checkPermission("moveTables");
       moveTablesAndWait(tables, request.getTargetGroup());
       if (master.getMasterCoprocessorHost() != null) {
         master.getMasterCoprocessorHost().postMoveTables(tables, request.getTargetGroup());
@@ -263,8 +230,7 @@ class RSGroupAdminServiceImpl extends RSGroupAdminProtos.RSGroupAdminService {
       if (master.getMasterCoprocessorHost() != null) {
         master.getMasterCoprocessorHost().preAddRSGroup(request.getRSGroupName());
       }
-      checkPermission("addRSGroup");
-      groupAdminServer.addRSGroup(request.getRSGroupName());
+      rsGroupInfoManager.addRSGroup(new RSGroupInfo(request.getRSGroupName()));
       if (master.getMasterCoprocessorHost() != null) {
         master.getMasterCoprocessorHost().postAddRSGroup(request.getRSGroupName());
       }
@@ -283,8 +249,7 @@ class RSGroupAdminServiceImpl extends RSGroupAdminProtos.RSGroupAdminService {
       if (master.getMasterCoprocessorHost() != null) {
         master.getMasterCoprocessorHost().preRemoveRSGroup(request.getRSGroupName());
       }
-      checkPermission("removeRSGroup");
-      groupAdminServer.removeRSGroup(request.getRSGroupName());
+      rsGroupInfoManager.removeRSGroup(request.getRSGroupName());
       if (master.getMasterCoprocessorHost() != null) {
         master.getMasterCoprocessorHost().postRemoveRSGroup(request.getRSGroupName());
       }
@@ -304,8 +269,7 @@ class RSGroupAdminServiceImpl extends RSGroupAdminProtos.RSGroupAdminService {
       if (master.getMasterCoprocessorHost() != null) {
         master.getMasterCoprocessorHost().preBalanceRSGroup(request.getRSGroupName());
       }
-      checkPermission("balanceRSGroup");
-      boolean balancerRan = groupAdminServer.balanceRSGroup(request.getRSGroupName());
+      boolean balancerRan = rsGroupInfoManager.balanceRSGroup(request.getRSGroupName());
       builder.setBalanceRan(balancerRan);
       if (master.getMasterCoprocessorHost() != null) {
         master.getMasterCoprocessorHost().postBalanceRSGroup(request.getRSGroupName(), balancerRan);
@@ -326,8 +290,7 @@ class RSGroupAdminServiceImpl extends RSGroupAdminProtos.RSGroupAdminService {
       if (master.getMasterCoprocessorHost() != null) {
         master.getMasterCoprocessorHost().preListRSGroups();
       }
-      checkPermission("listRSGroup");
-      List<RSGroupInfo> rsGroupInfos = groupAdminServer.listRSGroups().stream()
+      List<RSGroupInfo> rsGroupInfos = rsGroupInfoManager.listRSGroups().stream()
           .map(RSGroupInfo::new).collect(Collectors.toList());
       Map<String, RSGroupInfo> name2Info = new HashMap<>();
       for (RSGroupInfo rsGroupInfo : rsGroupInfos) {
@@ -364,8 +327,7 @@ class RSGroupAdminServiceImpl extends RSGroupAdminProtos.RSGroupAdminService {
       if (master.getMasterCoprocessorHost() != null) {
         master.getMasterCoprocessorHost().preGetRSGroupInfoOfServer(hp);
       }
-      checkPermission("getRSGroupInfoOfServer");
-      RSGroupInfo info = groupAdminServer.getRSGroupOfServer(hp);
+      RSGroupInfo info = rsGroupInfoManager.getRSGroupOfServer(hp);
       if (info != null) {
         builder.setRSGroupInfo(ProtobufUtil.toProtoGroupInfo(fillTables(info)));
       }
@@ -397,8 +359,7 @@ class RSGroupAdminServiceImpl extends RSGroupAdminProtos.RSGroupAdminService {
         master.getMasterCoprocessorHost().preMoveServersAndTables(hostPorts, tables,
           request.getTargetGroup());
       }
-      checkPermission("moveServersAndTables");
-      groupAdminServer.moveServers(hostPorts, request.getTargetGroup());
+      rsGroupInfoManager.moveServers(hostPorts, request.getTargetGroup());
       moveTablesAndWait(tables, request.getTargetGroup());
       if (master.getMasterCoprocessorHost() != null) {
         master.getMasterCoprocessorHost().postMoveServersAndTables(hostPorts, tables,
@@ -424,8 +385,7 @@ class RSGroupAdminServiceImpl extends RSGroupAdminProtos.RSGroupAdminService {
       if (master.getMasterCoprocessorHost() != null) {
         master.getMasterCoprocessorHost().preRemoveServers(servers);
       }
-      checkPermission("removeServers");
-      groupAdminServer.removeServers(servers);
+      rsGroupInfoManager.removeServers(servers);
       if (master.getMasterCoprocessorHost() != null) {
         master.getMasterCoprocessorHost().postRemoveServers(servers);
       }
@@ -434,4 +394,5 @@ class RSGroupAdminServiceImpl extends RSGroupAdminProtos.RSGroupAdminService {
     }
     done.run(builder.build());
   }
+
 }
\ No newline at end of file
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupInfoManager.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupInfoManager.java
index 0eb15e9..c1b03a6 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupInfoManager.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupInfoManager.java
@@ -48,13 +48,8 @@ public interface RSGroupInfoManager {
 
   /**
    * Move servers to a new group.
-   * @param servers list of servers, must be part of the same group
-   * @param srcGroup groupName being moved from
-   * @param dstGroup groupName being moved to
-   * @return Set of servers moved (May be a subset of {@code servers}).
    */
-  Set<Address> moveServers(Set<Address> servers, String srcGroup, String dstGroup)
-      throws IOException;
+  void moveServers(Set<Address> servers, String targetGroupName) throws IOException;
 
   /**
    * Gets the group info of server.
@@ -85,10 +80,7 @@ public interface RSGroupInfoManager {
 
   /**
    * Get {@code RSGroupInfo} for the given table.
-   * @deprecated Since 3.0.0, will be removed in 4.0.0. Only for compatibility, where we upgrade
-   *             from a version that stores table names for a rs group in the {@code RSGroupInfo}.
    */
-  @Deprecated
   RSGroupInfo getRSGroupForTable(TableName tableName) throws IOException;
 
   static RSGroupInfoManager create(MasterServices master) throws IOException {
@@ -98,4 +90,14 @@ public interface RSGroupInfoManager {
       return new DisabledRSGroupInfoManager(master.getServerManager());
     }
   }
+
+  /**
+   * Balance a region server group.
+   */
+  boolean balanceRSGroup(String groupName) throws IOException;
+
+  /**
+   * Set group for tables.
+   */
+  void setRSGroup(Set<TableName> tables, String groupName) throws IOException;
 }
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupInfoManagerImpl.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupInfoManagerImpl.java
index ce0bd8c..5a75f68 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupInfoManagerImpl.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupInfoManagerImpl.java
@@ -24,15 +24,21 @@ import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 import java.util.OptionalLong;
 import java.util.Set;
 import java.util.SortedSet;
 import java.util.TreeSet;
+import java.util.function.Function;
 import java.util.stream.Collectors;
+import org.apache.commons.lang3.StringUtils;
 import org.apache.hadoop.hbase.Coprocessor;
 import org.apache.hadoop.hbase.DoNotRetryIOException;
+import org.apache.hadoop.hbase.HConstants;
 import org.apache.hadoop.hbase.NamespaceDescriptor;
 import org.apache.hadoop.hbase.ServerName;
 import org.apache.hadoop.hbase.TableDescriptors;
@@ -45,6 +51,7 @@ import org.apache.hadoop.hbase.client.Delete;
 import org.apache.hadoop.hbase.client.Get;
 import org.apache.hadoop.hbase.client.Mutation;
 import org.apache.hadoop.hbase.client.Put;
+import org.apache.hadoop.hbase.client.RegionInfo;
 import org.apache.hadoop.hbase.client.Result;
 import org.apache.hadoop.hbase.client.ResultScanner;
 import org.apache.hadoop.hbase.client.TableDescriptor;
@@ -52,11 +59,17 @@ import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
 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.master.LoadBalancer;
 import org.apache.hadoop.hbase.master.MasterServices;
+import org.apache.hadoop.hbase.master.RegionPlan;
+import org.apache.hadoop.hbase.master.RegionState;
 import org.apache.hadoop.hbase.master.ServerListener;
+import org.apache.hadoop.hbase.master.ServerManager;
 import org.apache.hadoop.hbase.master.TableStateManager;
+import org.apache.hadoop.hbase.master.assignment.RegionStateNode;
 import org.apache.hadoop.hbase.master.procedure.CreateTableProcedure;
 import org.apache.hadoop.hbase.master.procedure.MasterProcedureUtil;
+import org.apache.hadoop.hbase.master.procedure.ProcedureSyncWait;
 import org.apache.hadoop.hbase.net.Address;
 import org.apache.hadoop.hbase.procedure2.Procedure;
 import org.apache.hadoop.hbase.protobuf.ProtobufMagic;
@@ -83,15 +96,13 @@ import org.apache.hbase.thirdparty.com.google.common.collect.ImmutableMap;
 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;
-
 /**
  * This is an implementation of {@link RSGroupInfoManager} which makes use of an HBase table as the
  * persistence store for the group information. It also makes use of zookeeper to store group
  * information needed for bootstrapping during offline mode.
  * <h2>Concurrency</h2> RSGroup state is kept locally in Maps. There is a rsgroup name to cached
- * RSGroupInfo Map at {@link #rsGroupMap} and a Map of tables to the name of the rsgroup they belong
- * too (in {@link #tableMap}). These Maps are persisted to the hbase:rsgroup table (and cached in
- * zk) on each modification.
+ * RSGroupInfo Map at {@link #rsGroupMap}. These Maps are persisted to the hbase:rsgroup table
+ * (and cached in zk) on each modification.
  * <p/>
  * Mutations on state are synchronized but reads can continue without having to wait on an instance
  * monitor, mutations do wholesale replace of the Maps on update -- Copy-On-Write; the local Maps of
@@ -113,6 +124,18 @@ final class RSGroupInfoManagerImpl implements RSGroupInfoManager {
   static final TableName RSGROUP_TABLE_NAME =
       TableName.valueOf(NamespaceDescriptor.SYSTEM_NAMESPACE_NAME_STR, "rsgroup");
 
+  @VisibleForTesting
+  static final String KEEP_ONE_SERVER_IN_DEFAULT_ERROR_MESSAGE = "should keep at least " +
+      "one server in 'default' RSGroup.";
+
+  /** Define the config key of retries threshold when movements failed */
+  @VisibleForTesting
+  static final String FAILED_MOVE_MAX_RETRY = "hbase.rsgroup.move.max.retry";
+
+  /** Define the default number of retries */
+  @VisibleForTesting
+  static final int DEFAULT_MAX_RETRY_VALUE = 50;
+
   private static final String RS_GROUP_ZNODE = "rsgroup";
 
   @VisibleForTesting
@@ -176,7 +199,7 @@ final class RSGroupInfoManagerImpl implements RSGroupInfoManager {
   // contains list of groups that were last flushed to persistent store
   private Set<String> prevRSGroups = new HashSet<>();
 
-  private RSGroupInfoManagerImpl(MasterServices masterServices) throws IOException {
+  private RSGroupInfoManagerImpl(MasterServices masterServices) {
     this.masterServices = masterServices;
     this.watcher = masterServices.getZooKeeper();
     this.conn = masterServices.getAsyncClusterConnection();
@@ -214,8 +237,8 @@ final class RSGroupInfoManagerImpl implements RSGroupInfoManager {
     migrate();
   }
 
-  static RSGroupInfoManager getInstance(MasterServices master) throws IOException {
-    RSGroupInfoManagerImpl instance = new RSGroupInfoManagerImpl(master);
+  static RSGroupInfoManager getInstance(MasterServices masterServices) throws IOException {
+    RSGroupInfoManagerImpl instance = new RSGroupInfoManagerImpl(masterServices);
     instance.init();
     return instance;
   }
@@ -231,17 +254,17 @@ final class RSGroupInfoManagerImpl implements RSGroupInfoManager {
     Map<String, RSGroupInfo> rsGroupMap = holder.groupName2Group;
     if (rsGroupMap.get(rsGroupInfo.getName()) != null ||
       rsGroupInfo.getName().equals(RSGroupInfo.DEFAULT_GROUP)) {
-      throw new DoNotRetryIOException("Group already exists: " + rsGroupInfo.getName());
+      throw new ConstraintException("Group already exists: " + rsGroupInfo.getName());
     }
     Map<String, RSGroupInfo> newGroupMap = Maps.newHashMap(rsGroupMap);
     newGroupMap.put(rsGroupInfo.getName(), rsGroupInfo);
     flushConfig(newGroupMap);
   }
 
-  private RSGroupInfo getRSGroupInfo(final String groupName) throws DoNotRetryIOException {
-    RSGroupInfo rsGroupInfo = getRSGroup(groupName);
+  private RSGroupInfo getRSGroupInfo(final String groupName) throws ConstraintException {
+    RSGroupInfo rsGroupInfo = holder.groupName2Group.get(groupName);
     if (rsGroupInfo == null) {
-      throw new DoNotRetryIOException("RSGroup " + groupName + " does not exist");
+      throw new ConstraintException("RSGroup " + groupName + " does not exist");
     }
     return rsGroupInfo;
   }
@@ -254,7 +277,6 @@ final class RSGroupInfoManagerImpl implements RSGroupInfoManager {
       .map(ServerName::getAddress).collect(Collectors.toSet());
   }
 
-  @Override
   public synchronized Set<Address> moveServers(Set<Address> servers, String srcGroup,
       String dstGroup) throws IOException {
     RSGroupInfo src = getRSGroupInfo(srcGroup);
@@ -284,7 +306,7 @@ final class RSGroupInfoManagerImpl implements RSGroupInfoManager {
   }
 
   @Override
-  public RSGroupInfo getRSGroupOfServer(Address serverHostPort) throws IOException {
+  public RSGroupInfo getRSGroupOfServer(Address serverHostPort) {
     for (RSGroupInfo info : holder.groupName2Group.values()) {
       if (info.containsServer(serverHostPort)) {
         return info;
@@ -300,9 +322,30 @@ final class RSGroupInfoManagerImpl implements RSGroupInfoManager {
 
   @Override
   public synchronized void removeRSGroup(String groupName) throws IOException {
+    RSGroupInfo rsGroupInfo = getRSGroupInfo(groupName);
+    int serverCount = rsGroupInfo.getServers().size();
+    if (serverCount > 0) {
+      throw new ConstraintException("RSGroup " + groupName + " has " + serverCount +
+          " servers; you must remove these servers from the RSGroup before" +
+          " the RSGroup can be removed.");
+    }
+    for (TableDescriptor td : masterServices.getTableDescriptors().getAll().values()) {
+      if (td.getRegionServerGroup().map(groupName::equals).orElse(false)) {
+        throw new ConstraintException("RSGroup " + groupName + " is already referenced by " +
+            td.getTableName() + "; you must remove all the tables from the rsgroup before " +
+            "the rsgroup can be removed.");
+      }
+    }
+    for (NamespaceDescriptor ns : masterServices.getClusterSchema().getNamespaces()) {
+      String nsGroup = ns.getConfigurationValue(RSGroupInfo.NAMESPACE_DESC_PROP_GROUP);
+      if (nsGroup != null && nsGroup.equals(groupName)) {
+        throw new ConstraintException(
+            "RSGroup " + groupName + " is referenced by namespace: " + ns.getName());
+      }
+    }
     Map<String, RSGroupInfo> rsGroupMap = holder.groupName2Group;
     if (!rsGroupMap.containsKey(groupName) || groupName.equals(RSGroupInfo.DEFAULT_GROUP)) {
-      throw new DoNotRetryIOException(
+      throw new ConstraintException(
         "Group " + groupName + " does not exist or is a reserved " + "group");
     }
     Map<String, RSGroupInfo> newGroupMap = Maps.newHashMap(rsGroupMap);
@@ -322,6 +365,13 @@ final class RSGroupInfoManagerImpl implements RSGroupInfoManager {
 
   @Override
   public synchronized void removeServers(Set<Address> servers) throws IOException {
+    if (servers == null || servers.isEmpty()) {
+      throw new ConstraintException("The set of servers to remove cannot be null or empty.");
+    }
+
+    // check the set of servers
+    checkForDeadOrOnlineServers(servers);
+
     Map<String, RSGroupInfo> rsGroupInfos = new HashMap<String, RSGroupInfo>();
     for (Address el : servers) {
       RSGroupInfo rsGroupInfo = getRSGroupOfServer(el);
@@ -344,6 +394,7 @@ final class RSGroupInfoManagerImpl implements RSGroupInfoManager {
       newGroupMap.putAll(rsGroupInfos);
       flushConfig(newGroupMap);
     }
+    LOG.info("Remove decommissioned servers {} from RSGroup done", servers);
   }
 
   private List<RSGroupInfo> retrieveGroupListFromGroupTable() throws IOException {
@@ -575,7 +626,6 @@ final class RSGroupInfoManagerImpl implements RSGroupInfoManager {
     // Make changes visible after having been persisted to the source of truth
     resetRSGroupMap(newGroupMap);
     saveRSGroupMapToZK(newGroupMap);
-
     updateCacheOfRSGroups(newGroupMap.keySet());
   }
 
@@ -764,9 +814,371 @@ final class RSGroupInfoManagerImpl implements RSGroupInfoManager {
     }
   }
 
-
   @Override
   public RSGroupInfo getRSGroupForTable(TableName tableName) throws IOException {
     return holder.tableName2Group.get(tableName);
   }
+
+
+  /**
+   * Check if the set of servers are belong to dead servers list or online servers list.
+   * @param servers servers to remove
+   */
+  private void checkForDeadOrOnlineServers(Set<Address> servers) throws IOException {
+    // This uglyness is because we only have Address, not ServerName.
+    Set<Address> onlineServers = new HashSet<>();
+    List<ServerName> drainingServers = masterServices.getServerManager().getDrainingServersList();
+    for (ServerName server : masterServices.getServerManager().getOnlineServers().keySet()) {
+      // Only online but not decommissioned servers are really online
+      if (!drainingServers.contains(server)) {
+        onlineServers.add(server.getAddress());
+      }
+    }
+
+    Set<Address> deadServers = new HashSet<>();
+    for(ServerName server: masterServices.getServerManager().getDeadServers().copyServerNames()) {
+      deadServers.add(server.getAddress());
+    }
+
+    for (Address address: servers) {
+      if (onlineServers.contains(address)) {
+        throw new DoNotRetryIOException(
+            "Server " + address + " is an online server, not allowed to remove.");
+      }
+      if (deadServers.contains(address)) {
+        throw new DoNotRetryIOException(
+            "Server " + address + " is on the dead servers list,"
+                + " Maybe it will come back again, not allowed to remove.");
+      }
+    }
+  }
+
+  private void checkOnlineServersOnly(Set<Address> servers) throws IOException {
+    // This uglyness is because we only have Address, not ServerName.
+    // Online servers are keyed by ServerName.
+    Set<Address> onlineServers = new HashSet<>();
+    for(ServerName server: masterServices.getServerManager().getOnlineServers().keySet()) {
+      onlineServers.add(server.getAddress());
+    }
+    for (Address address: servers) {
+      if (!onlineServers.contains(address)) {
+        throw new DoNotRetryIOException("Server " + address +
+            " is not an online server in 'default' RSGroup.");
+      }
+    }
+  }
+
+  /**
+   * @return List of Regions associated with this <code>server</code>.
+   */
+  private List<RegionInfo> getRegions(final Address server) {
+    LinkedList<RegionInfo> regions = new LinkedList<>();
+    for (Map.Entry<RegionInfo, ServerName> el :
+        masterServices.getAssignmentManager().getRegionStates().getRegionAssignments().entrySet()) {
+      if (el.getValue() == null) {
+        continue;
+      }
+
+      if (el.getValue().getAddress().equals(server)) {
+        addRegion(regions, el.getKey());
+      }
+    }
+    for (RegionStateNode state : masterServices.getAssignmentManager().getRegionsInTransition()) {
+      if (state.getRegionLocation() != null &&
+          state.getRegionLocation().getAddress().equals(server)) {
+        addRegion(regions, state.getRegionInfo());
+      }
+    }
+    return regions;
+  }
+
+  private void addRegion(final LinkedList<RegionInfo> regions, RegionInfo hri) {
+    // If meta, move it last otherwise other unassigns fail because meta is not
+    // online for them to update state in. This is dodgy. Needs to be made more
+    // robust. See TODO below.
+    if (hri.isMetaRegion()) {
+      regions.addLast(hri);
+    } else {
+      regions.addFirst(hri);
+    }
+  }
+
+  /**
+   * Move every region from servers which are currently located on these servers, but should not be
+   * located there.
+   * @param servers the servers that will move to new group
+   * @param targetGroupName the target group name
+   * @throws IOException if moving the server and tables fail
+   */
+  private void moveServerRegionsFromGroup(Set<Address> servers, String targetGroupName)
+      throws IOException {
+    moveRegionsBetweenGroups(servers, targetGroupName, rs -> getRegions(rs), info -> {
+      try {
+        String groupName = RSGroupUtil.getRSGroupInfo(masterServices, this, info.getTable())
+            .map(RSGroupInfo::getName).orElse(RSGroupInfo.DEFAULT_GROUP);
+        return groupName.equals(targetGroupName);
+      } catch (IOException e) {
+        LOG.warn("Failed to test group for region {} and target group {}", info, targetGroupName);
+        return false;
+      }
+    }, rs -> rs.getHostname());
+  }
+
+  private <T> void moveRegionsBetweenGroups(Set<T> regionsOwners, String targetGroupName,
+      Function<T, List<RegionInfo>> getRegionsInfo, Function<RegionInfo, Boolean> validation,
+      Function<T, String> getOwnerName) throws IOException {
+    boolean hasRegionsToMove;
+    int retry = 0;
+    Set<T> allOwners = new HashSet<>(regionsOwners);
+    Set<String> failedRegions = new HashSet<>();
+    IOException toThrow = null;
+    do {
+      hasRegionsToMove = false;
+      for (Iterator<T> iter = allOwners.iterator(); iter.hasNext(); ) {
+        T owner = iter.next();
+        // Get regions that are associated with this server and filter regions by group tables.
+        for (RegionInfo region : getRegionsInfo.apply(owner)) {
+          if (!validation.apply(region)) {
+            LOG.info("Moving region {}, which do not belong to RSGroup {}",
+                region.getShortNameToLog(), targetGroupName);
+            try {
+              this.masterServices.getAssignmentManager().move(region);
+              failedRegions.remove(region.getRegionNameAsString());
+            } catch (IOException ioe) {
+              LOG.debug("Move region {} from group failed, will retry, current retry time is {}",
+                  region.getShortNameToLog(), retry, ioe);
+              toThrow = ioe;
+              failedRegions.add(region.getRegionNameAsString());
+            }
+            if (masterServices.getAssignmentManager().getRegionStates().
+                getRegionState(region).isFailedOpen()) {
+              continue;
+            }
+            hasRegionsToMove = true;
+          }
+        }
+
+        if (!hasRegionsToMove) {
+          LOG.info("No more regions to move from {} to RSGroup", getOwnerName.apply(owner));
+          iter.remove();
+        }
+      }
+
+      retry++;
+      try {
+        wait(1000);
+      } catch (InterruptedException e) {
+        LOG.warn("Sleep interrupted", e);
+        Thread.currentThread().interrupt();
+      }
+    } while (hasRegionsToMove && retry <=
+        masterServices.getConfiguration().getInt(FAILED_MOVE_MAX_RETRY, DEFAULT_MAX_RETRY_VALUE));
+
+    //has up to max retry time or there are no more regions to move
+    if (hasRegionsToMove) {
+      // print failed moved regions, for later process conveniently
+      String msg = String
+          .format("move regions for group %s failed, failed regions: %s", targetGroupName,
+              failedRegions);
+      LOG.error(msg);
+      throw new DoNotRetryIOException(
+          msg + ", just record the last failed region's cause, more details in server log",
+          toThrow);
+    }
+  }
+
+  private boolean isTableInGroup(TableName tableName, String groupName,
+      Set<TableName> tablesInGroupCache) throws IOException {
+    if (tablesInGroupCache.contains(tableName)) {
+      return true;
+    }
+    if (RSGroupUtil.getRSGroupInfo(masterServices, this, tableName)
+        .map(RSGroupInfo::getName)
+        .orElse(RSGroupInfo.DEFAULT_GROUP).equals(groupName)) {
+      tablesInGroupCache.add(tableName);
+      return true;
+    }
+    return false;
+  }
+
+  private Map<String, RegionState> rsGroupGetRegionsInTransition(String groupName)
+      throws IOException {
+    Map<String, RegionState> rit = Maps.newTreeMap();
+    Set<TableName> tablesInGroupCache = new HashSet<>();
+    for (RegionStateNode regionNode :
+        masterServices.getAssignmentManager().getRegionsInTransition()) {
+      TableName tn = regionNode.getTable();
+      if (isTableInGroup(tn, groupName, tablesInGroupCache)) {
+        rit.put(regionNode.getRegionInfo().getEncodedName(), regionNode.toRegionState());
+      }
+    }
+    return rit;
+  }
+
+  private Map<TableName, Map<ServerName, List<RegionInfo>>> getRSGroupAssignmentsByTable(
+      String groupName) throws IOException {
+    Map<TableName, Map<ServerName, List<RegionInfo>>> result = Maps.newHashMap();
+    Set<TableName> tablesInGroupCache = new HashSet<>();
+    for (Map.Entry<RegionInfo, ServerName> entry :
+        masterServices.getAssignmentManager().getRegionStates()
+        .getRegionAssignments().entrySet()) {
+      RegionInfo region = entry.getKey();
+      TableName tn = region.getTable();
+      ServerName server = entry.getValue();
+      if (isTableInGroup(tn, groupName, tablesInGroupCache)) {
+        result.computeIfAbsent(tn, k -> new HashMap<>())
+            .computeIfAbsent(server, k -> new ArrayList<>()).add(region);
+      }
+    }
+    RSGroupInfo rsGroupInfo = getRSGroupInfo(groupName);
+    for (ServerName serverName : masterServices.getServerManager().getOnlineServers().keySet()) {
+      if (rsGroupInfo.containsServer(serverName.getAddress())) {
+        for (Map<ServerName, List<RegionInfo>> map : result.values()) {
+          map.computeIfAbsent(serverName, k -> Collections.emptyList());
+        }
+      }
+    }
+
+    return result;
+  }
+
+  @Override
+  public boolean balanceRSGroup(String groupName) throws IOException {
+    ServerManager serverManager = masterServices.getServerManager();
+    LoadBalancer balancer = masterServices.getLoadBalancer();
+    getRSGroupInfo(groupName);
+
+    synchronized (balancer) {
+      // If balance not true, don't run balancer.
+      if (!masterServices.isBalancerOn()) {
+        return false;
+      }
+      // Only allow one balance run at at time.
+      Map<String, RegionState> groupRIT = rsGroupGetRegionsInTransition(groupName);
+      if (groupRIT.size() > 0) {
+        LOG.debug("Not running balancer because {} region(s) in transition: {}", groupRIT.size(),
+            StringUtils.abbreviate(masterServices.getAssignmentManager().getRegionStates()
+                    .getRegionsInTransition().toString(),
+                256));
+        return false;
+      }
+      if (serverManager.areDeadServersInProgress()) {
+        LOG.debug("Not running balancer because processing dead regionserver(s): {}",
+            serverManager.getDeadServers());
+        return false;
+      }
+
+      // We balance per group instead of per table
+      List<RegionPlan> plans = new ArrayList<>();
+      Map<TableName, Map<ServerName, List<RegionInfo>>> assignmentsByTable =
+          getRSGroupAssignmentsByTable(groupName);
+      for (Map.Entry<TableName, Map<ServerName, List<RegionInfo>>> tableMap : assignmentsByTable
+          .entrySet()) {
+        LOG.info("Creating partial plan for table {} : {}", tableMap.getKey(), tableMap.getValue());
+        List<RegionPlan> partialPlans = balancer.balanceCluster(tableMap.getValue());
+        LOG.info("Partial plan for table {} : {}", tableMap.getKey(), partialPlans);
+        if (partialPlans != null) {
+          plans.addAll(partialPlans);
+        }
+      }
+      boolean balancerRan = !plans.isEmpty();
+      if (balancerRan) {
+        LOG.info("RSGroup balance {} starting with plan count: {}", groupName, plans.size());
+        masterServices.executeRegionPlansWithThrottling(plans);
+        LOG.info("RSGroup balance " + groupName + " completed");
+      }
+      return balancerRan;
+    }
+  }
+
+  private void moveTablesAndWait(Set<TableName> tables, String targetGroup) throws IOException {
+    List<Long> procIds = new ArrayList<Long>();
+    for (TableName tableName : tables) {
+      TableDescriptor oldTd = masterServices.getTableDescriptors().get(tableName);
+      if (oldTd == null) {
+        continue;
+      }
+      TableDescriptor newTd =
+          TableDescriptorBuilder.newBuilder(oldTd).setRegionServerGroup(targetGroup).build();
+      procIds.add(masterServices.modifyTable(tableName, newTd, HConstants.NO_NONCE,
+          HConstants.NO_NONCE));
+    }
+    for (long procId : procIds) {
+      Procedure<?> proc = masterServices.getMasterProcedureExecutor().getProcedure(procId);
+      if (proc == null) {
+        continue;
+      }
+      ProcedureSyncWait.waitForProcedureToCompleteIOE(masterServices.getMasterProcedureExecutor(),
+          proc, Long.MAX_VALUE);
+    }
+  }
+
+  @Override
+  public void setRSGroup(Set<TableName> tables, String groupName) throws IOException {
+    getRSGroupInfo(groupName);
+    moveTablesAndWait(tables, groupName);
+  }
+
+  public void moveServers(Set<Address> servers, String targetGroupName) throws IOException {
+    if (servers == null) {
+      throw new ConstraintException("The list of servers to move cannot be null.");
+    }
+    if (servers.isEmpty()) {
+      // For some reason this difference between null servers and isEmpty is important distinction.
+      // TODO. Why? Stuff breaks if I equate them.
+      return;
+    }
+    if (StringUtils.isEmpty(targetGroupName)) {
+      throw new ConstraintException("RSGroup cannot be null.");
+    }
+    getRSGroupInfo(targetGroupName);
+
+    // Hold a lock on the manager instance while moving servers to prevent
+    // another writer changing our state while we are working.
+    synchronized (this) {
+      // Presume first server's source group. Later ensure all servers are from this group.
+      Address firstServer = servers.iterator().next();
+      RSGroupInfo srcGrp = getRSGroupOfServer(firstServer);
+      if (srcGrp == null) {
+        // Be careful. This exception message is tested for in TestRSGroupsBase...
+        throw new ConstraintException("Source RSGroup for server " + firstServer
+            + " does not exist.");
+      }
+
+      // Only move online servers (when moving from 'default') or servers from other
+      // groups. This prevents bogus servers from entering groups
+      if (RSGroupInfo.DEFAULT_GROUP.equals(srcGrp.getName())) {
+        if (srcGrp.getServers().size() <= servers.size()) {
+          throw new ConstraintException(KEEP_ONE_SERVER_IN_DEFAULT_ERROR_MESSAGE);
+        }
+        checkOnlineServersOnly(servers);
+      }
+      // Ensure all servers are of same rsgroup.
+      for (Address server: servers) {
+        String tmpGroup = getRSGroupOfServer(server).getName();
+        if (!tmpGroup.equals(srcGrp.getName())) {
+          throw new ConstraintException("Move server request should only come from one source " +
+              "RSGroup. Expecting only " + srcGrp.getName() + " but contains " + tmpGroup);
+        }
+      }
+      if (srcGrp.getServers().size() <= servers.size()) {
+        // check if there are still tables reference this group
+        for (TableDescriptor td : masterServices.getTableDescriptors().getAll().values()) {
+          Optional<String> optGroupName = td.getRegionServerGroup();
+          if (optGroupName.isPresent() && optGroupName.get().equals(srcGrp.getName())) {
+            throw new ConstraintException(
+                "Cannot leave a RSGroup " + srcGrp.getName() + " that contains tables('" +
+                    td.getTableName() + "' at least) without servers to host them.");
+          }
+        }
+      }
+
+      // MovedServers may be < passed in 'servers'.
+      Set<Address> movedServers = moveServers(servers, srcGrp.getName(),
+          targetGroupName);
+      moveServerRegionsFromGroup(movedServers, targetGroupName);
+      LOG.info("Move servers done: {} => {}", srcGrp.getName(), targetGroupName);
+    }
+  }
+
 }
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupMajorCompactionTTL.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupMajorCompactionTTL.java
index d1b3751..fa07108 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupMajorCompactionTTL.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupMajorCompactionTTL.java
@@ -19,11 +19,11 @@
 package org.apache.hadoop.hbase.rsgroup;
 
 import java.util.Arrays;
-
 import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.hbase.HBaseConfiguration;
 import org.apache.hadoop.hbase.HBaseInterfaceAudience;
 import org.apache.hadoop.hbase.TableName;
+import org.apache.hadoop.hbase.client.Admin;
 import org.apache.hadoop.hbase.client.Connection;
 import org.apache.hadoop.hbase.client.ConnectionFactory;
 import org.apache.hadoop.hbase.util.compaction.MajorCompactorTTL;
@@ -31,6 +31,7 @@ import org.apache.hadoop.util.ToolRunner;
 import org.apache.yetus.audience.InterfaceAudience;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+
 import org.apache.hbase.thirdparty.com.google.common.annotations.VisibleForTesting;
 import org.apache.hbase.thirdparty.org.apache.commons.cli.CommandLine;
 import org.apache.hbase.thirdparty.org.apache.commons.cli.CommandLineParser;
@@ -58,9 +59,9 @@ public class RSGroupMajorCompactionTTL extends MajorCompactorTTL {
       throws Exception {
 
     Connection conn = ConnectionFactory.createConnection(conf);
-    RSGroupAdmin rsGroupAdmin = new RSGroupAdminClient(conn);
+    Admin admin = conn.getAdmin();
 
-    RSGroupInfo rsGroupInfo = rsGroupAdmin.getRSGroupInfo(rsgroup);
+    RSGroupInfo rsGroupInfo = admin.getRSGroup(rsgroup);
     if (rsGroupInfo == null) {
       LOG.error("Invalid rsgroup specified: " + rsgroup);
       throw new IllegalArgumentException("Invalid rsgroup specified: " + rsgroup);
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupUtil.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupUtil.java
index af30049..8aef0a1 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupUtil.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupUtil.java
@@ -27,7 +27,7 @@ import org.slf4j.LoggerFactory;
  * Helper class for RSGroup implementation
  */
 @InterfaceAudience.Private
-final class RSGroupUtil {
+public final class RSGroupUtil {
 
   private static final Logger LOG = LoggerFactory.getLogger(RSGroupUtil.class);
 
@@ -38,8 +38,8 @@ final class RSGroupUtil {
    * Will try to get the rsgroup from {@link TableDescriptor} first, and then try to get the rsgroup
    * from the {@link NamespaceDescriptor}. If still not present, return empty.
    */
-  static Optional<RSGroupInfo> getRSGroupInfo(MasterServices master, RSGroupInfoManager manager,
-      TableName tableName) throws IOException {
+  public static Optional<RSGroupInfo> getRSGroupInfo(MasterServices master,
+      RSGroupInfoManager manager, TableName tableName) throws IOException {
     TableDescriptor td = master.getTableDescriptors().get(tableName);
     if (td == null) {
       return Optional.empty();
@@ -79,7 +79,7 @@ final class RSGroupUtil {
    * Fill the tables field for {@link RSGroupInfo}, for backward compatibility.
    */
   @SuppressWarnings("deprecation")
-  static RSGroupInfo fillTables(RSGroupInfo rsGroupInfo, Collection<TableDescriptor> tds) {
+  public static RSGroupInfo fillTables(RSGroupInfo rsGroupInfo, Collection<TableDescriptor> tds) {
     RSGroupInfo newRsGroupInfo = new RSGroupInfo(rsGroupInfo);
     Predicate<TableDescriptor> filter;
     if (rsGroupInfo.getName().equals(RSGroupInfo.DEFAULT_GROUP)) {
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 7719b53..b10e6c3 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
@@ -98,6 +98,7 @@ import org.apache.hadoop.hbase.io.hfile.HFile;
 import org.apache.hadoop.hbase.ipc.CoprocessorRpcUtils;
 import org.apache.hadoop.hbase.ipc.RpcServer;
 import org.apache.hadoop.hbase.master.MasterServices;
+import org.apache.hadoop.hbase.net.Address;
 import org.apache.hadoop.hbase.protobuf.ProtobufUtil;
 import org.apache.hadoop.hbase.protobuf.generated.AccessControlProtos;
 import org.apache.hadoop.hbase.protobuf.generated.AccessControlProtos.AccessControlService;
@@ -2586,4 +2587,84 @@ public class AccessController implements MasterCoprocessor, RegionCoprocessor,
       }
     }
   }
+
+  @Override
+  public void preMoveServersAndTables(ObserverContext<MasterCoprocessorEnvironment> ctx,
+      Set<Address> servers, Set<TableName> tables, String targetGroup) throws IOException {
+    accessChecker.requirePermission(getActiveUser(ctx), "moveServersAndTables",
+        null, Permission.Action.ADMIN);
+  }
+
+  @Override
+  public void preMoveServers(final ObserverContext<MasterCoprocessorEnvironment> ctx,
+      Set<Address> servers, String targetGroup) throws IOException {
+    accessChecker.requirePermission(getActiveUser(ctx), "moveServers",
+        null, Permission.Action.ADMIN);
+  }
+
+  @Override
+  public void preMoveTables(ObserverContext<MasterCoprocessorEnvironment> ctx,
+      Set<TableName> tables, String targetGroup) throws IOException {
+    accessChecker.requirePermission(getActiveUser(ctx), "moveTables",
+        null, Permission.Action.ADMIN);
+  }
+
+  @Override
+  public void preAddRSGroup(ObserverContext<MasterCoprocessorEnvironment> ctx,
+      String name) throws IOException {
+    accessChecker.requirePermission(getActiveUser(ctx), "addRSGroup",
+        null, Permission.Action.ADMIN);
+  }
+
+  @Override
+  public void preRemoveRSGroup(ObserverContext<MasterCoprocessorEnvironment> ctx,
+      String name) throws IOException {
+    accessChecker.requirePermission(getActiveUser(ctx), "removeRSGroup",
+        null, Permission.Action.ADMIN);
+  }
+
+  @Override
+  public void preBalanceRSGroup(ObserverContext<MasterCoprocessorEnvironment> ctx,
+      String groupName) throws IOException {
+    accessChecker.requirePermission(getActiveUser(ctx), "balanceRSGroup",
+        null, Permission.Action.ADMIN);
+  }
+
+  @Override
+  public void preRemoveServers(
+      ObserverContext<MasterCoprocessorEnvironment> ctx,
+      Set<Address> servers) throws IOException {
+    accessChecker.requirePermission(getActiveUser(ctx), "removeServers",
+        null, Permission.Action.ADMIN);
+  }
+
+  @Override
+  public void preGetRSGroupInfo(ObserverContext<MasterCoprocessorEnvironment> ctx,
+      String groupName) throws IOException {
+    accessChecker.requirePermission(getActiveUser(ctx), "getRSGroupInfo",
+        null, Permission.Action.ADMIN);
+  }
+
+  @Override
+  public void preGetRSGroupInfoOfTable(ObserverContext<MasterCoprocessorEnvironment> ctx,
+      TableName tableName) throws IOException {
+    accessChecker.requirePermission(getActiveUser(ctx), "getRSGroupInfoOfTable",
+        null, Permission.Action.ADMIN);
+    //todo: should add check for table existence
+  }
+
+  @Override
+  public void preListRSGroups(ObserverContext<MasterCoprocessorEnvironment> ctx)
+      throws IOException {
+    accessChecker.requirePermission(getActiveUser(ctx), "listRSGroups",
+        null, Permission.Action.ADMIN);
+  }
+
+  @Override
+  public void preGetRSGroupInfoOfServer(ObserverContext<MasterCoprocessorEnvironment> ctx,
+      Address server) throws IOException {
+    accessChecker.requirePermission(getActiveUser(ctx), "getRSGroupInfoOfServer",
+        null, Permission.Action.ADMIN);
+  }
+
 }
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/MockNoopMasterServices.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/MockNoopMasterServices.java
index 5554e51..4d80f9b 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/MockNoopMasterServices.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/MockNoopMasterServices.java
@@ -500,4 +500,9 @@ public class MockNoopMasterServices implements MasterServices {
   public RSGroupInfoManager getRSGroupInfoManager() {
     return null;
   }
+
+  @Override
+  public boolean isBalancerOn() {
+    return false;
+  }
 }
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestMigrateRSGroupInfo.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestMigrateRSGroupInfo.java
index f61e18a..122abc4 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestMigrateRSGroupInfo.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestMigrateRSGroupInfo.java
@@ -41,15 +41,18 @@ import org.apache.hadoop.hbase.protobuf.generated.RSGroupProtos;
 import org.apache.hadoop.hbase.testclassification.MediumTests;
 import org.apache.hadoop.hbase.util.Bytes;
 import org.apache.zookeeper.KeeperException;
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
+import org.junit.After;
+import org.junit.Before;
 import org.junit.ClassRule;
 import org.junit.Test;
 import org.junit.experimental.categories.Category;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
 
 /**
  * Testcase for HBASE-22819
  */
+@RunWith(Parameterized.class)
 @Category({ MediumTests.class })
 public class TestMigrateRSGroupInfo extends TestRSGroupsBase {
 
@@ -63,8 +66,8 @@ public class TestMigrateRSGroupInfo extends TestRSGroupsBase {
 
   private static byte[] FAMILY = Bytes.toBytes("family");
 
-  @BeforeClass
-  public static void setUp() throws Exception {
+  @Before
+  public void setUp() throws Exception {
     TEST_UTIL.getConfiguration().setClass(HConstants.MASTER_IMPL, HMasterForTest.class,
       HMaster.class);
     setUpTestBeforeClass();
@@ -73,8 +76,8 @@ public class TestMigrateRSGroupInfo extends TestRSGroupsBase {
     }
   }
 
-  @AfterClass
-  public static void tearDown() throws Exception {
+  @After
+  public void tearDown() throws Exception {
     tearDownAfterClass();
   }
 
@@ -90,7 +93,7 @@ public class TestMigrateRSGroupInfo extends TestRSGroupsBase {
     public TableDescriptors getTableDescriptors() {
       if (RESUME != null) {
         for (StackTraceElement element : Thread.currentThread().getStackTrace()) {
-          if (element.getClassName().contains("RSGroupInfoManagerImpl")) {
+          if (element.getMethodName().equals("migrate")) {
             try {
               RESUME.await();
             } catch (InterruptedException e) {
@@ -106,9 +109,10 @@ public class TestMigrateRSGroupInfo extends TestRSGroupsBase {
 
   @Test
   public void testMigrate() throws IOException, InterruptedException {
-    String groupName = name.getMethodName();
+    setAdmin();
+    String groupName = getNameWithoutIndex(name.getMethodName());
     addGroup(groupName, TEST_UTIL.getMiniHBaseCluster().getRegionServerThreads().size() - 1);
-    RSGroupInfo rsGroupInfo = rsGroupAdmin.getRSGroupInfo(groupName);
+    RSGroupInfo rsGroupInfo = rsGroupAdmin.getRSGroup(groupName);
     assertTrue(rsGroupInfo.getTables().isEmpty());
     for (int i = 0; i < NUM_TABLES; i++) {
       rsGroupInfo.addTable(TableName.valueOf(TABLE_NAME_PREFIX + i));
@@ -126,7 +130,7 @@ public class TestMigrateRSGroupInfo extends TestRSGroupsBase {
     // wait until we can get the rs group info for a table
     TEST_UTIL.waitFor(30000, () -> {
       try {
-        rsGroupAdmin.getRSGroupInfoOfTable(TableName.valueOf(TABLE_NAME_PREFIX + 0));
+        rsGroupAdmin.getRSGroup(TableName.valueOf(TABLE_NAME_PREFIX + 0));
         return true;
       } catch (IOException e) {
         return false;
@@ -135,7 +139,7 @@ public class TestMigrateRSGroupInfo extends TestRSGroupsBase {
     // confirm that before migrating, we could still get the correct rs group for a table.
     for (int i = 0; i < NUM_TABLES; i++) {
       RSGroupInfo info =
-        rsGroupAdmin.getRSGroupInfoOfTable(TableName.valueOf(TABLE_NAME_PREFIX + i));
+        rsGroupAdmin.getRSGroup(TableName.valueOf(TABLE_NAME_PREFIX + i));
       assertEquals(rsGroupInfo.getName(), info.getName());
       assertEquals(NUM_TABLES, info.getTables().size());
     }
@@ -171,7 +175,7 @@ public class TestMigrateRSGroupInfo extends TestRSGroupsBase {
     // make sure we could still get the correct rs group info after migration
     for (int i = 0; i < NUM_TABLES; i++) {
       RSGroupInfo info =
-        rsGroupAdmin.getRSGroupInfoOfTable(TableName.valueOf(TABLE_NAME_PREFIX + i));
+        rsGroupAdmin.getRSGroup(TableName.valueOf(TABLE_NAME_PREFIX + i));
       assertEquals(rsGroupInfo.getName(), info.getName());
       assertEquals(NUM_TABLES, info.getTables().size());
     }
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsAdmin1.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsAdmin1.java
index aff591f..614b6c4 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsAdmin1.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsAdmin1.java
@@ -42,11 +42,8 @@ import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder;
 import org.apache.hadoop.hbase.client.TableDescriptor;
 import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
 import org.apache.hadoop.hbase.constraint.ConstraintException;
-import org.apache.hadoop.hbase.master.ServerManager;
 import org.apache.hadoop.hbase.master.TableNamespaceManager;
-import org.apache.hadoop.hbase.master.snapshot.SnapshotManager;
 import org.apache.hadoop.hbase.net.Address;
-import org.apache.hadoop.hbase.quotas.QuotaUtil;
 import org.apache.hadoop.hbase.testclassification.MediumTests;
 import org.apache.hadoop.hbase.util.Bytes;
 import org.junit.After;
@@ -57,11 +54,14 @@ import org.junit.BeforeClass;
 import org.junit.ClassRule;
 import org.junit.Test;
 import org.junit.experimental.categories.Category;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import org.apache.hbase.thirdparty.com.google.common.collect.Sets;
 
+@RunWith(Parameterized.class)
 @Category({ MediumTests.class })
 public class TestRSGroupsAdmin1 extends TestRSGroupsBase {
 
@@ -112,9 +112,9 @@ public class TestRSGroupsAdmin1 extends TestRSGroupsBase {
 
   @Test
   public void testBogusArgs() throws Exception {
-    assertNull(rsGroupAdmin.getRSGroupInfoOfTable(TableName.valueOf("nonexistent")));
-    assertNull(rsGroupAdmin.getRSGroupOfServer(Address.fromParts("bogus", 123)));
-    assertNull(rsGroupAdmin.getRSGroupInfo("bogus"));
+    assertNull(rsGroupAdmin.getRSGroup(TableName.valueOf("nonexistent")));
+    assertNull(rsGroupAdmin.getRSGroup(Address.fromParts("bogus", 123)));
+    assertNull(rsGroupAdmin.getRSGroup("bogus"));
 
     try {
       rsGroupAdmin.removeRSGroup("bogus");
@@ -124,14 +124,15 @@ public class TestRSGroupsAdmin1 extends TestRSGroupsBase {
     }
 
     try {
-      rsGroupAdmin.moveTables(Sets.newHashSet(TableName.valueOf("bogustable")), "bogus");
-      fail("Expected move with bogus group to fail");
+      rsGroupAdmin.setRSGroup(Sets.newHashSet(TableName.valueOf("bogustable")), "bogus");
+      fail("Expected set table to bogus group fail");
     } catch (ConstraintException | TableNotFoundException ex) {
       // expected
     }
 
     try {
-      rsGroupAdmin.moveServers(Sets.newHashSet(Address.fromParts("bogus", 123)), "bogus");
+      rsGroupAdmin.moveToRSGroup(Sets.newHashSet(Address.fromParts("bogus", 123)),
+          "bogus");
       fail("Expected move with bogus group to fail");
     } catch (ConstraintException ex) {
       // expected
@@ -158,8 +159,8 @@ public class TestRSGroupsAdmin1 extends TestRSGroupsBase {
 
     admin.createNamespace(NamespaceDescriptor.create(nsName)
       .addConfiguration(RSGroupInfo.NAMESPACE_DESC_PROP_GROUP, groupName).build());
-    RSGroupInfo rsGroupInfo = rsGroupAdmin.getRSGroupInfo(groupName);
-    rsGroupAdmin.moveServers(rsGroupInfo.getServers(), RSGroupInfo.DEFAULT_GROUP);
+    RSGroupInfo rsGroupInfo = rsGroupAdmin.getRSGroup(groupName);
+    rsGroupAdmin.moveToRSGroup(rsGroupInfo.getServers(), RSGroupInfo.DEFAULT_GROUP);
     // test removing a referenced group
     try {
       rsGroupAdmin.removeRSGroup(groupName);
@@ -198,16 +199,20 @@ public class TestRSGroupsAdmin1 extends TestRSGroupsBase {
 
   @Test
   public void testGetRSGroupInfoCPHookCalled() throws Exception {
-    rsGroupAdmin.getRSGroupInfo(RSGroupInfo.DEFAULT_GROUP);
-    assertTrue(observer.preGetRSGroupInfoCalled);
-    assertTrue(observer.postGetRSGroupInfoCalled);
+    rsGroupAdmin.getRSGroup(RSGroupInfo.DEFAULT_GROUP);
+    if (rsGroupAdmin instanceof RSGroupAdminClient) {
+      assertTrue(observer.preGetRSGroupInfoCalled);
+      assertTrue(observer.postGetRSGroupInfoCalled);
+    }
   }
 
   @Test
   public void testGetRSGroupInfoOfTableCPHookCalled() throws Exception {
-    rsGroupAdmin.getRSGroupInfoOfTable(TableName.META_TABLE_NAME);
-    assertTrue(observer.preGetRSGroupInfoOfTableCalled);
-    assertTrue(observer.postGetRSGroupInfoOfTableCalled);
+    rsGroupAdmin.getRSGroup(TableName.META_TABLE_NAME);
+    if (rsGroupAdmin instanceof RSGroupAdminClient) {
+      assertTrue(observer.preGetRSGroupInfoOfTableCalled);
+      assertTrue(observer.postGetRSGroupInfoOfTableCalled);
+    }
   }
 
   @Test
@@ -220,9 +225,11 @@ public class TestRSGroupsAdmin1 extends TestRSGroupsBase {
   @Test
   public void testGetRSGroupInfoOfServerCPHookCalled() throws Exception {
     ServerName masterServerName = ((MiniHBaseCluster) cluster).getMaster().getServerName();
-    rsGroupAdmin.getRSGroupOfServer(masterServerName.getAddress());
-    assertTrue(observer.preGetRSGroupInfoOfServerCalled);
-    assertTrue(observer.postGetRSGroupInfoOfServerCalled);
+    rsGroupAdmin.getRSGroup(masterServerName.getAddress());
+    if (rsGroupAdmin instanceof RSGroupAdminClient) {
+      assertTrue(observer.preGetRSGroupInfoOfServerCalled);
+      assertTrue(observer.postGetRSGroupInfoOfServerCalled);
+    }
   }
 
   @Test
@@ -230,8 +237,8 @@ public class TestRSGroupsAdmin1 extends TestRSGroupsBase {
     int initNumGroups = rsGroupAdmin.listRSGroups().size();
     addGroup("bar", 3);
     TEST_UTIL.createTable(tableName, Bytes.toBytes("f"));
-    rsGroupAdmin.moveTables(Sets.newHashSet(tableName), "bar");
-    RSGroupInfo barGroup = rsGroupAdmin.getRSGroupInfo("bar");
+    rsGroupAdmin.setRSGroup(Sets.newHashSet(tableName), "bar");
+    RSGroupInfo barGroup = rsGroupAdmin.getRSGroup("bar");
     // group is not empty therefore it should fail
     try {
       rsGroupAdmin.removeRSGroup(barGroup.getName());
@@ -240,19 +247,19 @@ public class TestRSGroupsAdmin1 extends TestRSGroupsBase {
     }
     // group cannot lose all it's servers therefore it should fail
     try {
-      rsGroupAdmin.moveServers(barGroup.getServers(), RSGroupInfo.DEFAULT_GROUP);
+      rsGroupAdmin.moveToRSGroup(barGroup.getServers(), RSGroupInfo.DEFAULT_GROUP);
       fail("Expected move servers to fail");
     } catch (IOException e) {
     }
 
-    rsGroupAdmin.moveTables(barGroup.getTables(), RSGroupInfo.DEFAULT_GROUP);
+    rsGroupAdmin.setRSGroup(barGroup.getTables(), RSGroupInfo.DEFAULT_GROUP);
     try {
       rsGroupAdmin.removeRSGroup(barGroup.getName());
       fail("Expected move servers to fail");
     } catch (IOException e) {
     }
 
-    rsGroupAdmin.moveServers(barGroup.getServers(), RSGroupInfo.DEFAULT_GROUP);
+    rsGroupAdmin.moveToRSGroup(barGroup.getServers(), RSGroupInfo.DEFAULT_GROUP);
     rsGroupAdmin.removeRSGroup(barGroup.getName());
 
     Assert.assertEquals(initNumGroups, rsGroupAdmin.listRSGroups().size());
@@ -260,10 +267,12 @@ public class TestRSGroupsAdmin1 extends TestRSGroupsBase {
 
   @Test
   public void testMultiTableMove() throws Exception {
-    final TableName tableNameA = TableName.valueOf(tablePrefix + name.getMethodName() + "A");
-    final TableName tableNameB = TableName.valueOf(tablePrefix + name.getMethodName() + "B");
+    final TableName tableNameA = TableName.valueOf(tablePrefix +
+        getNameWithoutIndex(name.getMethodName()) + "A");
+    final TableName tableNameB = TableName.valueOf(tablePrefix +
+        getNameWithoutIndex(name.getMethodName()) + "B");
     final byte[] familyNameBytes = Bytes.toBytes("f");
-    String newGroupName = getGroupName(name.getMethodName());
+    String newGroupName = getGroupName(getNameWithoutIndex(name.getMethodName()));
     final RSGroupInfo newGroup = addGroup(newGroupName, 1);
 
     TEST_UTIL.createTable(tableNameA, familyNameBytes);
@@ -284,30 +293,30 @@ public class TestRSGroupsAdmin1 extends TestRSGroupsBase {
       }
     });
 
-    RSGroupInfo tableGrpA = rsGroupAdmin.getRSGroupInfoOfTable(tableNameA);
+    RSGroupInfo tableGrpA = rsGroupAdmin.getRSGroup(tableNameA);
     assertTrue(tableGrpA.getName().equals(RSGroupInfo.DEFAULT_GROUP));
 
-    RSGroupInfo tableGrpB = rsGroupAdmin.getRSGroupInfoOfTable(tableNameB);
+    RSGroupInfo tableGrpB = rsGroupAdmin.getRSGroup(tableNameB);
     assertTrue(tableGrpB.getName().equals(RSGroupInfo.DEFAULT_GROUP));
     // change table's group
     LOG.info("Moving table [" + tableNameA + "," + tableNameB + "] to " + newGroup.getName());
-    rsGroupAdmin.moveTables(Sets.newHashSet(tableNameA, tableNameB), newGroup.getName());
+    rsGroupAdmin.setRSGroup(Sets.newHashSet(tableNameA, tableNameB), newGroup.getName());
 
     // verify group change
     Assert.assertEquals(newGroup.getName(),
-      rsGroupAdmin.getRSGroupInfoOfTable(tableNameA).getName());
+      rsGroupAdmin.getRSGroup(tableNameA).getName());
 
     Assert.assertEquals(newGroup.getName(),
-      rsGroupAdmin.getRSGroupInfoOfTable(tableNameB).getName());
+      rsGroupAdmin.getRSGroup(tableNameB).getName());
 
     // verify tables' not exist in old group
     Set<TableName> DefaultTables =
-      rsGroupAdmin.getRSGroupInfo(RSGroupInfo.DEFAULT_GROUP).getTables();
+      rsGroupAdmin.getRSGroup(RSGroupInfo.DEFAULT_GROUP).getTables();
     assertFalse(DefaultTables.contains(tableNameA));
     assertFalse(DefaultTables.contains(tableNameB));
 
     // verify tables' exist in new group
-    Set<TableName> newGroupTables = rsGroupAdmin.getRSGroupInfo(newGroupName).getTables();
+    Set<TableName> newGroupTables = rsGroupAdmin.getRSGroup(newGroupName).getTables();
     assertTrue(newGroupTables.contains(tableNameA));
     assertTrue(newGroupTables.contains(tableNameB));
   }
@@ -315,7 +324,7 @@ public class TestRSGroupsAdmin1 extends TestRSGroupsBase {
   @Test
   public void testTableMoveTruncateAndDrop() throws Exception {
     final byte[] familyNameBytes = Bytes.toBytes("f");
-    String newGroupName = getGroupName(name.getMethodName());
+    String newGroupName = getGroupName(getNameWithoutIndex(name.getMethodName()));
     final RSGroupInfo newGroup = addGroup(newGroupName, 2);
 
     TEST_UTIL.createMultiRegionTable(tableName, familyNameBytes, 5);
@@ -331,16 +340,17 @@ public class TestRSGroupsAdmin1 extends TestRSGroupsBase {
       }
     });
 
-    RSGroupInfo tableGrp = rsGroupAdmin.getRSGroupInfoOfTable(tableName);
+    RSGroupInfo tableGrp = rsGroupAdmin.getRSGroup(tableName);
+    LOG.info("got table group info is {}", tableGrp);
     assertTrue(tableGrp.getName().equals(RSGroupInfo.DEFAULT_GROUP));
 
     // change table's group
     LOG.info("Moving table " + tableName + " to " + newGroup.getName());
-    rsGroupAdmin.moveTables(Sets.newHashSet(tableName), newGroup.getName());
+    rsGroupAdmin.setRSGroup(Sets.newHashSet(tableName), newGroup.getName());
 
     // verify group change
     Assert.assertEquals(newGroup.getName(),
-      rsGroupAdmin.getRSGroupInfoOfTable(tableName).getName());
+      rsGroupAdmin.getRSGroup(tableName).getName());
 
     TEST_UTIL.waitFor(WAIT_TIMEOUT, new Waiter.Predicate<Exception>() {
       @Override
@@ -361,22 +371,20 @@ public class TestRSGroupsAdmin1 extends TestRSGroupsBase {
     // test truncate
     admin.disableTable(tableName);
     admin.truncateTable(tableName, true);
-    Assert.assertEquals(1, rsGroupAdmin.getRSGroupInfo(newGroup.getName()).getTables().size());
+    Assert.assertEquals(1, rsGroupAdmin.getRSGroup(newGroup.getName()).getTables().size());
     Assert.assertEquals(tableName,
-      rsGroupAdmin.getRSGroupInfo(newGroup.getName()).getTables().first());
+      rsGroupAdmin.getRSGroup(newGroup.getName()).getTables().first());
 
     // verify removed table is removed from group
     TEST_UTIL.deleteTable(tableName);
-    Assert.assertEquals(0, rsGroupAdmin.getRSGroupInfo(newGroup.getName()).getTables().size());
+    Assert.assertEquals(0, rsGroupAdmin.getRSGroup(newGroup.getName()).getTables().size());
 
-    assertTrue(observer.preMoveTablesCalled);
-    assertTrue(observer.postMoveTablesCalled);
   }
 
   @Test
   public void testDisabledTableMove() throws Exception {
     final byte[] familyNameBytes = Bytes.toBytes("f");
-    String newGroupName = getGroupName(name.getMethodName());
+    String newGroupName = getGroupName(getNameWithoutIndex(name.getMethodName()));
     final RSGroupInfo newGroup = addGroup(newGroupName, 2);
 
     TEST_UTIL.createMultiRegionTable(tableName, familyNameBytes, 5);
@@ -391,7 +399,7 @@ public class TestRSGroupsAdmin1 extends TestRSGroupsBase {
       }
     });
 
-    RSGroupInfo tableGrp = rsGroupAdmin.getRSGroupInfoOfTable(tableName);
+    RSGroupInfo tableGrp = rsGroupAdmin.getRSGroup(tableName);
     assertTrue(tableGrp.getName().equals(RSGroupInfo.DEFAULT_GROUP));
 
     // test disable table
@@ -399,18 +407,18 @@ public class TestRSGroupsAdmin1 extends TestRSGroupsBase {
 
     // change table's group
     LOG.info("Moving table " + tableName + " to " + newGroup.getName());
-    rsGroupAdmin.moveTables(Sets.newHashSet(tableName), newGroup.getName());
+    rsGroupAdmin.setRSGroup(Sets.newHashSet(tableName), newGroup.getName());
 
     // verify group change
     Assert.assertEquals(newGroup.getName(),
-      rsGroupAdmin.getRSGroupInfoOfTable(tableName).getName());
+      rsGroupAdmin.getRSGroup(tableName).getName());
   }
 
   @Test
   public void testNonExistentTableMove() throws Exception {
-    TableName tableName = TableName.valueOf(tablePrefix + name.getMethodName());
-
-    RSGroupInfo tableGrp = rsGroupAdmin.getRSGroupInfoOfTable(tableName);
+    TableName tableName = TableName.valueOf(tablePrefix +
+        getNameWithoutIndex(name.getMethodName()));
+    RSGroupInfo tableGrp = rsGroupAdmin.getRSGroup(tableName);
     assertNull(tableGrp);
 
     // test if table exists already.
@@ -419,21 +427,22 @@ public class TestRSGroupsAdmin1 extends TestRSGroupsBase {
 
     LOG.info("Moving table " + tableName + " to " + RSGroupInfo.DEFAULT_GROUP);
     try {
-      rsGroupAdmin.moveTables(Sets.newHashSet(tableName), RSGroupInfo.DEFAULT_GROUP);
+      rsGroupAdmin.setRSGroup(Sets.newHashSet(tableName), RSGroupInfo.DEFAULT_GROUP);
       fail("Table " + tableName + " shouldn't have been successfully moved.");
     } catch (IOException ex) {
       assertTrue(ex instanceof TableNotFoundException);
     }
 
     try {
-      rsGroupAdmin.moveServersAndTables(Sets.newHashSet(Address.fromParts("bogus", 123)),
-        Sets.newHashSet(tableName), RSGroupInfo.DEFAULT_GROUP);
+      rsGroupAdmin.setRSGroup(Sets.newHashSet(tableName), RSGroupInfo.DEFAULT_GROUP);
+      rsGroupAdmin.moveToRSGroup(Sets.newHashSet(Address.fromParts("bogus", 123)),
+          RSGroupInfo.DEFAULT_GROUP);
       fail("Table " + tableName + " shouldn't have been successfully moved.");
     } catch (IOException ex) {
       assertTrue(ex instanceof TableNotFoundException);
     }
     // verify group change
-    assertNull(rsGroupAdmin.getRSGroupInfoOfTable(tableName));
+    assertNull(rsGroupAdmin.getRSGroup(tableName));
   }
 
   @Test
@@ -512,20 +521,10 @@ public class TestRSGroupsAdmin1 extends TestRSGroupsBase {
       }
     });
     SortedSet<TableName> tables
-        = rsGroupAdmin.getRSGroupInfo(RSGroupInfo.DEFAULT_GROUP).getTables();
+        = rsGroupAdmin.getRSGroup(RSGroupInfo.DEFAULT_GROUP).getTables();
     assertTrue("Table 't1' must be in 'default' rsgroup", tables.contains(tn1));
 
     // Cleanup
     TEST_UTIL.deleteTable(tn1);
   }
-
-  private void toggleQuotaCheckAndRestartMiniCluster(boolean enable) throws Exception {
-    TEST_UTIL.shutdownMiniCluster();
-    TEST_UTIL.getConfiguration().setBoolean(QuotaUtil.QUOTA_CONF_KEY, enable);
-    TEST_UTIL.startMiniCluster(NUM_SLAVES_BASE - 1);
-    TEST_UTIL.getConfiguration().setInt(ServerManager.WAIT_ON_REGIONSERVERS_MINTOSTART,
-      NUM_SLAVES_BASE - 1);
-    TEST_UTIL.getConfiguration().setBoolean(SnapshotManager.HBASE_SNAPSHOT_ENABLED, true);
-    initialize();
-  }
 }
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsAdmin2.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsAdmin2.java
index 076362e..eb808e3 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsAdmin2.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsAdmin2.java
@@ -17,7 +17,6 @@
  */
 package org.apache.hadoop.hbase.rsgroup;
 
-import static org.apache.hadoop.hbase.rsgroup.RSGroupAdminServer.DEFAULT_MAX_RETRY_VALUE;
 import static org.apache.hadoop.hbase.util.Threads.sleep;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -55,12 +54,15 @@ import org.junit.BeforeClass;
 import org.junit.ClassRule;
 import org.junit.Test;
 import org.junit.experimental.categories.Category;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import org.apache.hbase.thirdparty.com.google.common.base.Preconditions;
 import org.apache.hbase.thirdparty.com.google.common.collect.Sets;
 
+@RunWith(Parameterized.class)
 @Category({ LargeTests.class })
 public class TestRSGroupsAdmin2 extends TestRSGroupsBase {
 
@@ -128,7 +130,7 @@ public class TestRSGroupsAdmin2 extends TestRSGroupsBase {
     }
     final ServerName targetServer = tmpTargetServer;
     // move target server to group
-    rsGroupAdmin.moveServers(Sets.newHashSet(targetServer.getAddress()), newGroup.getName());
+    rsGroupAdmin.moveToRSGroup(Sets.newHashSet(targetServer.getAddress()), newGroup.getName());
     TEST_UTIL.waitFor(WAIT_TIMEOUT, new Waiter.Predicate<Exception>() {
       @Override
       public boolean evaluate() throws Exception {
@@ -162,14 +164,14 @@ public class TestRSGroupsAdmin2 extends TestRSGroupsBase {
     int initNumGroups = rsGroupAdmin.listRSGroups().size();
     RSGroupInfo appInfo = addGroup(getGroupName(name.getMethodName()), 1);
     RSGroupInfo adminInfo = addGroup(getGroupName(name.getMethodName()), 1);
-    RSGroupInfo dInfo = rsGroupAdmin.getRSGroupInfo(RSGroupInfo.DEFAULT_GROUP);
+    RSGroupInfo dInfo = rsGroupAdmin.getRSGroup(RSGroupInfo.DEFAULT_GROUP);
     Assert.assertEquals(initNumGroups + 2, rsGroupAdmin.listRSGroups().size());
     assertEquals(1, adminInfo.getServers().size());
     assertEquals(1, appInfo.getServers().size());
     assertEquals(getNumServers() - 2, dInfo.getServers().size());
-    rsGroupAdmin.moveServers(appInfo.getServers(), RSGroupInfo.DEFAULT_GROUP);
+    rsGroupAdmin.moveToRSGroup(appInfo.getServers(), RSGroupInfo.DEFAULT_GROUP);
     rsGroupAdmin.removeRSGroup(appInfo.getName());
-    rsGroupAdmin.moveServers(adminInfo.getServers(), RSGroupInfo.DEFAULT_GROUP);
+    rsGroupAdmin.moveToRSGroup(adminInfo.getServers(), RSGroupInfo.DEFAULT_GROUP);
     rsGroupAdmin.removeRSGroup(adminInfo.getName());
     Assert.assertEquals(rsGroupAdmin.listRSGroups().size(), initNumGroups);
   }
@@ -180,14 +182,14 @@ public class TestRSGroupsAdmin2 extends TestRSGroupsBase {
     addGroup("bar", 3);
     rsGroupAdmin.addRSGroup("foo");
 
-    RSGroupInfo barGroup = rsGroupAdmin.getRSGroupInfo("bar");
-    RSGroupInfo fooGroup = rsGroupAdmin.getRSGroupInfo("foo");
+    RSGroupInfo barGroup = rsGroupAdmin.getRSGroup("bar");
+    RSGroupInfo fooGroup = rsGroupAdmin.getRSGroup("foo");
     assertEquals(3, barGroup.getServers().size());
     assertEquals(0, fooGroup.getServers().size());
 
     // test fail bogus server move
     try {
-      rsGroupAdmin.moveServers(Sets.newHashSet(Address.fromString("foo:9999")), "foo");
+      rsGroupAdmin.moveToRSGroup(Sets.newHashSet(Address.fromString("foo:9999")), "foo");
       fail("Bogus servers shouldn't have been successfully moved.");
     } catch (IOException ex) {
       String exp = "Source RSGroup for server foo:9999 does not exist.";
@@ -197,34 +199,34 @@ public class TestRSGroupsAdmin2 extends TestRSGroupsBase {
 
     // test success case
     LOG.info("moving servers " + barGroup.getServers() + " to group foo");
-    rsGroupAdmin.moveServers(barGroup.getServers(), fooGroup.getName());
+    rsGroupAdmin.moveToRSGroup(barGroup.getServers(), fooGroup.getName());
 
-    barGroup = rsGroupAdmin.getRSGroupInfo("bar");
-    fooGroup = rsGroupAdmin.getRSGroupInfo("foo");
+    barGroup = rsGroupAdmin.getRSGroup("bar");
+    fooGroup = rsGroupAdmin.getRSGroup("foo");
     assertEquals(0, barGroup.getServers().size());
     assertEquals(3, fooGroup.getServers().size());
 
     LOG.info("moving servers " + fooGroup.getServers() + " to group default");
-    rsGroupAdmin.moveServers(fooGroup.getServers(), RSGroupInfo.DEFAULT_GROUP);
+    rsGroupAdmin.moveToRSGroup(fooGroup.getServers(), RSGroupInfo.DEFAULT_GROUP);
 
     TEST_UTIL.waitFor(WAIT_TIMEOUT, new Waiter.Predicate<Exception>() {
       @Override
       public boolean evaluate() throws Exception {
-        return getNumServers() == rsGroupAdmin.getRSGroupInfo(RSGroupInfo.DEFAULT_GROUP)
+        return getNumServers() == rsGroupAdmin.getRSGroup(RSGroupInfo.DEFAULT_GROUP)
           .getServers().size();
       }
     });
 
-    fooGroup = rsGroupAdmin.getRSGroupInfo("foo");
+    fooGroup = rsGroupAdmin.getRSGroup("foo");
     assertEquals(0, fooGroup.getServers().size());
 
     // test group removal
     LOG.info("Remove group " + barGroup.getName());
     rsGroupAdmin.removeRSGroup(barGroup.getName());
-    Assert.assertEquals(null, rsGroupAdmin.getRSGroupInfo(barGroup.getName()));
+    Assert.assertEquals(null, rsGroupAdmin.getRSGroup(barGroup.getName()));
     LOG.info("Remove group " + fooGroup.getName());
     rsGroupAdmin.removeRSGroup(fooGroup.getName());
-    Assert.assertEquals(null, rsGroupAdmin.getRSGroupInfo(fooGroup.getName()));
+    Assert.assertEquals(null, rsGroupAdmin.getRSGroup(fooGroup.getName()));
   }
 
   @Test
@@ -236,7 +238,7 @@ public class TestRSGroupsAdmin2 extends TestRSGroupsBase {
 
     // remove online servers
     try {
-      rsGroupAdmin.removeServers(Sets.newHashSet(targetServer.getAddress()));
+      rsGroupAdmin.removeRSGroup(Sets.newHashSet(targetServer.getAddress()));
       fail("Online servers shouldn't have been successfully removed.");
     } catch (IOException ex) {
       String exp =
@@ -267,7 +269,7 @@ public class TestRSGroupsAdmin2 extends TestRSGroupsBase {
     });
 
     try {
-      rsGroupAdmin.removeServers(Sets.newHashSet(targetServer.getAddress()));
+      rsGroupAdmin.removeRSGroup(Sets.newHashSet(targetServer.getAddress()));
       fail("Dead servers shouldn't have been successfully removed.");
     } catch (IOException ex) {
       String exp = "Server " + targetServer.getAddress() + " is on the dead servers list," +
@@ -287,8 +289,8 @@ public class TestRSGroupsAdmin2 extends TestRSGroupsBase {
     assertEquals(1, admin.listDecommissionedRegionServers().size());
 
     assertTrue(newGroup.getServers().contains(targetServer.getAddress()));
-    rsGroupAdmin.removeServers(Sets.newHashSet(targetServer.getAddress()));
-    Set<Address> newGroupServers = rsGroupAdmin.getRSGroupInfo(newGroup.getName()).getServers();
+    rsGroupAdmin.removeRSGroup(Sets.newHashSet(targetServer.getAddress()));
+    Set<Address> newGroupServers = rsGroupAdmin.getRSGroup(newGroup.getName()).getServers();
     assertFalse(newGroupServers.contains(targetServer.getAddress()));
     assertEquals(2, newGroupServers.size());
 
@@ -320,7 +322,7 @@ public class TestRSGroupsAdmin2 extends TestRSGroupsBase {
     for (ServerName server : admin.getClusterMetrics(EnumSet.of(Option.LIVE_SERVERS))
       .getLiveServerMetrics().keySet()) {
       if (!newGroup.containsServer(server.getAddress()) &&
-        !rsGroupAdmin.getRSGroupInfo("master").containsServer(server.getAddress())) {
+        !rsGroupAdmin.getRSGroup("master").containsServer(server.getAddress())) {
         targetServer = server;
         break;
       }
@@ -328,14 +330,15 @@ public class TestRSGroupsAdmin2 extends TestRSGroupsBase {
 
     LOG.debug("Print group info : " + rsGroupAdmin.listRSGroups());
     int oldDefaultGroupServerSize =
-      rsGroupAdmin.getRSGroupInfo(RSGroupInfo.DEFAULT_GROUP).getServers().size();
+      rsGroupAdmin.getRSGroup(RSGroupInfo.DEFAULT_GROUP).getServers().size();
     int oldDefaultGroupTableSize =
-      rsGroupAdmin.getRSGroupInfo(RSGroupInfo.DEFAULT_GROUP).getTables().size();
+      rsGroupAdmin.getRSGroup(RSGroupInfo.DEFAULT_GROUP).getTables().size();
 
     // test fail bogus server move
     try {
-      rsGroupAdmin.moveServersAndTables(Sets.newHashSet(Address.fromString("foo:9999")),
-        Sets.newHashSet(tableName), newGroup.getName());
+      rsGroupAdmin.moveToRSGroup(Sets.newHashSet(Address.fromString("foo:9999")),
+          newGroup.getName());
+      rsGroupAdmin.setRSGroup(Sets.newHashSet(tableName), newGroup.getName());
       fail("Bogus servers shouldn't have been successfully moved.");
     } catch (IOException ex) {
       String exp = "Source RSGroup for server foo:9999 does not exist.";
@@ -344,18 +347,19 @@ public class TestRSGroupsAdmin2 extends TestRSGroupsBase {
     }
 
     // test move when src = dst
-    rsGroupAdmin.moveServersAndTables(Sets.newHashSet(targetServer.getAddress()),
-      Sets.newHashSet(tableName), RSGroupInfo.DEFAULT_GROUP);
+    rsGroupAdmin.moveToRSGroup(Sets.newHashSet(targetServer.getAddress()),
+        RSGroupInfo.DEFAULT_GROUP);
+    rsGroupAdmin.setRSGroup(Sets.newHashSet(tableName), RSGroupInfo.DEFAULT_GROUP);
 
     // verify default group info
     Assert.assertEquals(oldDefaultGroupServerSize,
-      rsGroupAdmin.getRSGroupInfo(RSGroupInfo.DEFAULT_GROUP).getServers().size());
+      rsGroupAdmin.getRSGroup(RSGroupInfo.DEFAULT_GROUP).getServers().size());
     Assert.assertEquals(oldDefaultGroupTableSize,
-      rsGroupAdmin.getRSGroupInfo(RSGroupInfo.DEFAULT_GROUP).getTables().size());
+      rsGroupAdmin.getRSGroup(RSGroupInfo.DEFAULT_GROUP).getTables().size());
 
     // verify new group info
-    Assert.assertEquals(1, rsGroupAdmin.getRSGroupInfo(newGroup.getName()).getServers().size());
-    Assert.assertEquals(0, rsGroupAdmin.getRSGroupInfo(newGroup.getName()).getTables().size());
+    Assert.assertEquals(1, rsGroupAdmin.getRSGroup(newGroup.getName()).getServers().size());
+    Assert.assertEquals(0, rsGroupAdmin.getRSGroup(newGroup.getName()).getTables().size());
 
     // get all region to move targetServer
     List<String> regionList = getTableRegionMap().get(tableName);
@@ -381,38 +385,39 @@ public class TestRSGroupsAdmin2 extends TestRSGroupsBase {
 
     // move targetServer and table to newGroup
     LOG.info("moving server and table to newGroup");
-    rsGroupAdmin.moveServersAndTables(Sets.newHashSet(targetServer.getAddress()),
-      Sets.newHashSet(tableName), newGroup.getName());
+    rsGroupAdmin.moveToRSGroup(Sets.newHashSet(targetServer.getAddress()),
+        newGroup.getName());
+    rsGroupAdmin.setRSGroup(Sets.newHashSet(tableName), newGroup.getName());
 
     // verify group change
     Assert.assertEquals(newGroup.getName(),
-      rsGroupAdmin.getRSGroupInfoOfTable(tableName).getName());
+      rsGroupAdmin.getRSGroup(tableName).getName());
 
     // verify servers' not exist in old group
     Set<Address> defaultServers =
-      rsGroupAdmin.getRSGroupInfo(RSGroupInfo.DEFAULT_GROUP).getServers();
+      rsGroupAdmin.getRSGroup(RSGroupInfo.DEFAULT_GROUP).getServers();
     assertFalse(defaultServers.contains(targetServer.getAddress()));
 
     // verify servers' exist in new group
-    Set<Address> newGroupServers = rsGroupAdmin.getRSGroupInfo(newGroup.getName()).getServers();
+    Set<Address> newGroupServers = rsGroupAdmin.getRSGroup(newGroup.getName()).getServers();
     assertTrue(newGroupServers.contains(targetServer.getAddress()));
 
     // verify tables' not exist in old group
     Set<TableName> defaultTables =
-      rsGroupAdmin.getRSGroupInfo(RSGroupInfo.DEFAULT_GROUP).getTables();
+      rsGroupAdmin.getRSGroup(RSGroupInfo.DEFAULT_GROUP).getTables();
     assertFalse(defaultTables.contains(tableName));
 
     // verify tables' exist in new group
-    Set<TableName> newGroupTables = rsGroupAdmin.getRSGroupInfo(newGroup.getName()).getTables();
+    Set<TableName> newGroupTables = rsGroupAdmin.getRSGroup(newGroup.getName()).getTables();
     assertTrue(newGroupTables.contains(tableName));
 
     // verify that all region still assgin on targetServer
     // TODO: uncomment after we reimplement moveServersAndTables, now the implementation is
-    // moveServers first and then moveTables, so the region will be moved to other region servers.
+    // moveToRSGroup first and then moveTables, so the region will be moved to other region servers.
     // Assert.assertEquals(5, getTableServerRegionMap().get(tableName).get(targetServer).size());
 
-    assertTrue(observer.preMoveServersAndTables);
-    assertTrue(observer.postMoveServersAndTables);
+    assertTrue(observer.preMoveServersCalled);
+    assertTrue(observer.postMoveServersCalled);
   }
 
   @Test
@@ -420,17 +425,17 @@ public class TestRSGroupsAdmin2 extends TestRSGroupsBase {
     // create groups and assign servers
     rsGroupAdmin.addRSGroup("foo");
 
-    RSGroupInfo fooGroup = rsGroupAdmin.getRSGroupInfo("foo");
+    RSGroupInfo fooGroup = rsGroupAdmin.getRSGroup("foo");
     assertEquals(0, fooGroup.getServers().size());
-    RSGroupInfo defaultGroup = rsGroupAdmin.getRSGroupInfo(RSGroupInfo.DEFAULT_GROUP);
+    RSGroupInfo defaultGroup = rsGroupAdmin.getRSGroup(RSGroupInfo.DEFAULT_GROUP);
 
     // test remove all servers from default
     try {
-      rsGroupAdmin.moveServers(defaultGroup.getServers(), fooGroup.getName());
-      fail(RSGroupAdminServer.KEEP_ONE_SERVER_IN_DEFAULT_ERROR_MESSAGE);
+      rsGroupAdmin.moveToRSGroup(defaultGroup.getServers(), fooGroup.getName());
+      fail(RSGroupInfoManagerImpl.KEEP_ONE_SERVER_IN_DEFAULT_ERROR_MESSAGE);
     } catch (ConstraintException ex) {
       assertTrue(
-        ex.getMessage().contains(RSGroupAdminServer.KEEP_ONE_SERVER_IN_DEFAULT_ERROR_MESSAGE));
+        ex.getMessage().contains(RSGroupInfoManagerImpl.KEEP_ONE_SERVER_IN_DEFAULT_ERROR_MESSAGE));
     }
 
     // test success case, remove one server from default ,keep at least one server
@@ -438,35 +443,35 @@ public class TestRSGroupsAdmin2 extends TestRSGroupsBase {
       Address serverInDefaultGroup = defaultGroup.getServers().iterator().next();
       LOG.info("moving server " + serverInDefaultGroup + " from group default to group " +
         fooGroup.getName());
-      rsGroupAdmin.moveServers(Sets.newHashSet(serverInDefaultGroup), fooGroup.getName());
+      rsGroupAdmin.moveToRSGroup(Sets.newHashSet(serverInDefaultGroup), fooGroup.getName());
     }
 
-    fooGroup = rsGroupAdmin.getRSGroupInfo("foo");
+    fooGroup = rsGroupAdmin.getRSGroup("foo");
     LOG.info("moving servers " + fooGroup.getServers() + " to group default");
-    rsGroupAdmin.moveServers(fooGroup.getServers(), RSGroupInfo.DEFAULT_GROUP);
+    rsGroupAdmin.moveToRSGroup(fooGroup.getServers(), RSGroupInfo.DEFAULT_GROUP);
 
     TEST_UTIL.waitFor(WAIT_TIMEOUT, new Waiter.Predicate<Exception>() {
       @Override
       public boolean evaluate() throws Exception {
-        return getNumServers() == rsGroupAdmin.getRSGroupInfo(RSGroupInfo.DEFAULT_GROUP)
+        return getNumServers() == rsGroupAdmin.getRSGroup(RSGroupInfo.DEFAULT_GROUP)
           .getServers().size();
       }
     });
 
-    fooGroup = rsGroupAdmin.getRSGroupInfo("foo");
+    fooGroup = rsGroupAdmin.getRSGroup("foo");
     assertEquals(0, fooGroup.getServers().size());
 
     // test group removal
     LOG.info("Remove group " + fooGroup.getName());
     rsGroupAdmin.removeRSGroup(fooGroup.getName());
-    Assert.assertEquals(null, rsGroupAdmin.getRSGroupInfo(fooGroup.getName()));
+    Assert.assertEquals(null, rsGroupAdmin.getRSGroup(fooGroup.getName()));
   }
 
   @Test
   public void testFailedMoveBeforeRetryExhaustedWhenMoveServer() throws Exception {
     String groupName = getGroupName(name.getMethodName());
     rsGroupAdmin.addRSGroup(groupName);
-    final RSGroupInfo newGroup = rsGroupAdmin.getRSGroupInfo(groupName);
+    final RSGroupInfo newGroup = rsGroupAdmin.getRSGroup(groupName);
     Pair<ServerName, RegionStateNode> gotPair = createTableWithRegionSplitting(newGroup,
         10);
 
@@ -482,7 +487,7 @@ public class TestRSGroupsAdmin2 extends TestRSGroupsBase {
     Thread t2 = new Thread(() -> {
       LOG.info("thread2 start running, to move regions");
       try {
-        rsGroupAdmin.moveServers(Sets.newHashSet(movedServer.getAddress()), newGroup.getName());
+        rsGroupAdmin.moveToRSGroup(Sets.newHashSet(movedServer.getAddress()), newGroup.getName());
       } catch (IOException e) {
         LOG.error("move server error", e);
       }
@@ -512,7 +517,8 @@ public class TestRSGroupsAdmin2 extends TestRSGroupsBase {
       // wait until there is only left the region we changed state and recover its state.
       // wait time is set according to the number of max retries, all except failed regions will be
       // moved in one retry, and will sleep 1s until next retry.
-      while (System.currentTimeMillis() - current <= DEFAULT_MAX_RETRY_VALUE * 1000) {
+      while (System.currentTimeMillis() - current <=
+          RSGroupInfoManagerImpl.DEFAULT_MAX_RETRY_VALUE * 1000) {
         List<RegionInfo> regions = getRegions.apply(owner);
         LOG.debug("server table region size is:{}", regions.size());
         assert regions.size() >= 1;
@@ -600,7 +606,7 @@ public class TestRSGroupsAdmin2 extends TestRSGroupsBase {
 
   @Test
   public void testFailedMoveServersAndRepair() throws Exception{
-    // This UT calls moveServers() twice to test the idempotency of it.
+    // This UT calls moveToRSGroup() twice to test the idempotency of it.
     // The first time, movement fails because a region is made in SPLITTING state
     // which will not be moved.
     // The second time, the region state is OPEN and check if all
@@ -616,7 +622,7 @@ public class TestRSGroupsAdmin2 extends TestRSGroupsBase {
 
     // move server to newGroup and check regions
     try {
-      rsGroupAdmin.moveServers(Sets.newHashSet(srcServer.getAddress()), newGroup.getName());
+      rsGroupAdmin.moveToRSGroup(Sets.newHashSet(srcServer.getAddress()), newGroup.getName());
       fail("should get IOException when retry exhausted but there still exists failed moved "
           + "regions");
     }catch (Exception e){
@@ -632,7 +638,7 @@ public class TestRSGroupsAdmin2 extends TestRSGroupsBase {
 
     // retry move server to newGroup and check if all regions on srcServer was moved
     rsn.setState(RegionState.State.OPEN);
-    rsGroupAdmin.moveServers(Sets.newHashSet(srcServer.getAddress()), newGroup.getName());
+    rsGroupAdmin.moveToRSGroup(Sets.newHashSet(srcServer.getAddress()), newGroup.getName());
     assertEquals(master.getAssignmentManager().getRegionsOnServer(srcServer).size(), 0);
   }
 
@@ -661,8 +667,8 @@ public class TestRSGroupsAdmin2 extends TestRSGroupsBase {
 
     // move server and table to newGroup and check regions
     try {
-      rsGroupAdmin.moveServersAndTables(Sets.newHashSet(srcServer.getAddress()),
-          Sets.newHashSet(table2), newGroup.getName());
+      rsGroupAdmin.moveToRSGroup(Sets.newHashSet(srcServer.getAddress()), newGroup.getName());
+      rsGroupAdmin.setRSGroup(Sets.newHashSet(table2), newGroup.getName());
       fail("should get IOException when retry exhausted but there still exists failed moved "
           + "regions");
     }catch (Exception e){
@@ -679,8 +685,8 @@ public class TestRSGroupsAdmin2 extends TestRSGroupsBase {
     // retry moveServersAndTables to newGroup and check if all regions on srcServer belongs to
     // table2
     rsn.setState(RegionState.State.OPEN);
-    rsGroupAdmin.moveServersAndTables(Sets.newHashSet(srcServer.getAddress()),
-        Sets.newHashSet(table2), newGroup.getName());
+    rsGroupAdmin.moveToRSGroup(Sets.newHashSet(srcServer.getAddress()), newGroup.getName());
+    rsGroupAdmin.setRSGroup(Sets.newHashSet(table2), newGroup.getName());
     for(RegionInfo regionsInfo : master.getAssignmentManager().getRegionsOnServer(srcServer)){
       assertEquals(regionsInfo.getTable(), table2);
     }
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsBalance.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsBalance.java
index 8d10850..be93186 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsBalance.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsBalance.java
@@ -42,9 +42,12 @@ import org.junit.BeforeClass;
 import org.junit.ClassRule;
 import org.junit.Test;
 import org.junit.experimental.categories.Category;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+@RunWith(Parameterized.class)
 @Category({ MediumTests.class })
 public class TestRSGroupsBalance extends TestRSGroupsBase {
 
@@ -80,7 +83,8 @@ public class TestRSGroupsBalance extends TestRSGroupsBase {
     String newGroupName = getGroupName(name.getMethodName());
     addGroup(newGroupName, 3);
 
-    final TableName tableName = TableName.valueOf(tablePrefix + "_ns", name.getMethodName());
+    final TableName tableName = TableName.valueOf(tablePrefix + "_ns",
+        getNameWithoutIndex(name.getMethodName()));
     admin.createNamespace(NamespaceDescriptor.create(tableName.getNamespaceAsString())
       .addConfiguration(RSGroupInfo.NAMESPACE_DESC_PROP_GROUP, newGroupName).build());
     final TableDescriptor desc = TableDescriptorBuilder.newBuilder(tableName)
@@ -151,13 +155,12 @@ public class TestRSGroupsBalance extends TestRSGroupsBase {
 
   @Test
   public void testMisplacedRegions() throws Exception {
-    String namespace = tablePrefix + "_" + name.getMethodName();
+    String namespace = tablePrefix + "_" + getNameWithoutIndex(name.getMethodName());
     TEST_UTIL.getAdmin().createNamespace(NamespaceDescriptor.create(namespace).build());
-    final TableName tableName =
-        TableName.valueOf(namespace, tablePrefix + "_" + name.getMethodName());
-    LOG.info(name.getMethodName());
+    final TableName tableName = TableName.valueOf(namespace, tablePrefix + "_" +
+        getNameWithoutIndex(name.getMethodName()));
 
-    final RSGroupInfo rsGroupInfo = addGroup(name.getMethodName(), 1);
+    final RSGroupInfo rsGroupInfo = addGroup(getGroupName(name.getMethodName()), 1);
 
     TEST_UTIL.createMultiRegionTable(tableName, new byte[] { 'f' }, 15);
     TEST_UTIL.waitUntilAllRegionsAssigned(tableName);
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsBase.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsBase.java
index b9885be..6626fba 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsBase.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsBase.java
@@ -21,6 +21,7 @@ import static org.junit.Assert.assertTrue;
 
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.EnumSet;
 import java.util.HashSet;
 import java.util.LinkedList;
@@ -30,6 +31,7 @@ import java.util.Optional;
 import java.util.Random;
 import java.util.Set;
 import java.util.TreeMap;
+import java.util.function.Supplier;
 import java.util.regex.Pattern;
 import org.apache.hadoop.hbase.ClusterMetrics;
 import org.apache.hadoop.hbase.ClusterMetrics.Option;
@@ -54,8 +56,10 @@ import org.apache.hadoop.hbase.master.MasterCoprocessorHost;
 import org.apache.hadoop.hbase.master.ServerManager;
 import org.apache.hadoop.hbase.master.snapshot.SnapshotManager;
 import org.apache.hadoop.hbase.net.Address;
+import org.apache.hadoop.hbase.quotas.QuotaUtil;
 import org.junit.Rule;
 import org.junit.rules.TestName;
+import org.junit.runners.Parameterized;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -74,7 +78,6 @@ public abstract class TestRSGroupsBase {
   protected static HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
   protected static Admin admin;
   protected static HBaseCluster cluster;
-  protected static RSGroupAdminClient rsGroupAdmin;
   protected static HMaster master;
   protected boolean INIT = false;
   protected static RSGroupAdminEndpoint rsGroupAdminEndpoint;
@@ -89,6 +92,48 @@ public abstract class TestRSGroupsBase {
   public TestName name = new TestName();
   protected TableName tableName;
 
+  protected Admin rsGroupAdmin;
+
+  @Parameterized.Parameter
+  public Supplier<Object> getAdmin;
+
+  private static RSGroupAdminClient getRSGroupAdmin(){
+    try {
+      return new VerifyingRSGroupAdminClient(
+          new RSGroupAdminClient(TEST_UTIL.getConnection()), TEST_UTIL.getConfiguration());
+    } catch (IOException e) {
+      LOG.error("Get group admin failed", e);
+      return null;
+    }
+  }
+
+  private static Admin getAdmin(){
+    try {
+      return TEST_UTIL.getAdmin();
+    } catch (IOException e) {
+      LOG.error("Get hbase admin failed", e);
+      return null;
+    }
+  }
+
+  public static Object resetAdminConnection(Object admin) {
+    if(admin instanceof RSGroupAdminClient) {
+      return getRSGroupAdmin();
+    }else {
+      return getAdmin();
+    }
+  }
+
+  public static String getNameWithoutIndex(String name) {
+    return name.split("\\[")[0];
+  }
+
+  @Parameterized.Parameters
+  public static List<Object[]> params() {
+    return Arrays.asList(new Supplier<?>[] { TestRSGroupsBase::getRSGroupAdmin },
+        new Supplier<?>[] { TestRSGroupsBase::getAdmin });
+  }
+
   public static void setUpTestBeforeClass() throws Exception {
     TEST_UTIL.getConfiguration().setFloat(
             "hbase.master.balancer.stochastic.tableSkewCost", 6000);
@@ -107,6 +152,10 @@ public abstract class TestRSGroupsBase {
     initialize();
   }
 
+  public void setAdmin(){
+    rsGroupAdmin = (Admin) getAdmin.get();
+  }
+
   protected static void initialize() throws Exception {
     admin = TEST_UTIL.getAdmin();
     cluster = TEST_UTIL.getHBaseCluster();
@@ -121,8 +170,6 @@ public abstract class TestRSGroupsBase {
       }
     });
     admin.balancerSwitch(false, true);
-    rsGroupAdmin = new VerifyingRSGroupAdminClient(
-        new RSGroupAdminClient(TEST_UTIL.getConnection()), TEST_UTIL.getConfiguration());
     MasterCoprocessorHost host = master.getMasterCoprocessorHost();
     observer = (CPMasterObserver) host.findCoprocessor(CPMasterObserver.class.getName());
     rsGroupAdminEndpoint = (RSGroupAdminEndpoint)
@@ -134,8 +181,9 @@ public abstract class TestRSGroupsBase {
   }
 
   public void setUpBeforeMethod() throws Exception {
+    setAdmin();
     LOG.info(name.getMethodName());
-    tableName = TableName.valueOf(tablePrefix + "_" + name.getMethodName());
+    tableName = TableName.valueOf(tablePrefix + "_" + name.getMethodName().split("\\[")[0]);
     if (!INIT) {
       INIT = true;
       tearDownAfterMethod();
@@ -164,7 +212,8 @@ public abstract class TestRSGroupsBase {
         ((MiniHBaseCluster)cluster).getMaster().getServerName();
 
     try {
-      rsGroupAdmin.moveServers(Sets.newHashSet(masterServerName.getAddress()), "master");
+      rsGroupAdmin.moveToRSGroup(Sets.newHashSet(masterServerName.getAddress()),
+          "master");
     } catch (Exception ex) {
       LOG.warn("Got this on setup, FYI", ex);
     }
@@ -176,7 +225,7 @@ public abstract class TestRSGroupsBase {
         //Might be greater since moving servers back to default
         //is after starting a server
 
-        return rsGroupAdmin.getRSGroupInfo(RSGroupInfo.DEFAULT_GROUP).getServers().size()
+        return rsGroupAdmin.getRSGroup(RSGroupInfo.DEFAULT_GROUP).getServers().size()
             == NUM_SLAVES_BASE;
       }
     });
@@ -184,7 +233,7 @@ public abstract class TestRSGroupsBase {
 
   protected RSGroupInfo addGroup(String groupName, int serverCount)
       throws IOException, InterruptedException {
-    RSGroupInfo defaultInfo = rsGroupAdmin.getRSGroupInfo(RSGroupInfo.DEFAULT_GROUP);
+    RSGroupInfo defaultInfo = rsGroupAdmin.getRSGroup(RSGroupInfo.DEFAULT_GROUP);
     rsGroupAdmin.addRSGroup(groupName);
     Set<Address> set = new HashSet<>();
     for (Address server : defaultInfo.getServers()) {
@@ -193,15 +242,15 @@ public abstract class TestRSGroupsBase {
       }
       set.add(server);
     }
-    rsGroupAdmin.moveServers(set, groupName);
-    RSGroupInfo result = rsGroupAdmin.getRSGroupInfo(groupName);
+    rsGroupAdmin.moveToRSGroup(set, groupName);
+    RSGroupInfo result = rsGroupAdmin.getRSGroup(groupName);
     return result;
   }
 
-  protected void removeGroup(String groupName) throws IOException {
-    RSGroupInfo groupInfo = rsGroupAdmin.getRSGroupInfo(groupName);
-    rsGroupAdmin.moveTables(groupInfo.getTables(), RSGroupInfo.DEFAULT_GROUP);
-    rsGroupAdmin.moveServers(groupInfo.getServers(), RSGroupInfo.DEFAULT_GROUP);
+  public void removeGroup(String groupName) throws IOException {
+    RSGroupInfo groupInfo = rsGroupAdmin.getRSGroup(groupName);
+    rsGroupAdmin.setRSGroup(groupInfo.getTables(), RSGroupInfo.DEFAULT_GROUP);
+    rsGroupAdmin.moveToRSGroup(groupInfo.getServers(), RSGroupInfo.DEFAULT_GROUP);
     rsGroupAdmin.removeRSGroup(groupName);
   }
 
@@ -224,7 +273,7 @@ public abstract class TestRSGroupsBase {
     RSGroupAdminClient groupAdmin = new RSGroupAdminClient(TEST_UTIL.getConnection());
     for(RSGroupInfo group: groupAdmin.listRSGroups()) {
       if(!group.getName().equals(RSGroupInfo.DEFAULT_GROUP)) {
-        groupAdmin.moveTables(group.getTables(), RSGroupInfo.DEFAULT_GROUP);
+        groupAdmin.setRSGroup(group.getTables(), RSGroupInfo.DEFAULT_GROUP);
         groupAdmin.moveServers(group.getServers(), RSGroupInfo.DEFAULT_GROUP);
         groupAdmin.removeRSGroup(group.getName());
       }
@@ -276,8 +325,9 @@ public abstract class TestRSGroupsBase {
     return count;
   }
 
-  protected String getGroupName(String baseName) {
-    return groupPrefix + "_" + baseName + "_" + rand.nextInt(Integer.MAX_VALUE);
+  public String getGroupName(String baseName) {
+    return groupPrefix + "_" + getNameWithoutIndex(baseName) + "_" +
+        rand.nextInt(Integer.MAX_VALUE);
   }
 
   /**
@@ -290,6 +340,17 @@ public abstract class TestRSGroupsBase {
       .findFirst().get();
   }
 
+  protected void toggleQuotaCheckAndRestartMiniCluster(boolean enable) throws Exception {
+    TEST_UTIL.shutdownMiniCluster();
+    TEST_UTIL.getConfiguration().setBoolean(QuotaUtil.QUOTA_CONF_KEY, enable);
+    TEST_UTIL.startMiniCluster(NUM_SLAVES_BASE - 1);
+    TEST_UTIL.getConfiguration().setInt(ServerManager.WAIT_ON_REGIONSERVERS_MINTOSTART,
+        NUM_SLAVES_BASE - 1);
+    TEST_UTIL.getConfiguration().setBoolean(SnapshotManager.HBASE_SNAPSHOT_ENABLED, true);
+    initialize();
+    rsGroupAdmin = (Admin) resetAdminConnection(rsGroupAdmin);
+  }
+
   public static class CPMasterObserver implements MasterCoprocessor, MasterObserver {
     boolean preBalanceRSGroupCalled = false;
     boolean postBalanceRSGroupCalled = false;
@@ -313,6 +374,8 @@ public abstract class TestRSGroupsBase {
     boolean postListRSGroupsCalled = false;
     boolean preGetRSGroupInfoOfServerCalled = false;
     boolean postGetRSGroupInfoOfServerCalled = false;
+    boolean preSetRSGroupForTablesCalled = false;
+    boolean postSetRSGroupForTablesCalled = false;
 
     public void resetFlags() {
       preBalanceRSGroupCalled = false;
@@ -337,6 +400,8 @@ public abstract class TestRSGroupsBase {
       postListRSGroupsCalled = false;
       preGetRSGroupInfoOfServerCalled = false;
       postGetRSGroupInfoOfServerCalled = false;
+      preSetRSGroupForTablesCalled = false;
+      postSetRSGroupForTablesCalled = false;
     }
 
     @Override
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsBasics.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsBasics.java
index 79aaab8..bd1db52 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsBasics.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsBasics.java
@@ -34,7 +34,6 @@ import org.apache.hadoop.hbase.client.TableDescriptor;
 import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
 import org.apache.hadoop.hbase.net.Address;
 import org.apache.hadoop.hbase.quotas.QuotaTableUtil;
-import org.apache.hadoop.hbase.quotas.QuotaUtil;
 import org.apache.hadoop.hbase.testclassification.MediumTests;
 import org.apache.hadoop.hbase.util.Bytes;
 import org.junit.After;
@@ -45,11 +44,14 @@ import org.junit.BeforeClass;
 import org.junit.ClassRule;
 import org.junit.Test;
 import org.junit.experimental.categories.Category;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import org.apache.hbase.thirdparty.com.google.common.collect.Lists;
 
+@RunWith(Parameterized.class)
 @Category({ MediumTests.class })
 public class TestRSGroupsBasics extends TestRSGroupsBase {
 
@@ -81,10 +83,12 @@ public class TestRSGroupsBasics extends TestRSGroupsBase {
 
   @Test
   public void testBasicStartUp() throws IOException {
-    RSGroupInfo defaultInfo = rsGroupAdmin.getRSGroupInfo(RSGroupInfo.DEFAULT_GROUP);
+    RSGroupInfo defaultInfo = rsGroupAdmin.getRSGroup(RSGroupInfo.DEFAULT_GROUP);
     assertEquals(NUM_SLAVES_BASE, defaultInfo.getServers().size());
     // Assignment of meta and rsgroup regions.
     int count = master.getAssignmentManager().getRegionStates().getRegionAssignments().size();
+    LOG.info("regions assignments are" +
+        master.getAssignmentManager().getRegionStates().getRegionAssignments().toString());
     // 2 (meta and rsgroup)
     assertEquals(2, count);
   }
@@ -165,6 +169,7 @@ public class TestRSGroupsBasics extends TestRSGroupsBase {
 
     // clone
     admin.cloneSnapshot(snapshotName, clonedTableName);
+    admin.deleteSnapshot(snapshotName);
   }
 
   @Test
@@ -206,7 +211,7 @@ public class TestRSGroupsBasics extends TestRSGroupsBase {
     assertEquals(0, notClearedServers.size());
 
     // the stopped region server gets cleared and removed from the group
-    Set<Address> newGroupServers = rsGroupAdmin.getRSGroupInfo(newGroup.getName()).getServers();
+    Set<Address> newGroupServers = rsGroupAdmin.getRSGroup(newGroup.getName()).getServers();
     assertFalse(newGroupServers.contains(serverToStop.getAddress()));
     assertEquals(serverCountToMoveToNewGroup - 1 /* 1 stopped */, newGroupServers.size());
   }
@@ -240,34 +245,20 @@ public class TestRSGroupsBasics extends TestRSGroupsBase {
     });
 
     Set<Address> ServersInDeadServerGroup =
-        rsGroupAdmin.getRSGroupInfo(deadServerGroup.getName()).getServers();
+        rsGroupAdmin.getRSGroup(deadServerGroup.getName()).getServers();
     assertEquals(serverCountToMoveToDeadServerGroup, ServersInDeadServerGroup.size());
     assertTrue(ServersInDeadServerGroup.contains(serverToStop.getAddress()));
   }
 
   @Test
   public void testRSGroupsWithHBaseQuota() throws Exception {
-    TEST_UTIL.getConfiguration().setBoolean(QuotaUtil.QUOTA_CONF_KEY, true);
-    restartHBaseCluster();
-    try {
-      TEST_UTIL.waitFor(90000, new Waiter.Predicate<Exception>() {
-        @Override
-        public boolean evaluate() throws Exception {
-          return admin.isTableAvailable(QuotaTableUtil.QUOTA_TABLE_NAME);
-        }
-      });
-    } finally {
-      TEST_UTIL.getConfiguration().setBoolean(QuotaUtil.QUOTA_CONF_KEY, false);
-      restartHBaseCluster();
-    }
-  }
-
-  private void restartHBaseCluster() throws Exception {
-    LOG.info("\n\nShutting down cluster");
-    TEST_UTIL.shutdownMiniHBaseCluster();
-    LOG.info("\n\nSleeping a bit");
-    Thread.sleep(2000);
-    TEST_UTIL.restartHBaseCluster(NUM_SLAVES_BASE - 1);
-    initialize();
+    toggleQuotaCheckAndRestartMiniCluster(true);
+    TEST_UTIL.waitFor(90000, new Waiter.Predicate<Exception>() {
+      @Override
+      public boolean evaluate() throws Exception {
+        return admin.isTableAvailable(QuotaTableUtil.QUOTA_TABLE_NAME);
+      }
+    });
+    toggleQuotaCheckAndRestartMiniCluster(false);
   }
 }
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsKillRS.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsKillRS.java
index 6aa3931..fb9a2a9 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsKillRS.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsKillRS.java
@@ -41,7 +41,7 @@ import org.apache.hadoop.hbase.client.TableDescriptor;
 import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
 import org.apache.hadoop.hbase.master.procedure.ServerCrashProcedure;
 import org.apache.hadoop.hbase.net.Address;
-import org.apache.hadoop.hbase.testclassification.LargeTests;
+import org.apache.hadoop.hbase.testclassification.MediumTests;
 import org.apache.hadoop.hbase.util.Bytes;
 import org.apache.hadoop.hbase.util.JVMClusterUtil;
 import org.apache.hadoop.hbase.util.VersionInfo;
@@ -52,12 +52,15 @@ import org.junit.BeforeClass;
 import org.junit.ClassRule;
 import org.junit.Test;
 import org.junit.experimental.categories.Category;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import org.apache.hbase.thirdparty.com.google.common.collect.Sets;
 
-@Category({ LargeTests.class })
+@RunWith(Parameterized.class)
+@Category({ MediumTests.class })
 public class TestRSGroupsKillRS extends TestRSGroupsBase {
 
   @ClassRule
@@ -89,7 +92,8 @@ public class TestRSGroupsKillRS extends TestRSGroupsBase {
   @Test
   public void testKillRS() throws Exception {
     RSGroupInfo appInfo = addGroup("appInfo", 1);
-    final TableName tableName = TableName.valueOf(tablePrefix + "_ns", name.getMethodName());
+    final TableName tableName = TableName.valueOf(tablePrefix + "_ns",
+        getNameWithoutIndex(name.getMethodName()));
     admin.createNamespace(NamespaceDescriptor.create(tableName.getNamespaceAsString())
       .addConfiguration(RSGroupInfo.NAMESPACE_DESC_PROP_GROUP, appInfo.getName()).build());
     final TableDescriptor desc = TableDescriptorBuilder.newBuilder(tableName)
@@ -129,8 +133,8 @@ public class TestRSGroupsKillRS extends TestRSGroupsBase {
     });
     Set<Address> newServers = Sets.newHashSet();
     newServers
-      .add(rsGroupAdmin.getRSGroupInfo(RSGroupInfo.DEFAULT_GROUP).getServers().iterator().next());
-    rsGroupAdmin.moveServers(newServers, appInfo.getName());
+      .add(rsGroupAdmin.getRSGroup(RSGroupInfo.DEFAULT_GROUP).getServers().iterator().next());
+    rsGroupAdmin.moveToRSGroup(newServers, appInfo.getName());
 
     // Make sure all the table's regions get reassigned
     // disabling the table guarantees no conflicting assign/unassign (ie SSH) happens
@@ -162,12 +166,12 @@ public class TestRSGroupsKillRS extends TestRSGroupsBase {
     TEST_UTIL.loadTable(t, Bytes.toBytes("f"));
     Set<TableName> toAddTables = new HashSet<>();
     toAddTables.add(tableName);
-    rsGroupAdmin.moveTables(toAddTables, groupName);
-    assertTrue(rsGroupAdmin.getRSGroupInfo(groupName).getTables().contains(tableName));
+    rsGroupAdmin.setRSGroup(toAddTables, groupName);
+    assertTrue(rsGroupAdmin.getRSGroup(groupName).getTables().contains(tableName));
     TEST_UTIL.waitTableAvailable(tableName, 30000);
 
     // check my_group servers and table regions
-    Set<Address> servers = rsGroupAdmin.getRSGroupInfo(groupName).getServers();
+    Set<Address> servers = rsGroupAdmin.getRSGroup(groupName).getServers();
     assertEquals(2, servers.size());
     LOG.debug("group servers {}", servers);
     for (RegionInfo tr :
@@ -198,7 +202,7 @@ public class TestRSGroupsKillRS extends TestRSGroupsBase {
 
     // case 1: stop all the regionservers in my_group, and restart a regionserver in my_group,
     // and then check if all table regions are online
-    for(Address addr : rsGroupAdmin.getRSGroupInfo(groupName).getServers()) {
+    for(Address addr : rsGroupAdmin.getRSGroup(groupName).getServers()) {
       TEST_UTIL.getMiniHBaseCluster().stopRegionServer(getServerName(addr));
     }
     // better wait for a while for region reassign
@@ -225,7 +229,7 @@ public class TestRSGroupsKillRS extends TestRSGroupsBase {
     assertEquals(NUM_SLAVES_BASE - gsn.size(),
         TEST_UTIL.getMiniHBaseCluster().getLiveRegionServerThreads().size());
     ServerName newServer = master.getServerManager().getOnlineServersList().get(0);
-    rsGroupAdmin.moveServers(Sets.newHashSet(newServer.getAddress()), groupName);
+    rsGroupAdmin.moveToRSGroup(Sets.newHashSet(newServer.getAddress()), groupName);
     // wait and check if table regions are online
     TEST_UTIL.waitTableAvailable(tableName, 30000);
   }
@@ -240,14 +244,14 @@ public class TestRSGroupsKillRS extends TestRSGroupsBase {
     // move hbase:meta to meta_group
     Set<TableName> toAddTables = new HashSet<>();
     toAddTables.add(TableName.META_TABLE_NAME);
-    rsGroupAdmin.moveTables(toAddTables, groupName);
-    assertTrue(
-        rsGroupAdmin.getRSGroupInfo(groupName).getTables().contains(TableName.META_TABLE_NAME));
+    rsGroupAdmin.setRSGroup(toAddTables, groupName);
+    assertTrue(rsGroupAdmin.getRSGroup(groupName).getTables().contains(TableName.META_TABLE_NAME));
+    TEST_UTIL.waitTableAvailable(tableName, 30000);
 
     // restart the regionserver in meta_group, and lower its version
     String originVersion = "";
     Set<Address> servers = new HashSet<>();
-    for(Address addr : rsGroupAdmin.getRSGroupInfo(groupName).getServers()) {
+    for(Address addr : rsGroupAdmin.getRSGroup(groupName).getServers()) {
       servers.add(addr);
       TEST_UTIL.getMiniHBaseCluster().stopRegionServer(getServerName(addr));
       originVersion = master.getRegionServerVersion(getServerName(addr));
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsOfflineMode.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsOfflineMode.java
index 7ac1a49..cfc721f 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsOfflineMode.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsOfflineMode.java
@@ -56,7 +56,7 @@ import org.apache.hbase.thirdparty.com.google.common.collect.Sets;
 // online. In new master, RSGroupInfoManagerImpl gets the data from zk and waits for the expected
 // assignment with a timeout.
 @Category(MediumTests.class)
-public class TestRSGroupsOfflineMode {
+public class TestRSGroupsOfflineMode extends TestRSGroupsBase {
 
   @ClassRule
   public static final HBaseClassTestRule CLASS_RULE =
@@ -106,7 +106,7 @@ public class TestRSGroupsOfflineMode {
   @Test
   public void testOffline() throws Exception, InterruptedException {
     // Table should be after group table name so it gets assigned later.
-    final TableName failoverTable = TableName.valueOf(name.getMethodName());
+    final TableName failoverTable = TableName.valueOf(getNameWithoutIndex(name.getMethodName()));
     TEST_UTIL.createTable(failoverTable, Bytes.toBytes("f"));
     final HRegionServer killRS = ((MiniHBaseCluster) cluster).getRegionServer(0);
     final HRegionServer groupRS = ((MiniHBaseCluster) cluster).getRegionServer(1);
@@ -139,7 +139,7 @@ public class TestRSGroupsOfflineMode {
       }
     });
     // Move table to group and wait.
-    groupAdmin.moveTables(Sets.newHashSet(RSGroupInfoManagerImpl.RSGROUP_TABLE_NAME), newGroup);
+    groupAdmin.setRSGroup(Sets.newHashSet(RSGroupInfoManagerImpl.RSGROUP_TABLE_NAME), newGroup);
     LOG.info("Waiting for move table...");
     TEST_UTIL.waitFor(WAIT_TIMEOUT, new Waiter.Predicate<Exception>() {
       @Override
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsWithACL.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsWithACL.java
index c1c157a..06c4e4e 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsWithACL.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsWithACL.java
@@ -21,6 +21,8 @@ import static org.apache.hadoop.hbase.AuthUtil.toGroupEntry;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
 
+import java.io.IOException;
+import java.util.Optional;
 import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.hbase.HBaseClassTestRule;
 import org.apache.hadoop.hbase.HBaseTestingUtility;
@@ -31,7 +33,11 @@ import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder;
 import org.apache.hadoop.hbase.client.Connection;
 import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
 import org.apache.hadoop.hbase.coprocessor.CoprocessorHost;
+import org.apache.hadoop.hbase.ipc.RpcServer;
+import org.apache.hadoop.hbase.master.HMaster;
 import org.apache.hadoop.hbase.security.User;
+import org.apache.hadoop.hbase.security.UserProvider;
+import org.apache.hadoop.hbase.security.access.AccessChecker;
 import org.apache.hadoop.hbase.security.access.AccessControlClient;
 import org.apache.hadoop.hbase.security.access.Permission;
 import org.apache.hadoop.hbase.security.access.PermissionStorage;
@@ -92,6 +98,9 @@ public class TestRSGroupsWithACL extends SecureTestUtil {
   private static byte[] TEST_FAMILY = Bytes.toBytes("f1");
 
   private static RSGroupAdminEndpoint rsGroupAdminEndpoint;
+  private static HMaster master;
+  private static AccessChecker accessChecker;
+  private static UserProvider userProvider;
 
   @BeforeClass
   public static void setupBeforeClass() throws Exception {
@@ -131,6 +140,22 @@ public class TestRSGroupsWithACL extends SecureTestUtil {
 
     systemUserConnection = TEST_UTIL.getConnection();
     setUpTableAndUserPermissions();
+    master = TEST_UTIL.getHBaseCluster().getMaster();
+    accessChecker = master.getAccessChecker();
+    userProvider = UserProvider.instantiate(TEST_UTIL.getConfiguration());
+  }
+
+  private void checkPermission(String request) throws IOException {
+    accessChecker.requirePermission(getActiveUser(), request, null, Permission.Action.ADMIN);
+  }
+
+  private User getActiveUser() throws IOException {
+    // for non-rpc handling, fallback to system user
+    Optional<User> optionalUser = RpcServer.getRequestUser();
+    if (optionalUser.isPresent()) {
+      return optionalUser.get();
+    }
+    return userProvider.getCurrent();
   }
 
   private static void setUpTableAndUserPermissions() throws Exception {
@@ -204,7 +229,7 @@ public class TestRSGroupsWithACL extends SecureTestUtil {
   @Test
   public void testGetRSGroupInfo() throws Exception {
     AccessTestAction action = () -> {
-      rsGroupAdminEndpoint.getGroupAdminService().checkPermission("getRSGroupInfo");
+      checkPermission("getRSGroupInfo");
       return null;
     };
 
@@ -214,7 +239,7 @@ public class TestRSGroupsWithACL extends SecureTestUtil {
   @Test
   public void testGetRSGroupInfoOfTable() throws Exception {
     AccessTestAction action = () -> {
-      rsGroupAdminEndpoint.getGroupAdminService().checkPermission("getRSGroupInfoOfTable");
+      checkPermission("getRSGroupInfoOfTable");
       return null;
     };
 
@@ -224,7 +249,7 @@ public class TestRSGroupsWithACL extends SecureTestUtil {
   @Test
   public void testMoveServers() throws Exception {
     AccessTestAction action = () -> {
-      rsGroupAdminEndpoint.getGroupAdminService().checkPermission("moveServers");
+      checkPermission("moveServers");
       return null;
     };
 
@@ -234,7 +259,7 @@ public class TestRSGroupsWithACL extends SecureTestUtil {
   @Test
   public void testMoveTables() throws Exception {
     AccessTestAction action = () -> {
-      rsGroupAdminEndpoint.getGroupAdminService().checkPermission("moveTables");
+      checkPermission("moveTables");
       return null;
     };
 
@@ -244,7 +269,7 @@ public class TestRSGroupsWithACL extends SecureTestUtil {
   @Test
   public void testAddRSGroup() throws Exception {
     AccessTestAction action = () -> {
-      rsGroupAdminEndpoint.getGroupAdminService().checkPermission("addRSGroup");
+      checkPermission("addRSGroup");
       return null;
     };
 
@@ -254,7 +279,7 @@ public class TestRSGroupsWithACL extends SecureTestUtil {
   @Test
   public void testRemoveRSGroup() throws Exception {
     AccessTestAction action = () -> {
-      rsGroupAdminEndpoint.getGroupAdminService().checkPermission("removeRSGroup");
+      checkPermission("removeRSGroup");
       return null;
     };
 
@@ -264,7 +289,7 @@ public class TestRSGroupsWithACL extends SecureTestUtil {
   @Test
   public void testBalanceRSGroup() throws Exception {
     AccessTestAction action = () -> {
-      rsGroupAdminEndpoint.getGroupAdminService().checkPermission("balanceRSGroup");
+      checkPermission("balanceRSGroup");
       return null;
     };
 
@@ -274,7 +299,7 @@ public class TestRSGroupsWithACL extends SecureTestUtil {
   @Test
   public void testListRSGroup() throws Exception {
     AccessTestAction action = () -> {
-      rsGroupAdminEndpoint.getGroupAdminService().checkPermission("listRSGroup");
+      checkPermission("listRSGroup");
       return null;
     };
 
@@ -284,7 +309,7 @@ public class TestRSGroupsWithACL extends SecureTestUtil {
   @Test
   public void testGetRSGroupInfoOfServer() throws Exception {
     AccessTestAction action = () -> {
-      rsGroupAdminEndpoint.getGroupAdminService().checkPermission("getRSGroupInfoOfServer");
+      checkPermission("getRSGroupInfoOfServer");
       return null;
     };
 
@@ -294,7 +319,7 @@ public class TestRSGroupsWithACL extends SecureTestUtil {
   @Test
   public void testMoveServersAndTables() throws Exception {
     AccessTestAction action = () -> {
-      rsGroupAdminEndpoint.getGroupAdminService().checkPermission("moveServersAndTables");
+      checkPermission("moveServersAndTables");
       return null;
     };
 
@@ -304,7 +329,7 @@ public class TestRSGroupsWithACL extends SecureTestUtil {
   @Test
   public void testRemoveServers() throws Exception {
     AccessTestAction action = () -> {
-      rsGroupAdminEndpoint.getGroupAdminService().checkPermission("removeServers");
+      checkPermission("removeServers");
       return null;
     };
 
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/VerifyingRSGroupAdminClient.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/VerifyingRSGroupAdminClient.java
index 28131a9..b189697 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/VerifyingRSGroupAdminClient.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/VerifyingRSGroupAdminClient.java
@@ -73,24 +73,18 @@ public class VerifyingRSGroupAdminClient extends RSGroupAdminClient {
   }
 
   @Override
-  public RSGroupInfo getRSGroupInfo(String groupName) throws IOException {
+  public RSGroupInfo getRSGroup(String groupName) throws IOException {
     return wrapped.getRSGroupInfo(groupName);
   }
 
   @Override
-  public RSGroupInfo getRSGroupInfoOfTable(TableName tableName) throws IOException {
-    return wrapped.getRSGroupInfoOfTable(tableName);
+  public RSGroupInfo getRSGroup(TableName tableName) throws IOException {
+    return wrapped.getRSGroup(tableName);
   }
 
   @Override
-  public void moveServers(Set<Address> servers, String targetGroup) throws IOException {
-    wrapped.moveServers(servers, targetGroup);
-    verify();
-  }
-
-  @Override
-  public void moveTables(Set<TableName> tables, String targetGroup) throws IOException {
-    wrapped.moveTables(tables, targetGroup);
+  public void moveToRSGroup(Set<Address> servers, String targetGroup) throws IOException {
+    wrapped.moveToRSGroup(servers, targetGroup);
     verify();
   }
 
@@ -111,20 +105,19 @@ public class VerifyingRSGroupAdminClient extends RSGroupAdminClient {
   }
 
   @Override
-  public RSGroupInfo getRSGroupOfServer(Address hostPort) throws IOException {
-    return wrapped.getRSGroupOfServer(hostPort);
+  public RSGroupInfo getRSGroup(Address hostPort) throws IOException {
+    return wrapped.getRSGroup(hostPort);
   }
 
   @Override
-  public void moveServersAndTables(Set<Address> servers, Set<TableName> tables, String targetGroup)
-          throws IOException {
-    wrapped.moveServersAndTables(servers, tables, targetGroup);
+  public void removeRSGroup(Set<Address> servers) throws IOException {
+    wrapped.removeRSGroup(servers);
     verify();
   }
 
   @Override
-  public void removeServers(Set<Address> servers) throws IOException {
-    wrapped.removeServers(servers);
+  public void setRSGroup(Set<TableName> tables, String groupName) throws IOException{
+    wrapped.setRSGroup(tables, groupName);
     verify();
   }
 
diff --git a/hbase-thrift/src/main/java/org/apache/hadoop/hbase/thrift2/client/ThriftAdmin.java b/hbase-thrift/src/main/java/org/apache/hadoop/hbase/thrift2/client/ThriftAdmin.java
index f3fdde7..52d032e 100644
--- a/hbase-thrift/src/main/java/org/apache/hadoop/hbase/thrift2/client/ThriftAdmin.java
+++ b/hbase-thrift/src/main/java/org/apache/hadoop/hbase/thrift2/client/ThriftAdmin.java
@@ -50,12 +50,14 @@ import org.apache.hadoop.hbase.client.TableDescriptor;
 import org.apache.hadoop.hbase.client.replication.TableCFs;
 import org.apache.hadoop.hbase.client.security.SecurityCapability;
 import org.apache.hadoop.hbase.ipc.CoprocessorRpcChannel;
+import org.apache.hadoop.hbase.net.Address;
 import org.apache.hadoop.hbase.quotas.QuotaFilter;
 import org.apache.hadoop.hbase.quotas.QuotaSettings;
 import org.apache.hadoop.hbase.quotas.SpaceQuotaSnapshot;
 import org.apache.hadoop.hbase.replication.ReplicationPeerConfig;
 import org.apache.hadoop.hbase.replication.ReplicationPeerDescription;
 import org.apache.hadoop.hbase.replication.SyncReplicationState;
+import org.apache.hadoop.hbase.rsgroup.RSGroupInfo;
 import org.apache.hadoop.hbase.security.access.GetUserPermissionsRequest;
 import org.apache.hadoop.hbase.security.access.Permission;
 import org.apache.hadoop.hbase.security.access.UserPermission;
@@ -1158,6 +1160,56 @@ public class ThriftAdmin implements Admin {
   }
 
   @Override
+  public RSGroupInfo getRSGroup(String groupName) {
+    throw new NotImplementedException("getRSGroup not supported in ThriftAdmin");
+  }
+
+  @Override
+  public void moveToRSGroup(Set<Address> servers, String targetGroup) {
+    throw new NotImplementedException("moveToRSGroup not supported in ThriftAdmin");
+  }
+
+  @Override
+  public void addRSGroup(String groupName) {
+    throw new NotImplementedException("addRSGroup not supported in ThriftAdmin");
+  }
+
+  @Override
+  public void removeRSGroup(String groupName) {
+    throw new NotImplementedException("removeRSGroup not supported in ThriftAdmin");
+  }
+
+  @Override
+  public boolean balanceRSGroup(String groupName) {
+    throw new NotImplementedException("balanceRSGroup not supported in ThriftAdmin");
+  }
+
+  @Override
+  public List<RSGroupInfo> listRSGroups() {
+    throw new NotImplementedException("listRSGroups not supported in ThriftAdmin");
+  }
+
+  @Override
+  public RSGroupInfo getRSGroup(Address hostPort) {
+    throw new NotImplementedException("getRSGroup not supported in ThriftAdmin");
+  }
+
+  @Override
+  public void removeRSGroup(Set<Address> servers) {
+    throw new NotImplementedException("removeRSGroup not supported in ThriftAdmin");
+  }
+
+  @Override
+  public RSGroupInfo getRSGroup(TableName tableName) {
+    throw new NotImplementedException("getRSGroup not supported in ThriftAdmin");
+  }
+
+  @Override
+  public void setRSGroup(Set<TableName> tables, String groupName) {
+    throw new NotImplementedException("setRSGroup not supported in ThriftAdmin");
+  }
+
+  @Override
   public Future<Void> splitRegionAsync(byte[] regionName) throws IOException {
     return splitRegionAsync(regionName, null);
   }


[hbase] 01/08: HBASE-23081 Add an option to enable/disable rs group feature (#691)

Posted by zh...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

zhangduo pushed a commit to branch HBASE-22514
in repository https://gitbox.apache.org/repos/asf/hbase.git

commit 247d063f327545cf9b6e48bf1ea3ed871ca379db
Author: Duo Zhang <zh...@apache.org>
AuthorDate: Mon Oct 7 17:34:33 2019 +0800

    HBASE-23081 Add an option to enable/disable rs group feature (#691)
    
    Signed-off-by: Peter Somogyi <ps...@apache.org>
---
 .../org/apache/hadoop/hbase/master/HMaster.java    |  12 +++
 .../hbase/rsgroup/DisabledRSGroupInfoManager.java  | 109 +++++++++++++++++++++
 .../hadoop/hbase/rsgroup/RSGroupInfoManager.java   |   8 +-
 .../org/apache/hadoop/hbase/TestNamespace.java     |   3 +-
 .../hadoop/hbase/master/AbstractTestDLS.java       |   4 +-
 .../hadoop/hbase/master/TestClusterRestart.java    |   2 +-
 .../hadoop/hbase/master/TestMasterMetrics.java     |   4 +-
 .../TestMasterRestartAfterDisablingTable.java      |   4 +-
 .../hadoop/hbase/master/TestRollingRestart.java    |   2 +-
 .../hadoop/hbase/regionserver/TestRegionOpen.java  |   1 -
 .../TestRegionReplicasWithRestartScenarios.java    |   2 +-
 .../regionserver/TestRegionServerMetrics.java      |   4 +-
 .../hbase/util/TestHBaseFsckReplication.java       |   2 -
 13 files changed, 139 insertions(+), 18 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 07b9fc2..59a1c81 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
@@ -183,6 +183,7 @@ import org.apache.hadoop.hbase.replication.master.ReplicationHFileCleaner;
 import org.apache.hadoop.hbase.replication.master.ReplicationLogCleaner;
 import org.apache.hadoop.hbase.replication.master.ReplicationPeerConfigUpgrader;
 import org.apache.hadoop.hbase.replication.regionserver.ReplicationStatus;
+import org.apache.hadoop.hbase.rsgroup.RSGroupAdminEndpoint;
 import org.apache.hadoop.hbase.rsgroup.RSGroupBasedLoadBalancer;
 import org.apache.hadoop.hbase.rsgroup.RSGroupInfoManager;
 import org.apache.hadoop.hbase.security.AccessDeniedException;
@@ -784,6 +785,17 @@ public class HMaster extends HRegionServer implements MasterServices {
     this.splitOrMergeTracker = new SplitOrMergeTracker(zooKeeper, conf, this);
     this.splitOrMergeTracker.start();
 
+    // This is for backwards compatible. We do not need the CP for rs group now but if user want to
+    // load it, we need to enable rs group.
+    String[] cpClasses = conf.getStrings(MasterCoprocessorHost.MASTER_COPROCESSOR_CONF_KEY);
+    if (cpClasses != null) {
+      for (String cpClass : cpClasses) {
+        if (RSGroupAdminEndpoint.class.getName().equals(cpClass)) {
+          conf.setBoolean(RSGroupInfoManager.RS_GROUP_ENABLED, true);
+          break;
+        }
+      }
+    }
     this.rsGroupInfoManager = RSGroupInfoManager.create(this);
 
     this.replicationPeerManager = ReplicationPeerManager.create(zooKeeper, conf);
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/rsgroup/DisabledRSGroupInfoManager.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/rsgroup/DisabledRSGroupInfoManager.java
new file mode 100644
index 0000000..0762750
--- /dev/null
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/rsgroup/DisabledRSGroupInfoManager.java
@@ -0,0 +1,109 @@
+/**
+ * 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.Arrays;
+import java.util.List;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import org.apache.hadoop.hbase.DoNotRetryIOException;
+import org.apache.hadoop.hbase.ServerName;
+import org.apache.hadoop.hbase.TableName;
+import org.apache.hadoop.hbase.master.ServerManager;
+import org.apache.hadoop.hbase.net.Address;
+import org.apache.yetus.audience.InterfaceAudience;
+
+/**
+ * A dummy RSGroupInfoManager which only contains a default rs group.
+ */
+@InterfaceAudience.Private
+class DisabledRSGroupInfoManager implements RSGroupInfoManager {
+
+  private final ServerManager serverManager;
+
+  public DisabledRSGroupInfoManager(ServerManager serverManager) {
+    this.serverManager = serverManager;
+  }
+
+  @Override
+  public void start() {
+  }
+
+  @Override
+  public void addRSGroup(RSGroupInfo rsGroupInfo) throws IOException {
+    throw new DoNotRetryIOException("RSGroup is disabled");
+  }
+
+  @Override
+  public void removeRSGroup(String groupName) throws IOException {
+    throw new DoNotRetryIOException("RSGroup is disabled");
+  }
+
+  @Override
+  public Set<Address> moveServers(Set<Address> servers, String srcGroup, String dstGroup)
+    throws IOException {
+    throw new DoNotRetryIOException("RSGroup is disabled");
+  }
+
+  private SortedSet<Address> getOnlineServers() {
+    SortedSet<Address> onlineServers = new TreeSet<Address>();
+    serverManager.getOnlineServers().keySet().stream().map(ServerName::getAddress)
+      .forEach(onlineServers::add);
+    return onlineServers;
+  }
+
+  @Override
+  public RSGroupInfo getRSGroupOfServer(Address serverHostPort) throws IOException {
+    SortedSet<Address> onlineServers = getOnlineServers();
+    if (onlineServers.contains(serverHostPort)) {
+      return new RSGroupInfo(RSGroupInfo.DEFAULT_GROUP, onlineServers);
+    } else {
+      return null;
+    }
+  }
+
+  @Override
+  public RSGroupInfo getRSGroup(String groupName) throws IOException {
+    if (RSGroupInfo.DEFAULT_GROUP.equals(groupName)) {
+      return new RSGroupInfo(RSGroupInfo.DEFAULT_GROUP, getOnlineServers());
+    } else {
+      return null;
+    }
+  }
+
+  @Override
+  public List<RSGroupInfo> listRSGroups() throws IOException {
+    return Arrays.asList(new RSGroupInfo(RSGroupInfo.DEFAULT_GROUP, getOnlineServers()));
+  }
+
+  @Override
+  public boolean isOnline() {
+    return true;
+  }
+
+  @Override
+  public void removeServers(Set<Address> servers) throws IOException {
+  }
+
+  @Override
+  public RSGroupInfo getRSGroupForTable(TableName tableName) throws IOException {
+    return null;
+  }
+}
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupInfoManager.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupInfoManager.java
index a46fa4b..3f73f78 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupInfoManager.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupInfoManager.java
@@ -32,6 +32,8 @@ import org.apache.yetus.audience.InterfaceAudience;
 @InterfaceAudience.Private
 public interface RSGroupInfoManager {
 
+  static final String RS_GROUP_ENABLED = "hbase.balancer.rsgroup.enabled";
+
   void start();
 
   /**
@@ -90,6 +92,10 @@ public interface RSGroupInfoManager {
   RSGroupInfo getRSGroupForTable(TableName tableName) throws IOException;
 
   static RSGroupInfoManager create(MasterServices master) throws IOException {
-    return RSGroupInfoManagerImpl.getInstance(master);
+    if (master.getConfiguration().getBoolean(RS_GROUP_ENABLED, false)) {
+      return RSGroupInfoManagerImpl.getInstance(master);
+    } else {
+      return new DisabledRSGroupInfoManager(master.getServerManager());
+    }
   }
 }
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/TestNamespace.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/TestNamespace.java
index df161c9..9eb7d81 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/TestNamespace.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/TestNamespace.java
@@ -117,8 +117,7 @@ public class TestNamespace {
     assertEquals(2, admin.listNamespaceDescriptors().length);
 
     //verify existence of system tables
-    Set<TableName> systemTables = Sets.newHashSet(
-        TableName.META_TABLE_NAME, TableName.valueOf("hbase:rsgroup"));
+    Set<TableName> systemTables = Sets.newHashSet(TableName.META_TABLE_NAME);
     List<TableDescriptor> descs = admin.listTableDescriptorsByNamespace(
       Bytes.toBytes(NamespaceDescriptor.SYSTEM_NAMESPACE.getName()));
     assertEquals(systemTables.size(), descs.size());
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/AbstractTestDLS.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/AbstractTestDLS.java
index fc2e9f7..a576adc 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/AbstractTestDLS.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/AbstractTestDLS.java
@@ -504,14 +504,14 @@ public abstract class AbstractTestDLS {
       for (String oregion : regions)
         LOG.debug("Region still online: " + oregion);
     }
-    assertEquals(2 + existingRegions, regions.size());
+    assertEquals(1 + existingRegions, regions.size());
     LOG.debug("Enabling table\n");
     TEST_UTIL.getAdmin().enableTable(tableName);
     LOG.debug("Waiting for no more RIT\n");
     blockUntilNoRIT();
     LOG.debug("Verifying there are " + numRegions + " assigned on cluster\n");
     regions = HBaseTestingUtility.getAllOnlineRegions(cluster);
-    assertEquals(numRegions + 2 + existingRegions, regions.size());
+    assertEquals(numRegions + 1 + existingRegions, regions.size());
     return table;
   }
 
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/TestClusterRestart.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/TestClusterRestart.java
index a8d80b6..e880f97 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/TestClusterRestart.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/TestClusterRestart.java
@@ -43,7 +43,7 @@ public class TestClusterRestart extends AbstractTestRestartCluster {
 
   private static final Logger LOG = LoggerFactory.getLogger(TestClusterRestart.class);
 
-  private static final int NUM_REGIONS = 4;
+  private static final int NUM_REGIONS = 3;
 
   @Override
   protected boolean splitWALCoordinatedByZk() {
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/TestMasterMetrics.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/TestMasterMetrics.java
index 61720df..75d9ee1 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/TestMasterMetrics.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/TestMasterMetrics.java
@@ -25,7 +25,6 @@ import org.apache.hadoop.hbase.HBaseTestingUtility;
 import org.apache.hadoop.hbase.MiniHBaseCluster;
 import org.apache.hadoop.hbase.ServerName;
 import org.apache.hadoop.hbase.StartMiniClusterOption;
-import org.apache.hadoop.hbase.TableName;
 import org.apache.hadoop.hbase.test.MetricsAssertHelper;
 import org.apache.hadoop.hbase.testclassification.MasterTests;
 import org.apache.hadoop.hbase.testclassification.MediumTests;
@@ -91,7 +90,6 @@ public class TestMasterMetrics {
     cluster = TEST_UTIL.getHBaseCluster();
     LOG.info("Waiting for active/ready master");
     cluster.waitForActiveAndReadyMaster();
-    TEST_UTIL.waitTableAvailable(TableName.valueOf("hbase:rsgroup"));
     master = cluster.getMaster();
   }
 
@@ -133,7 +131,7 @@ public class TestMasterMetrics {
     MetricsMasterSource masterSource = master.getMasterMetrics().getMetricsSource();
     boolean tablesOnMaster = LoadBalancer.isTablesOnMaster(TEST_UTIL.getConfiguration());
     metricsHelper.assertGauge("numRegionServers", 1 + (tablesOnMaster ? 1 : 0), masterSource);
-    metricsHelper.assertGauge("averageLoad", 2, masterSource);
+    metricsHelper.assertGauge("averageLoad", 1, masterSource);
     metricsHelper.assertGauge("numDeadRegionServers", 0, masterSource);
 
     metricsHelper.assertGauge("masterStartTime", master.getMasterStartTime(), masterSource);
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/TestMasterRestartAfterDisablingTable.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/TestMasterRestartAfterDisablingTable.java
index 2d0af3e..fca2866 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/TestMasterRestartAfterDisablingTable.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/TestMasterRestartAfterDisablingTable.java
@@ -92,7 +92,7 @@ public class TestMasterRestartAfterDisablingTable {
 
     NavigableSet<String> regions = HBaseTestingUtility.getAllOnlineRegions(cluster);
     assertEquals("The number of regions for the table tableRestart should be 0 and only" +
-      "the catalog table should be present.", 2, regions.size());
+      "the catalog table should be present.", 1, regions.size());
 
     List<MasterThread> masterThreads = cluster.getMasterThreads();
     MasterThread activeMaster = null;
@@ -120,7 +120,7 @@ public class TestMasterRestartAfterDisablingTable {
     log("Verifying there are " + numRegions + " assigned on cluster\n");
     regions = HBaseTestingUtility.getAllOnlineRegions(cluster);
     assertEquals("The assigned regions were not onlined after master" +
-      " switch except for the catalog table.", 6, regions.size());
+      " switch except for the catalog table.", 5, regions.size());
     assertTrue("The table should be in enabled state", cluster.getMaster().getTableStateManager()
       .isTableState(TableName.valueOf(name.getMethodName()), TableState.State.ENABLED));
     ht.close();
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/TestRollingRestart.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/TestRollingRestart.java
index 2dc1b6c..0aba487 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/TestRollingRestart.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/TestRollingRestart.java
@@ -121,7 +121,7 @@ public class TestRollingRestart {
         log("Region still online: " + oregion);
       }
     }
-    assertEquals(2, regions.size());
+    assertEquals(1, regions.size());
     log("Enabling table\n");
     TEST_UTIL.getAdmin().enableTable(tableName);
     log("Waiting for no more RIT\n");
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/TestRegionOpen.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/TestRegionOpen.java
index 2503825..7d52508 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/TestRegionOpen.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/TestRegionOpen.java
@@ -86,7 +86,6 @@ public class TestRegionOpen {
     final TableName tableName = TableName.valueOf(TestRegionOpen.class.getSimpleName());
     ThreadPoolExecutor exec = getRS().getExecutorService()
         .getExecutorThreadPool(ExecutorType.RS_OPEN_PRIORITY_REGION);
-    HTU.waitTableAvailable(TableName.valueOf("hbase:rsgroup"));
     long completed = exec.getCompletedTaskCount();
 
     HTableDescriptor htd = new HTableDescriptor(tableName);
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/TestRegionReplicasWithRestartScenarios.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/TestRegionReplicasWithRestartScenarios.java
index 6211db6..d4e2507 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/TestRegionReplicasWithRestartScenarios.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/TestRegionReplicasWithRestartScenarios.java
@@ -156,7 +156,7 @@ public class TestRegionReplicasWithRestartScenarios {
     assertFalse(res);
     int totalRegions = HTU.getMiniHBaseCluster().getLiveRegionServerThreads().stream().
       mapToInt(l -> l.getRegionServer().getOnlineRegions().size()).sum();
-    assertEquals(62, totalRegions);
+    assertEquals(61, totalRegions);
   }
 
   private boolean checkDuplicates(Collection<HRegion> onlineRegions3) throws Exception {
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/TestRegionServerMetrics.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/TestRegionServerMetrics.java
index 952bcbc..bc44daa 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/TestRegionServerMetrics.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/regionserver/TestRegionServerMetrics.java
@@ -225,7 +225,7 @@ public class TestRegionServerMetrics {
 
   @Test
   public void testRegionCount() throws Exception {
-    metricsHelper.assertGauge("regionCount", TABLES_ON_MASTER ? 1 : 3, serverSource);
+    metricsHelper.assertGauge("regionCount", TABLES_ON_MASTER ? 1 : 2, serverSource);
   }
 
   @Test
@@ -335,7 +335,7 @@ public class TestRegionServerMetrics {
     TEST_UTIL.getAdmin().flush(tableName);
 
     metricsRegionServer.getRegionServerWrapper().forceRecompute();
-    assertGauge("storeCount", TABLES_ON_MASTER ? 1 : 6);
+    assertGauge("storeCount", TABLES_ON_MASTER ? 1 : 5);
     assertGauge("storeFileCount", 1);
   }
 
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/util/TestHBaseFsckReplication.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/util/TestHBaseFsckReplication.java
index 0ac07d8..316a321 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/util/TestHBaseFsckReplication.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/util/TestHBaseFsckReplication.java
@@ -24,7 +24,6 @@ import java.util.stream.Stream;
 import org.apache.hadoop.hbase.HBaseClassTestRule;
 import org.apache.hadoop.hbase.HBaseTestingUtility;
 import org.apache.hadoop.hbase.ServerName;
-import org.apache.hadoop.hbase.TableName;
 import org.apache.hadoop.hbase.replication.ReplicationPeerConfig;
 import org.apache.hadoop.hbase.replication.ReplicationPeerStorage;
 import org.apache.hadoop.hbase.replication.ReplicationQueueStorage;
@@ -53,7 +52,6 @@ public class TestHBaseFsckReplication {
   public static void setUp() throws Exception {
     UTIL.getConfiguration().setBoolean("hbase.write.hbck1.lock.file", false);
     UTIL.startMiniCluster(1);
-    UTIL.waitTableAvailable(TableName.valueOf("hbase:rsgroup"));
   }
 
   @AfterClass


[hbase] 02/08: HBASE-23232 Remove rsgroup profile from pom.xml of hbase-assembly (#779)

Posted by zh...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

zhangduo pushed a commit to branch HBASE-22514
in repository https://gitbox.apache.org/repos/asf/hbase.git

commit c53d12aa98ac46f001c0d36101db1ac614edc972
Author: Guangxu Cheng <gu...@gmail.com>
AuthorDate: Thu Oct 31 03:01:09 2019 +0800

    HBASE-23232 Remove rsgroup profile from pom.xml of hbase-assembly (#779)
---
 hbase-assembly/pom.xml | 17 -----------------
 1 file changed, 17 deletions(-)

diff --git a/hbase-assembly/pom.xml b/hbase-assembly/pom.xml
index 9096e09..f9e4431 100644
--- a/hbase-assembly/pom.xml
+++ b/hbase-assembly/pom.xml
@@ -323,21 +323,4 @@
       <type>pom</type>
     </dependency>
   </dependencies>
-  <profiles>
-    <profile>
-      <id>rsgroup</id>
-      <activation>
-        <property>
-            <name>!skip-rsgroup</name>
-        </property>
-      </activation>
-      <dependencies>
-        <dependency>
-          <groupId>org.apache.hbase</groupId>
-          <artifactId>hbase-rsgroup</artifactId>
-          <version>${project.version}</version>
-        </dependency>
-      </dependencies>
-    </profile>
-  </profiles>
 </project>


[hbase] 07/08: HBASE-23276 Add admin methods to get tables within a group (#1118)

Posted by zh...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

zhangduo pushed a commit to branch HBASE-22514
in repository https://gitbox.apache.org/repos/asf/hbase.git

commit 6db385671a7653e7cced00e172ceeeb32efb1b2e
Author: Duo Zhang <zh...@apache.org>
AuthorDate: Thu Feb 6 19:53:57 2020 +0800

    HBASE-23276 Add admin methods to get tables within a group (#1118)
    
    Signed-off-by: Guanghao Zhang <zg...@apache.org>
---
 .../java/org/apache/hadoop/hbase/client/Admin.java | 25 +++++++++
 .../hadoop/hbase/client/AdminOverAsyncAdmin.java   | 13 ++++-
 .../org/apache/hadoop/hbase/client/AsyncAdmin.java | 25 +++++++++
 .../hadoop/hbase/client/AsyncHBaseAdmin.java       | 13 ++++-
 .../hadoop/hbase/client/RawAsyncHBaseAdmin.java    | 33 +++++++++++
 .../hbase/rsgroup/IntegrationTestRSGroup.java      |  5 +-
 .../src/main/protobuf/Master.proto                 |  6 ++
 .../src/main/protobuf/RSGroupAdmin.proto           | 17 ++++++
 .../hadoop/hbase/coprocessor/MasterObserver.java   | 34 ++++++++++++
 .../hadoop/hbase/master/MasterCoprocessorHost.java | 42 ++++++++++++++
 .../hadoop/hbase/master/MasterRpcServices.java     | 64 ++++++++++++++++++++++
 .../hbase/security/access/AccessController.java    | 14 +++++
 .../hbase/rsgroup/TestMigrateRSGroupInfo.java      | 20 +++++--
 .../hadoop/hbase/rsgroup/TestRSGroupsAdmin1.java   | 30 +++++-----
 .../hadoop/hbase/rsgroup/TestRSGroupsAdmin2.java   | 18 +++---
 .../hadoop/hbase/rsgroup/TestRSGroupsBase.java     | 59 ++++++++++++++++----
 .../hadoop/hbase/rsgroup/TestRSGroupsKillRS.java   |  5 +-
 .../hbase/rsgroup/VerifyingRSGroupAdmin.java       | 12 ++++
 .../hadoop/hbase/thrift2/client/ThriftAdmin.java   | 12 ++++
 19 files changed, 401 insertions(+), 46 deletions(-)

diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/Admin.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/Admin.java
index 184d588..b7381cc 100644
--- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/Admin.java
+++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/Admin.java
@@ -63,6 +63,7 @@ import org.apache.hadoop.hbase.snapshot.RestoreSnapshotException;
 import org.apache.hadoop.hbase.snapshot.SnapshotCreationException;
 import org.apache.hadoop.hbase.snapshot.UnknownSnapshotException;
 import org.apache.hadoop.hbase.util.Bytes;
+import org.apache.hadoop.hbase.util.Pair;
 import org.apache.yetus.audience.InterfaceAudience;
 
 /**
@@ -2300,6 +2301,30 @@ public interface Admin extends Abortable, Closeable {
   List<RSGroupInfo> listRSGroups() throws IOException;
 
   /**
+   * Get all tables in this RegionServer group.
+   * @param groupName the group name
+   * @throws IOException if a remote or network exception occurs
+   * @see #getConfiguredNamespacesAndTablesInRSGroup(String)
+   */
+  List<TableName> listTablesInRSGroup(String groupName) throws IOException;
+
+  /**
+   * Get the namespaces and tables which have this RegionServer group in descriptor.
+   * <p/>
+   * The difference between this method and {@link #listTablesInRSGroup(String)} is that, this
+   * method will not include the table which is actually in this RegionServr group but without the
+   * RegionServer group configuration in its {@link TableDescriptor}. For example, we have a group
+   * 'A', and we make namespace 'nsA' in this group, then all the tables under this namespace will
+   * in the group 'A', but this method will not return these tables but only the namespace 'nsA',
+   * while the {@link #listTablesInRSGroup(String)} will return all these tables.
+   * @param groupName the group name
+   * @throws IOException if a remote or network exception occurs
+   * @see #listTablesInRSGroup(String)
+   */
+  Pair<List<String>, List<TableName>> getConfiguredNamespacesAndTablesInRSGroup(String groupName)
+    throws IOException;
+
+  /**
    * Remove RegionServer group associated with the given name
    * @param groupName the group name
    * @throws IOException if a remote or network exception occurs
diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AdminOverAsyncAdmin.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AdminOverAsyncAdmin.java
index 9ebc593..c28eeb4 100644
--- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AdminOverAsyncAdmin.java
+++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AdminOverAsyncAdmin.java
@@ -66,6 +66,7 @@ import org.apache.hadoop.hbase.snapshot.RestoreSnapshotException;
 import org.apache.hadoop.hbase.snapshot.SnapshotCreationException;
 import org.apache.hadoop.hbase.snapshot.UnknownSnapshotException;
 import org.apache.hadoop.hbase.util.Bytes;
+import org.apache.hadoop.hbase.util.Pair;
 import org.apache.yetus.audience.InterfaceAudience;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -993,6 +994,17 @@ class AdminOverAsyncAdmin implements Admin {
   }
 
   @Override
+  public List<TableName> listTablesInRSGroup(String groupName) throws IOException {
+    return get(admin.listTablesInRSGroup(groupName));
+  }
+
+  @Override
+  public Pair<List<String>, List<TableName>>
+    getConfiguredNamespacesAndTablesInRSGroup(String groupName) throws IOException {
+    return get(admin.getConfiguredNamespacesAndTablesInRSGroup(groupName));
+  }
+
+  @Override
   public RSGroupInfo getRSGroup(Address hostPort) throws IOException {
     return get(admin.getRSGroup(hostPort));
   }
@@ -1011,5 +1023,4 @@ class AdminOverAsyncAdmin implements Admin {
   public void setRSGroup(Set<TableName> tables, String groupName) throws IOException {
     get(admin.setRSGroup(tables, groupName));
   }
-
 }
diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AsyncAdmin.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AsyncAdmin.java
index 07cce90..af401f5 100644
--- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AsyncAdmin.java
+++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AsyncAdmin.java
@@ -51,6 +51,7 @@ import org.apache.hadoop.hbase.rsgroup.RSGroupInfo;
 import org.apache.hadoop.hbase.security.access.GetUserPermissionsRequest;
 import org.apache.hadoop.hbase.security.access.Permission;
 import org.apache.hadoop.hbase.security.access.UserPermission;
+import org.apache.hadoop.hbase.util.Pair;
 import org.apache.yetus.audience.InterfaceAudience;
 
 /**
@@ -1545,6 +1546,30 @@ public interface AsyncAdmin {
   CompletableFuture<List<RSGroupInfo>> listRSGroups();
 
   /**
+   * Get all tables in this RegionServer group.
+   * @param groupName the group name
+   * @throws IOException if a remote or network exception occurs
+   * @see #getConfiguredNamespacesAndTablesInRSGroup(String)
+   */
+  CompletableFuture<List<TableName>> listTablesInRSGroup(String groupName);
+
+  /**
+   * Get the namespaces and tables which have this RegionServer group in descriptor.
+   * <p/>
+   * The difference between this method and {@link #listTablesInRSGroup(String)} is that, this
+   * method will not include the table which is actually in this RegionServr group but without the
+   * RegionServer group configuration in its {@link TableDescriptor}. For example, we have a group
+   * 'A', and we make namespace 'nsA' in this group, then all the tables under this namespace will
+   * in the group 'A', but this method will not return these tables but only the namespace 'nsA',
+   * while the {@link #listTablesInRSGroup(String)} will return all these tables.
+   * @param groupName the group name
+   * @throws IOException if a remote or network exception occurs
+   * @see #listTablesInRSGroup(String)
+   */
+  CompletableFuture<Pair<List<String>, List<TableName>>>
+    getConfiguredNamespacesAndTablesInRSGroup(String groupName);
+
+  /**
    * Remove RegionServer group associated with the given name
    * @param groupName the group name
    * @throws IOException if a remote or network exception occurs
diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AsyncHBaseAdmin.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AsyncHBaseAdmin.java
index 6bf2504..cf99427 100644
--- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AsyncHBaseAdmin.java
+++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AsyncHBaseAdmin.java
@@ -48,6 +48,7 @@ import org.apache.hadoop.hbase.security.access.GetUserPermissionsRequest;
 import org.apache.hadoop.hbase.security.access.Permission;
 import org.apache.hadoop.hbase.security.access.UserPermission;
 import org.apache.hadoop.hbase.util.FutureUtils;
+import org.apache.hadoop.hbase.util.Pair;
 import org.apache.yetus.audience.InterfaceAudience;
 
 /**
@@ -870,6 +871,17 @@ class AsyncHBaseAdmin implements AsyncAdmin {
   }
 
   @Override
+  public CompletableFuture<List<TableName>> listTablesInRSGroup(String groupName) {
+    return wrap(rawAdmin.listTablesInRSGroup(groupName));
+  }
+
+  @Override
+  public CompletableFuture<Pair<List<String>, List<TableName>>>
+    getConfiguredNamespacesAndTablesInRSGroup(String groupName) {
+    return wrap(rawAdmin.getConfiguredNamespacesAndTablesInRSGroup(groupName));
+  }
+
+  @Override
   public CompletableFuture<RSGroupInfo> getRSGroup(Address hostPort) {
     return wrap(rawAdmin.getRSGroup(hostPort));
   }
@@ -888,5 +900,4 @@ class AsyncHBaseAdmin implements AsyncAdmin {
   public CompletableFuture<Void> setRSGroup(Set<TableName> tables, String groupName) {
     return wrap(rawAdmin.setRSGroup(tables, groupName));
   }
-
 }
diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/RawAsyncHBaseAdmin.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/RawAsyncHBaseAdmin.java
index 5376820..296ce6e 100644
--- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/RawAsyncHBaseAdmin.java
+++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/RawAsyncHBaseAdmin.java
@@ -98,6 +98,7 @@ import org.apache.hadoop.hbase.snapshot.SnapshotCreationException;
 import org.apache.hadoop.hbase.util.Bytes;
 import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
 import org.apache.hadoop.hbase.util.ForeignExceptionUtil;
+import org.apache.hadoop.hbase.util.Pair;
 import org.apache.yetus.audience.InterfaceAudience;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -289,6 +290,8 @@ import org.apache.hadoop.hbase.shaded.protobuf.generated.RSGroupAdminProtos.AddR
 import org.apache.hadoop.hbase.shaded.protobuf.generated.RSGroupAdminProtos.AddRSGroupResponse;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.RSGroupAdminProtos.BalanceRSGroupRequest;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.RSGroupAdminProtos.BalanceRSGroupResponse;
+import org.apache.hadoop.hbase.shaded.protobuf.generated.RSGroupAdminProtos.GetConfiguredNamespacesAndTablesInRSGroupRequest;
+import org.apache.hadoop.hbase.shaded.protobuf.generated.RSGroupAdminProtos.GetConfiguredNamespacesAndTablesInRSGroupResponse;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.RSGroupAdminProtos.GetRSGroupInfoOfServerRequest;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.RSGroupAdminProtos.GetRSGroupInfoOfServerResponse;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.RSGroupAdminProtos.GetRSGroupInfoOfTableRequest;
@@ -297,6 +300,8 @@ import org.apache.hadoop.hbase.shaded.protobuf.generated.RSGroupAdminProtos.GetR
 import org.apache.hadoop.hbase.shaded.protobuf.generated.RSGroupAdminProtos.GetRSGroupInfoResponse;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.RSGroupAdminProtos.ListRSGroupInfosRequest;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.RSGroupAdminProtos.ListRSGroupInfosResponse;
+import org.apache.hadoop.hbase.shaded.protobuf.generated.RSGroupAdminProtos.ListTablesInRSGroupRequest;
+import org.apache.hadoop.hbase.shaded.protobuf.generated.RSGroupAdminProtos.ListTablesInRSGroupResponse;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.RSGroupAdminProtos.MoveServersRequest;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.RSGroupAdminProtos.MoveServersResponse;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.RSGroupAdminProtos.RemoveRSGroupRequest;
@@ -335,6 +340,7 @@ import org.apache.hadoop.hbase.shaded.protobuf.generated.SnapshotProtos;
  */
 @InterfaceAudience.Private
 class RawAsyncHBaseAdmin implements AsyncAdmin {
+
   public static final String FLUSH_TABLE_PROCEDURE_SIGNATURE = "flush-table-proc";
 
   private static final Logger LOG = LoggerFactory.getLogger(AsyncHBaseAdmin.class);
@@ -3955,6 +3961,33 @@ class RawAsyncHBaseAdmin implements AsyncAdmin {
   }
 
   @Override
+  public CompletableFuture<List<TableName>> listTablesInRSGroup(String groupName) {
+    return this.<List<TableName>> newMasterCaller()
+      .action((controller, stub) -> this
+        .<ListTablesInRSGroupRequest, ListTablesInRSGroupResponse, List<TableName>> call(controller,
+          stub, ListTablesInRSGroupRequest.newBuilder().setGroupName(groupName).build(),
+          (s, c, req, done) -> s.listTablesInRSGroup(c, req, done), resp -> resp.getTableNameList()
+            .stream().map(ProtobufUtil::toTableName).collect(Collectors.toList())))
+      .call();
+  }
+
+  @Override
+  public CompletableFuture<Pair<List<String>, List<TableName>>>
+    getConfiguredNamespacesAndTablesInRSGroup(String groupName) {
+    return this.<Pair<List<String>, List<TableName>>> newMasterCaller()
+      .action((controller, stub) -> this
+        .<GetConfiguredNamespacesAndTablesInRSGroupRequest,
+          GetConfiguredNamespacesAndTablesInRSGroupResponse,
+          Pair<List<String>, List<TableName>>> call(controller, stub,
+          GetConfiguredNamespacesAndTablesInRSGroupRequest.newBuilder().setGroupName(groupName)
+            .build(),
+            (s, c, req, done) -> s.getConfiguredNamespacesAndTablesInRSGroup(c, req, done),
+            resp -> Pair.newPair(resp.getNamespaceList(), resp.getTableNameList().stream()
+              .map(ProtobufUtil::toTableName).collect(Collectors.toList()))))
+      .call();
+  }
+
+  @Override
   public CompletableFuture<RSGroupInfo> getRSGroup(Address hostPort) {
     return this.<RSGroupInfo> newMasterCaller()
       .action(((controller, stub) -> this
diff --git a/hbase-it/src/test/java/org/apache/hadoop/hbase/rsgroup/IntegrationTestRSGroup.java b/hbase-it/src/test/java/org/apache/hadoop/hbase/rsgroup/IntegrationTestRSGroup.java
index 551d280..12e2438 100644
--- a/hbase-it/src/test/java/org/apache/hadoop/hbase/rsgroup/IntegrationTestRSGroup.java
+++ b/hbase-it/src/test/java/org/apache/hadoop/hbase/rsgroup/IntegrationTestRSGroup.java
@@ -22,7 +22,6 @@ package org.apache.hadoop.hbase.rsgroup;
 import org.apache.hadoop.hbase.HConstants;
 import org.apache.hadoop.hbase.IntegrationTestingUtility;
 import org.apache.hadoop.hbase.Waiter;
-import org.apache.hadoop.hbase.coprocessor.CoprocessorHost;
 import org.apache.hadoop.hbase.testclassification.IntegrationTests;
 import org.junit.After;
 import org.junit.Before;
@@ -47,13 +46,11 @@ public class IntegrationTestRSGroup extends TestRSGroupsBase {
       TEST_UTIL = new IntegrationTestingUtility();
       TEST_UTIL.getConfiguration().set(HConstants.HBASE_MASTER_LOADBALANCER_CLASS,
         RSGroupBasedLoadBalancer.class.getName());
-      TEST_UTIL.getConfiguration().set(CoprocessorHost.MASTER_COPROCESSOR_CONF_KEY,
-        RSGroupAdminEndpoint.class.getName());
+      TEST_UTIL.getConfiguration().setBoolean(RSGroupInfoManager.RS_GROUP_ENABLED, true);
       ((IntegrationTestingUtility) TEST_UTIL).initializeCluster(NUM_SLAVES_BASE);
       // set shared configs
       ADMIN = TEST_UTIL.getAdmin();
       CLUSTER = TEST_UTIL.getHBaseClusterInterface();
-      RS_GROUP_ADMIN_CLIENT = new RSGroupAdminClient(TEST_UTIL.getConnection());
       LOG.info("Done initializing cluster");
       initialized = true;
       // cluster may not be clean
diff --git a/hbase-protocol-shaded/src/main/protobuf/Master.proto b/hbase-protocol-shaded/src/main/protobuf/Master.proto
index c67dfab..550159f 100644
--- a/hbase-protocol-shaded/src/main/protobuf/Master.proto
+++ b/hbase-protocol-shaded/src/main/protobuf/Master.proto
@@ -1113,6 +1113,12 @@ service MasterService {
 
   rpc RemoveServers(RemoveServersRequest)
     returns (RemoveServersResponse);
+
+  rpc ListTablesInRSGroup(ListTablesInRSGroupRequest)
+    returns (ListTablesInRSGroupResponse);
+
+  rpc GetConfiguredNamespacesAndTablesInRSGroup(GetConfiguredNamespacesAndTablesInRSGroupRequest)
+    returns (GetConfiguredNamespacesAndTablesInRSGroupResponse);
 }
 
 // HBCK Service definitions.
diff --git a/hbase-protocol-shaded/src/main/protobuf/RSGroupAdmin.proto b/hbase-protocol-shaded/src/main/protobuf/RSGroupAdmin.proto
index 1db7136..895f50e 100644
--- a/hbase-protocol-shaded/src/main/protobuf/RSGroupAdmin.proto
+++ b/hbase-protocol-shaded/src/main/protobuf/RSGroupAdmin.proto
@@ -122,6 +122,23 @@ message RemoveServersRequest {
 message RemoveServersResponse {
 }
 
+message ListTablesInRSGroupRequest {
+  required string group_name = 1;
+}
+
+message ListTablesInRSGroupResponse {
+  repeated TableName table_name = 1;
+}
+
+message GetConfiguredNamespacesAndTablesInRSGroupRequest {
+  required string group_name = 1;
+}
+
+message GetConfiguredNamespacesAndTablesInRSGroupResponse {
+  repeated string namespace = 1;
+  repeated TableName table_name = 2;
+}
+
 service RSGroupAdminService {
   rpc GetRSGroupInfo(GetRSGroupInfoRequest)
     returns (GetRSGroupInfoResponse);
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 2a2e666..900a99c 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
@@ -1325,6 +1325,40 @@ public interface MasterObserver {
       throws IOException {}
 
   /**
+   * Called before listing all tables in the region server group.
+   * @param ctx the environment to interact with the framework and master
+   * @param groupName name of the region server group
+   */
+  default void preListTablesInRSGroup(final ObserverContext<MasterCoprocessorEnvironment> ctx,
+    final String groupName) throws IOException {}
+
+  /**
+   * Called after listing all tables in the region server group.
+   * @param ctx the environment to interact with the framework and master
+   * @param groupName name of the region server group
+   */
+  default void postListTablesInRSGroup(final ObserverContext<MasterCoprocessorEnvironment> ctx,
+    final String groupName) throws IOException {}
+
+  /**
+   * Called before getting the configured namespaces and tables in the region server group.
+   * @param ctx the environment to interact with the framework and master
+   * @param groupName name of the region server group
+   */
+  default void preGetConfiguredNamespacesAndTablesInRSGroup(
+    final ObserverContext<MasterCoprocessorEnvironment> ctx, final String groupName)
+    throws IOException {}
+
+  /**
+   * Called after getting the configured namespaces and tables in the region server group.
+   * @param ctx the environment to interact with the framework and master
+   * @param groupName name of the region server group
+   */
+  default void postGetConfiguredNamespacesAndTablesInRSGroup(
+    final ObserverContext<MasterCoprocessorEnvironment> ctx, final String groupName)
+    throws IOException {}
+
+  /**
    * Called before getting region server group info of the passed server.
    * @param ctx the environment to interact with the framework and master
    * @param server server to get RSGroupInfo for
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 abd297c..f1b818c 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
@@ -1529,6 +1529,48 @@ public class MasterCoprocessorHost
     });
   }
 
+  public void preListTablesInRSGroup(final String groupName) throws IOException {
+    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
+
+      @Override
+      protected void call(MasterObserver observer) throws IOException {
+        observer.preListTablesInRSGroup(this, groupName);
+      }
+    });
+  }
+
+  public void postListTablesInRSGroup(final String groupName) throws IOException {
+    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
+
+      @Override
+      protected void call(MasterObserver observer) throws IOException {
+        observer.postListTablesInRSGroup(this, groupName);
+      }
+    });
+  }
+
+  public void preGetConfiguredNamespacesAndTablesInRSGroup(final String groupName)
+    throws IOException {
+    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
+
+      @Override
+      protected void call(MasterObserver observer) throws IOException {
+        observer.preGetConfiguredNamespacesAndTablesInRSGroup(this, groupName);
+      }
+    });
+  }
+
+  public void postGetConfiguredNamespacesAndTablesInRSGroup(final String groupName)
+    throws IOException {
+    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
+
+      @Override
+      protected void call(MasterObserver observer) throws IOException {
+        observer.postGetConfiguredNamespacesAndTablesInRSGroup(this, groupName);
+      }
+    });
+  }
+
   public void preGetRSGroupInfoOfServer(final Address server) throws IOException {
     execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
       @Override
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/MasterRpcServices.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/MasterRpcServices.java
index 39edc42..72c0ba9 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/MasterRpcServices.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/MasterRpcServices.java
@@ -95,6 +95,7 @@ import org.apache.hadoop.hbase.replication.ReplicationException;
 import org.apache.hadoop.hbase.replication.ReplicationPeerConfig;
 import org.apache.hadoop.hbase.replication.ReplicationPeerDescription;
 import org.apache.hadoop.hbase.rsgroup.RSGroupInfo;
+import org.apache.hadoop.hbase.rsgroup.RSGroupUtil;
 import org.apache.hadoop.hbase.security.Superusers;
 import org.apache.hadoop.hbase.security.User;
 import org.apache.hadoop.hbase.security.access.AccessChecker;
@@ -311,6 +312,8 @@ import org.apache.hadoop.hbase.shaded.protobuf.generated.RSGroupAdminProtos.AddR
 import org.apache.hadoop.hbase.shaded.protobuf.generated.RSGroupAdminProtos.AddRSGroupResponse;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.RSGroupAdminProtos.BalanceRSGroupRequest;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.RSGroupAdminProtos.BalanceRSGroupResponse;
+import org.apache.hadoop.hbase.shaded.protobuf.generated.RSGroupAdminProtos.GetConfiguredNamespacesAndTablesInRSGroupRequest;
+import org.apache.hadoop.hbase.shaded.protobuf.generated.RSGroupAdminProtos.GetConfiguredNamespacesAndTablesInRSGroupResponse;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.RSGroupAdminProtos.GetRSGroupInfoOfServerRequest;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.RSGroupAdminProtos.GetRSGroupInfoOfServerResponse;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.RSGroupAdminProtos.GetRSGroupInfoOfTableRequest;
@@ -319,6 +322,8 @@ import org.apache.hadoop.hbase.shaded.protobuf.generated.RSGroupAdminProtos.GetR
 import org.apache.hadoop.hbase.shaded.protobuf.generated.RSGroupAdminProtos.GetRSGroupInfoResponse;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.RSGroupAdminProtos.ListRSGroupInfosRequest;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.RSGroupAdminProtos.ListRSGroupInfosResponse;
+import org.apache.hadoop.hbase.shaded.protobuf.generated.RSGroupAdminProtos.ListTablesInRSGroupRequest;
+import org.apache.hadoop.hbase.shaded.protobuf.generated.RSGroupAdminProtos.ListTablesInRSGroupResponse;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.RSGroupAdminProtos.MoveServersRequest;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.RSGroupAdminProtos.MoveServersResponse;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.RSGroupAdminProtos.RemoveRSGroupRequest;
@@ -3156,4 +3161,63 @@ public class MasterRpcServices extends RSRpcServices implements MasterService.Bl
     }
     return builder.build();
   }
+
+  @Override
+  public ListTablesInRSGroupResponse listTablesInRSGroup(RpcController controller,
+    ListTablesInRSGroupRequest request) throws ServiceException {
+    ListTablesInRSGroupResponse.Builder builder = ListTablesInRSGroupResponse.newBuilder();
+    String groupName = request.getGroupName();
+    LOG.info(master.getClientIdAuditPrefix() + " list tables in rsgroup " + groupName);
+    try {
+      if (master.getMasterCoprocessorHost() != null) {
+        master.getMasterCoprocessorHost().preListTablesInRSGroup(groupName);
+      }
+      boolean isDefaultGroup = RSGroupInfo.DEFAULT_GROUP.equals(groupName);
+      for (TableDescriptor td : master.getTableDescriptors().getAll().values()) {
+        // no config means in default group
+        if (RSGroupUtil.getRSGroupInfo(master, master.getRSGroupInfoManager(), td.getTableName())
+          .map(g -> g.getName().equals(groupName)).orElse(isDefaultGroup)) {
+          builder.addTableName(ProtobufUtil.toProtoTableName(td.getTableName()));
+        }
+      }
+      if (master.getMasterCoprocessorHost() != null) {
+        master.getMasterCoprocessorHost().postListTablesInRSGroup(groupName);
+      }
+    } catch (IOException e) {
+      throw new ServiceException(e);
+    }
+    return builder.build();
+  }
+
+  @Override
+  public GetConfiguredNamespacesAndTablesInRSGroupResponse
+    getConfiguredNamespacesAndTablesInRSGroup(RpcController controller,
+      GetConfiguredNamespacesAndTablesInRSGroupRequest request) throws ServiceException {
+    GetConfiguredNamespacesAndTablesInRSGroupResponse.Builder builder =
+      GetConfiguredNamespacesAndTablesInRSGroupResponse.newBuilder();
+    String groupName = request.getGroupName();
+    LOG.info(master.getClientIdAuditPrefix() + " get configured namespaces and tables in rsgroup " +
+      groupName);
+    try {
+      if (master.getMasterCoprocessorHost() != null) {
+        master.getMasterCoprocessorHost().preGetConfiguredNamespacesAndTablesInRSGroup(groupName);
+      }
+      for (NamespaceDescriptor nd : master.getClusterSchema().getNamespaces()) {
+        if (groupName.equals(nd.getConfigurationValue(RSGroupInfo.NAMESPACE_DESC_PROP_GROUP))) {
+          builder.addNamespace(nd.getName());
+        }
+      }
+      for (TableDescriptor td : master.getTableDescriptors().getAll().values()) {
+        if (td.getRegionServerGroup().map(g -> g.equals(groupName)).orElse(false)) {
+          builder.addTableName(ProtobufUtil.toProtoTableName(td.getTableName()));
+        }
+      }
+      if (master.getMasterCoprocessorHost() != null) {
+        master.getMasterCoprocessorHost().postGetConfiguredNamespacesAndTablesInRSGroup(groupName);
+      }
+    } catch (IOException e) {
+      throw new ServiceException(e);
+    }
+    return builder.build();
+  }
 }
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 b10e6c3..74cbdfd 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
@@ -2661,6 +2661,20 @@ public class AccessController implements MasterCoprocessor, RegionCoprocessor,
   }
 
   @Override
+  public void preListTablesInRSGroup(ObserverContext<MasterCoprocessorEnvironment> ctx,
+    String groupName) throws IOException {
+    accessChecker.requirePermission(getActiveUser(ctx), "listTablesInRSGroup",
+      null, Permission.Action.ADMIN);
+  }
+
+  @Override
+  public void preGetConfiguredNamespacesAndTablesInRSGroup(
+    ObserverContext<MasterCoprocessorEnvironment> ctx, String groupName) throws IOException {
+    accessChecker.requirePermission(getActiveUser(ctx), "getConfiguredNamespacesAndTablesInRSGroup",
+      null, Permission.Action.ADMIN);
+  }
+
+  @Override
   public void preGetRSGroupInfoOfServer(ObserverContext<MasterCoprocessorEnvironment> ctx,
       Address server) throws IOException {
     accessChecker.requirePermission(getActiveUser(ctx), "getRSGroupInfoOfServer",
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestMigrateRSGroupInfo.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestMigrateRSGroupInfo.java
index c3e4fe7..0d94549 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestMigrateRSGroupInfo.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestMigrateRSGroupInfo.java
@@ -35,6 +35,7 @@ import org.apache.hadoop.hbase.client.Put;
 import org.apache.hadoop.hbase.client.Result;
 import org.apache.hadoop.hbase.client.Table;
 import org.apache.hadoop.hbase.client.TableDescriptor;
+import org.apache.hadoop.hbase.coprocessor.CoprocessorHost;
 import org.apache.hadoop.hbase.master.HMaster;
 import org.apache.hadoop.hbase.protobuf.ProtobufUtil;
 import org.apache.hadoop.hbase.protobuf.generated.RSGroupProtos;
@@ -42,8 +43,8 @@ import org.apache.hadoop.hbase.testclassification.MediumTests;
 import org.apache.hadoop.hbase.testclassification.RSGroupTests;
 import org.apache.hadoop.hbase.util.Bytes;
 import org.apache.zookeeper.KeeperException;
-import org.junit.After;
-import org.junit.Before;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
 import org.junit.ClassRule;
 import org.junit.Test;
 import org.junit.experimental.categories.Category;
@@ -64,18 +65,25 @@ public class TestMigrateRSGroupInfo extends TestRSGroupsBase {
 
   private static byte[] FAMILY = Bytes.toBytes("family");
 
-  @Before
-  public void setUp() throws Exception {
+  private static RSGroupAdminClient RS_GROUP_ADMIN_CLIENT;
+
+  @BeforeClass
+  public static void setUp() throws Exception {
     TEST_UTIL.getConfiguration().setClass(HConstants.MASTER_IMPL, HMasterForTest.class,
       HMaster.class);
+    // confirm that we could enable rs group by setting the old CP.
+    TEST_UTIL.getConfiguration().setBoolean(RSGroupInfoManager.RS_GROUP_ENABLED, false);
+    TEST_UTIL.getConfiguration().set(CoprocessorHost.MASTER_COPROCESSOR_CONF_KEY,
+      RSGroupAdminEndpoint.class.getName());
     setUpTestBeforeClass();
+    RS_GROUP_ADMIN_CLIENT = new RSGroupAdminClient(TEST_UTIL.getConnection());
     for (int i = 0; i < NUM_TABLES; i++) {
       TEST_UTIL.createTable(TableName.valueOf(TABLE_NAME_PREFIX + i), FAMILY);
     }
   }
 
-  @After
-  public void tearDown() throws Exception {
+  @AfterClass
+  public static void tearDown() throws Exception {
     tearDownAfterClass();
   }
 
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsAdmin1.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsAdmin1.java
index 255f80d..33077cc 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsAdmin1.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsAdmin1.java
@@ -27,7 +27,6 @@ import java.io.IOException;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
-import java.util.SortedSet;
 import org.apache.hadoop.hbase.HBaseClassTestRule;
 import org.apache.hadoop.hbase.NamespaceDescriptor;
 import org.apache.hadoop.hbase.ServerName;
@@ -186,7 +185,7 @@ public class TestRSGroupsAdmin1 extends TestRSGroupsBase {
     addGroup("bar", 3);
     TEST_UTIL.createTable(tableName, Bytes.toBytes("f"));
     ADMIN.setRSGroup(Sets.newHashSet(tableName), "bar");
-    RSGroupInfo barGroup = RS_GROUP_ADMIN_CLIENT.getRSGroupInfo("bar");
+    RSGroupInfo barGroup = ADMIN.getRSGroup("bar");
     // group is not empty therefore it should fail
     try {
       ADMIN.removeRSGroup(barGroup.getName());
@@ -200,7 +199,7 @@ public class TestRSGroupsAdmin1 extends TestRSGroupsBase {
     } catch (IOException e) {
     }
 
-    ADMIN.setRSGroup(barGroup.getTables(), RSGroupInfo.DEFAULT_GROUP);
+    ADMIN.setRSGroup(Sets.newHashSet(ADMIN.listTablesInRSGroup("bar")), RSGroupInfo.DEFAULT_GROUP);
     try {
       ADMIN.removeRSGroup(barGroup.getName());
       fail("Expected move servers to fail");
@@ -257,12 +256,12 @@ public class TestRSGroupsAdmin1 extends TestRSGroupsBase {
 
     // verify tables' not exist in old group
     Set<TableName> defaultTables =
-      RS_GROUP_ADMIN_CLIENT.getRSGroupInfo(RSGroupInfo.DEFAULT_GROUP).getTables();
+      Sets.newHashSet(ADMIN.listTablesInRSGroup(RSGroupInfo.DEFAULT_GROUP));
     assertFalse(defaultTables.contains(tableNameA));
     assertFalse(defaultTables.contains(tableNameB));
 
     // verify tables' exist in new group
-    Set<TableName> newGroupTables = RS_GROUP_ADMIN_CLIENT.getRSGroupInfo(newGroupName).getTables();
+    Set<TableName> newGroupTables = Sets.newHashSet(ADMIN.listTablesInRSGroup(newGroupName));
     assertTrue(newGroupTables.contains(tableNameA));
     assertTrue(newGroupTables.contains(tableNameB));
   }
@@ -316,13 +315,13 @@ public class TestRSGroupsAdmin1 extends TestRSGroupsBase {
     // test truncate
     ADMIN.disableTable(tableName);
     ADMIN.truncateTable(tableName, true);
-    assertEquals(1, RS_GROUP_ADMIN_CLIENT.getRSGroupInfo(newGroup.getName()).getTables().size());
-    assertEquals(tableName,
-      RS_GROUP_ADMIN_CLIENT.getRSGroupInfo(newGroup.getName()).getTables().first());
+    List<TableName> tablesInGroup = ADMIN.listTablesInRSGroup(newGroup.getName());
+    assertEquals(1, tablesInGroup.size());
+    assertEquals(tableName, tablesInGroup.get(0));
 
     // verify removed table is removed from group
     TEST_UTIL.deleteTable(tableName);
-    assertEquals(0, RS_GROUP_ADMIN_CLIENT.getRSGroupInfo(newGroup.getName()).getTables().size());
+    assertEquals(0, ADMIN.listTablesInRSGroup(newGroup.getName()).size());
   }
 
   @Test
@@ -418,14 +417,16 @@ public class TestRSGroupsAdmin1 extends TestRSGroupsBase {
       assertTrue("Constraint not violated for table " + tableDescTwo.getTableName(),
         constraintViolated);
     }
-    List<RSGroupInfo> rsGroupInfoList = RS_GROUP_ADMIN_CLIENT.listRSGroups();
+    List<RSGroupInfo> rsGroupInfoList = ADMIN.listRSGroups();
     boolean foundTable2 = false;
     boolean foundTable1 = false;
     for (int i = 0; i < rsGroupInfoList.size(); i++) {
-      if (rsGroupInfoList.get(i).getTables().contains(tableDescTwo.getTableName())) {
+      Set<TableName> tables =
+        Sets.newHashSet(ADMIN.listTablesInRSGroup(rsGroupInfoList.get(i).getName()));
+      if (tables.contains(tableDescTwo.getTableName())) {
         foundTable2 = true;
       }
-      if (rsGroupInfoList.get(i).getTables().contains(tableDescOne.getTableName())) {
+      if (tables.contains(tableDescOne.getTableName())) {
         foundTable1 = true;
       }
     }
@@ -458,11 +459,10 @@ public class TestRSGroupsAdmin1 extends TestRSGroupsBase {
     TEST_UTIL.waitFor(5000, new Waiter.Predicate<Exception>() {
       @Override
       public boolean evaluate() throws Exception {
-        return (MASTER.getMasterProcedureExecutor().getActiveExecutorCount() == 0);
+        return MASTER.getMasterProcedureExecutor().getActiveExecutorCount() == 0;
       }
     });
-    SortedSet<TableName> tables =
-      RS_GROUP_ADMIN_CLIENT.getRSGroupInfo(RSGroupInfo.DEFAULT_GROUP).getTables();
+    Set<TableName> tables = Sets.newHashSet(ADMIN.listTablesInRSGroup(RSGroupInfo.DEFAULT_GROUP));
     assertTrue("Table 't1' must be in 'default' rsgroup", tables.contains(tn1));
 
     // Cleanup
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsAdmin2.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsAdmin2.java
index 2faa786..a3a08ea 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsAdmin2.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsAdmin2.java
@@ -326,8 +326,9 @@ public class TestRSGroupsAdmin2 extends TestRSGroupsBase {
 
     LOG.debug("Print group info : " + ADMIN.listRSGroups());
     int oldDefaultGroupServerSize = ADMIN.getRSGroup(RSGroupInfo.DEFAULT_GROUP).getServers().size();
-    int oldDefaultGroupTableSize =
-      RS_GROUP_ADMIN_CLIENT.getRSGroupInfo(RSGroupInfo.DEFAULT_GROUP).getTables().size();
+    int oldDefaultGroupTableSize = ADMIN.listTablesInRSGroup(RSGroupInfo.DEFAULT_GROUP).size();
+    assertTrue(OBSERVER.preListTablesInRSGroupCalled);
+    assertTrue(OBSERVER.postListTablesInRSGroupCalled);
 
     // test fail bogus server move
     try {
@@ -350,11 +351,14 @@ public class TestRSGroupsAdmin2 extends TestRSGroupsBase {
     assertEquals(oldDefaultGroupServerSize,
       ADMIN.getRSGroup(RSGroupInfo.DEFAULT_GROUP).getServers().size());
     assertEquals(oldDefaultGroupTableSize,
-      RS_GROUP_ADMIN_CLIENT.getRSGroupInfo(RSGroupInfo.DEFAULT_GROUP).getTables().size());
+      ADMIN.listTablesInRSGroup(RSGroupInfo.DEFAULT_GROUP).size());
 
     // verify new group info
     assertEquals(1, ADMIN.getRSGroup(newGroup.getName()).getServers().size());
-    assertEquals(0, RS_GROUP_ADMIN_CLIENT.getRSGroupInfo(newGroup.getName()).getTables().size());
+    assertEquals(0,
+      ADMIN.getConfiguredNamespacesAndTablesInRSGroup(newGroup.getName()).getSecond().size());
+    assertTrue(OBSERVER.preGetConfiguredNamespacesAndTablesInRSGroupCalled);
+    assertTrue(OBSERVER.postGetConfiguredNamespacesAndTablesInRSGroupCalled);
 
     // get all region to move targetServer
     List<String> regionList = getTableRegionMap().get(tableName);
@@ -396,12 +400,12 @@ public class TestRSGroupsAdmin2 extends TestRSGroupsBase {
 
     // verify tables' not exist in old group
     Set<TableName> defaultTables =
-      RS_GROUP_ADMIN_CLIENT.getRSGroupInfo(RSGroupInfo.DEFAULT_GROUP).getTables();
+      Sets.newHashSet(ADMIN.listTablesInRSGroup(RSGroupInfo.DEFAULT_GROUP));
     assertFalse(defaultTables.contains(tableName));
 
     // verify tables' exist in new group
-    Set<TableName> newGroupTables =
-      RS_GROUP_ADMIN_CLIENT.getRSGroupInfo(newGroup.getName()).getTables();
+    Set<TableName> newGroupTables = Sets
+      .newHashSet(ADMIN.getConfiguredNamespacesAndTablesInRSGroup(newGroup.getName()).getSecond());
     assertTrue(newGroupTables.contains(tableName));
 
     // verify that all region still assign on targetServer
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsBase.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsBase.java
index 7963126..b921508 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsBase.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsBase.java
@@ -31,6 +31,7 @@ import java.util.Set;
 import java.util.TreeMap;
 import java.util.concurrent.ThreadLocalRandom;
 import java.util.regex.Pattern;
+import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.hbase.ClusterMetrics;
 import org.apache.hadoop.hbase.ClusterMetrics.Option;
 import org.apache.hadoop.hbase.HBaseCluster;
@@ -76,7 +77,6 @@ public abstract class TestRSGroupsBase {
   protected static HMaster MASTER;
   protected boolean INIT = false;
   protected static CPMasterObserver OBSERVER;
-  protected static RSGroupAdminClient RS_GROUP_ADMIN_CLIENT;
 
   public final static long WAIT_TIMEOUT = 60000;
   public final static int NUM_SLAVES_BASE = 4; // number of slaves for the smallest cluster
@@ -92,14 +92,23 @@ public abstract class TestRSGroupsBase {
   }
 
   public static void setUpTestBeforeClass() throws Exception {
-    TEST_UTIL.getConfiguration().setFloat("hbase.master.balancer.stochastic.tableSkewCost", 6000);
-    TEST_UTIL.getConfiguration().setBoolean(RSGroupInfoManager.RS_GROUP_ENABLED, true);
-    TEST_UTIL.getConfiguration().set(CoprocessorHost.MASTER_COPROCESSOR_CONF_KEY,
-      RSGroupAdminEndpoint.class.getName() + "," + CPMasterObserver.class.getName());
-    TEST_UTIL.getConfiguration().setInt(ServerManager.WAIT_ON_REGIONSERVERS_MINTOSTART,
+    Configuration conf = TEST_UTIL.getConfiguration();
+    conf.setFloat("hbase.master.balancer.stochastic.tableSkewCost", 6000);
+    if (conf.get(RSGroupInfoManager.RS_GROUP_ENABLED) == null) {
+      conf.setBoolean(RSGroupInfoManager.RS_GROUP_ENABLED, true);
+    }
+    if (conf.get(CoprocessorHost.MASTER_COPROCESSOR_CONF_KEY) != null) {
+      conf.set(CoprocessorHost.MASTER_COPROCESSOR_CONF_KEY,
+        conf.get(CoprocessorHost.MASTER_COPROCESSOR_CONF_KEY) + "," +
+          CPMasterObserver.class.getName());
+    } else {
+      conf.set(CoprocessorHost.MASTER_COPROCESSOR_CONF_KEY, CPMasterObserver.class.getName());
+    }
+
+    conf.setInt(ServerManager.WAIT_ON_REGIONSERVERS_MINTOSTART,
       NUM_SLAVES_BASE - 1);
-    TEST_UTIL.getConfiguration().setBoolean(SnapshotManager.HBASE_SNAPSHOT_ENABLED, true);
-    TEST_UTIL.getConfiguration().setInt("hbase.rpc.timeout", 100000);
+    conf.setBoolean(SnapshotManager.HBASE_SNAPSHOT_ENABLED, true);
+    conf.setInt("hbase.rpc.timeout", 100000);
 
     TEST_UTIL.startMiniCluster(NUM_SLAVES_BASE - 1);
     initialize();
@@ -121,7 +130,6 @@ public abstract class TestRSGroupsBase {
     ADMIN.balancerSwitch(false, true);
     MasterCoprocessorHost host = MASTER.getMasterCoprocessorHost();
     OBSERVER = (CPMasterObserver) host.findCoprocessor(CPMasterObserver.class.getName());
-    RS_GROUP_ADMIN_CLIENT = new RSGroupAdminClient(TEST_UTIL.getConnection());
   }
 
   public static void tearDownAfterClass() throws Exception {
@@ -319,6 +327,10 @@ public abstract class TestRSGroupsBase {
     boolean postGetRSGroupInfoOfServerCalled = false;
     boolean preSetRSGroupForTablesCalled = false;
     boolean postSetRSGroupForTablesCalled = false;
+    boolean preListTablesInRSGroupCalled = false;
+    boolean postListTablesInRSGroupCalled = false;
+    boolean preGetConfiguredNamespacesAndTablesInRSGroupCalled = false;
+    boolean postGetConfiguredNamespacesAndTablesInRSGroupCalled = false;
 
     public void resetFlags() {
       preBalanceRSGroupCalled = false;
@@ -345,6 +357,10 @@ public abstract class TestRSGroupsBase {
       postGetRSGroupInfoOfServerCalled = false;
       preSetRSGroupForTablesCalled = false;
       postSetRSGroupForTablesCalled = false;
+      preListTablesInRSGroupCalled = false;
+      postListTablesInRSGroupCalled = false;
+      preGetConfiguredNamespacesAndTablesInRSGroupCalled = false;
+      postGetConfiguredNamespacesAndTablesInRSGroupCalled = false;
     }
 
     @Override
@@ -483,6 +499,29 @@ public abstract class TestRSGroupsBase {
       final Address server) throws IOException {
       postGetRSGroupInfoOfServerCalled = true;
     }
-  }
 
+    @Override
+    public void preListTablesInRSGroup(ObserverContext<MasterCoprocessorEnvironment> ctx,
+      String groupName) throws IOException {
+      preListTablesInRSGroupCalled = true;
+    }
+
+    @Override
+    public void postListTablesInRSGroup(ObserverContext<MasterCoprocessorEnvironment> ctx,
+      String groupName) throws IOException {
+      postListTablesInRSGroupCalled = true;
+    }
+
+    @Override
+    public void preGetConfiguredNamespacesAndTablesInRSGroup(
+      ObserverContext<MasterCoprocessorEnvironment> ctx, String groupName) throws IOException {
+      preGetConfiguredNamespacesAndTablesInRSGroupCalled = true;
+    }
+
+    @Override
+    public void postGetConfiguredNamespacesAndTablesInRSGroup(
+      ObserverContext<MasterCoprocessorEnvironment> ctx, String groupName) throws IOException {
+      postGetConfiguredNamespacesAndTablesInRSGroupCalled = true;
+    }
+  }
 }
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsKillRS.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsKillRS.java
index d6eaeb2..b4b62ce 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsKillRS.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsKillRS.java
@@ -164,7 +164,8 @@ public class TestRSGroupsKillRS extends TestRSGroupsBase {
     Set<TableName> toAddTables = new HashSet<>();
     toAddTables.add(tableName);
     ADMIN.setRSGroup(toAddTables, groupName);
-    assertTrue(RS_GROUP_ADMIN_CLIENT.getRSGroupInfo(groupName).getTables().contains(tableName));
+    assertTrue(
+      ADMIN.getConfiguredNamespacesAndTablesInRSGroup(groupName).getSecond().contains(tableName));
     TEST_UTIL.waitTableAvailable(tableName, 30000);
 
     // check my_group servers and table regions
@@ -241,7 +242,7 @@ public class TestRSGroupsKillRS extends TestRSGroupsBase {
     Set<TableName> toAddTables = new HashSet<>();
     toAddTables.add(TableName.META_TABLE_NAME);
     ADMIN.setRSGroup(toAddTables, groupName);
-    assertTrue(RS_GROUP_ADMIN_CLIENT.getRSGroupInfo(groupName).getTables()
+    assertTrue(ADMIN.getConfiguredNamespacesAndTablesInRSGroup(groupName).getSecond()
       .contains(TableName.META_TABLE_NAME));
 
     // restart the regionserver in meta_group, and lower its version
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/VerifyingRSGroupAdmin.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/VerifyingRSGroupAdmin.java
index 6fe2c0a..683b256 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/VerifyingRSGroupAdmin.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/VerifyingRSGroupAdmin.java
@@ -76,6 +76,7 @@ import org.apache.hadoop.hbase.snapshot.HBaseSnapshotException;
 import org.apache.hadoop.hbase.snapshot.RestoreSnapshotException;
 import org.apache.hadoop.hbase.snapshot.SnapshotCreationException;
 import org.apache.hadoop.hbase.snapshot.UnknownSnapshotException;
+import org.apache.hadoop.hbase.util.Pair;
 import org.apache.hadoop.hbase.zookeeper.ZKUtil;
 import org.apache.hadoop.hbase.zookeeper.ZKWatcher;
 import org.apache.hadoop.hbase.zookeeper.ZNodePaths;
@@ -777,6 +778,17 @@ public class VerifyingRSGroupAdmin implements Admin, Closeable {
     return admin.listRSGroups();
   }
 
+  @Override
+  public List<TableName> listTablesInRSGroup(String groupName) throws IOException {
+    return admin.listTablesInRSGroup(groupName);
+  }
+
+  @Override
+  public Pair<List<String>, List<TableName>>
+    getConfiguredNamespacesAndTablesInRSGroup(String groupName) throws IOException {
+    return admin.getConfiguredNamespacesAndTablesInRSGroup(groupName);
+  }
+
   public void removeRSGroup(String groupName) throws IOException {
     admin.removeRSGroup(groupName);
     verify();
diff --git a/hbase-thrift/src/main/java/org/apache/hadoop/hbase/thrift2/client/ThriftAdmin.java b/hbase-thrift/src/main/java/org/apache/hadoop/hbase/thrift2/client/ThriftAdmin.java
index a222c74..fa20318 100644
--- a/hbase-thrift/src/main/java/org/apache/hadoop/hbase/thrift2/client/ThriftAdmin.java
+++ b/hbase-thrift/src/main/java/org/apache/hadoop/hbase/thrift2/client/ThriftAdmin.java
@@ -69,6 +69,7 @@ import org.apache.hadoop.hbase.thrift2.generated.TNamespaceDescriptor;
 import org.apache.hadoop.hbase.thrift2.generated.TTableDescriptor;
 import org.apache.hadoop.hbase.thrift2.generated.TTableName;
 import org.apache.hadoop.hbase.util.Bytes;
+import org.apache.hadoop.hbase.util.Pair;
 import org.apache.thrift.TException;
 import org.apache.thrift.transport.TTransport;
 import org.apache.yetus.audience.InterfaceAudience;
@@ -1213,4 +1214,15 @@ public class ThriftAdmin implements Admin {
   public Future<Void> splitRegionAsync(byte[] regionName) throws IOException {
     return splitRegionAsync(regionName, null);
   }
+
+  @Override
+  public List<TableName> listTablesInRSGroup(String groupName) throws IOException {
+    throw new NotImplementedException("setRSGroup not supported in ThriftAdmin");
+  }
+
+  @Override
+  public Pair<List<String>, List<TableName>>
+    getConfiguredNamespacesAndTablesInRSGroup(String groupName) throws IOException {
+    throw new NotImplementedException("setRSGroup not supported in ThriftAdmin");
+  }
 }


[hbase] 05/08: HBASE-23253 Rewrite rsgroup related UTs with the new methods introduced in HBASE-22932 (#813)

Posted by zh...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

zhangduo pushed a commit to branch HBASE-22514
in repository https://gitbox.apache.org/repos/asf/hbase.git

commit 57804ae589009878f94f4dd0c9b84247246d3c71
Author: Duo Zhang <zh...@apache.org>
AuthorDate: Fri Nov 15 18:56:01 2019 +0800

    HBASE-23253 Rewrite rsgroup related UTs with the new methods introduced in HBASE-22932 (#813)
    
    Signed-off-by: Guanghao Zhang <zg...@apache.org>
---
 .../hbase/testclassification/RSGroupTests.java     |   24 +
 .../java/org/apache/hadoop/hbase/client/Admin.java |    4 +-
 .../hadoop/hbase/client/AdminOverAsyncAdmin.java   |    8 +-
 .../org/apache/hadoop/hbase/client/AsyncAdmin.java |    4 +-
 .../hadoop/hbase/client/AsyncHBaseAdmin.java       |    8 +-
 .../hadoop/hbase/client/RawAsyncHBaseAdmin.java    |   93 +-
 .../hbase/rsgroup/IntegrationTestRSGroup.java      |   27 +-
 .../src/main/protobuf/Master.proto                 |    9 +
 .../hadoop/hbase/master/MasterRpcServices.java     |   94 ++
 .../apache/hadoop/hbase/rsgroup/RSGroupAdmin.java  |   82 --
 .../hadoop/hbase/rsgroup/RSGroupAdminClient.java   | 1067 +++-----------------
 ...leRSGroups.java => EnableRSGroupsTestBase.java} |   28 +-
 .../hadoop/hbase/rsgroup/TestEnableRSGroups.java   |   71 +-
 .../rsgroup/TestEnableRSGroupsCompatibility.java   |   45 +
 .../hbase/rsgroup/TestMigrateRSGroupInfo.java      |   15 +-
 .../rsgroup/TestRSGroupMajorCompactionTTL.java     |   16 +-
 .../hadoop/hbase/rsgroup/TestRSGroupsAdmin1.java   |  241 ++---
 .../hadoop/hbase/rsgroup/TestRSGroupsAdmin2.java   |  275 +++--
 .../hadoop/hbase/rsgroup/TestRSGroupsBalance.java  |   54 +-
 .../hadoop/hbase/rsgroup/TestRSGroupsBase.java     |  271 ++---
 .../hadoop/hbase/rsgroup/TestRSGroupsBasics.java   |   69 +-
 .../hbase/rsgroup/TestRSGroupsCPHookCalled.java    |   91 ++
 .../hadoop/hbase/rsgroup/TestRSGroupsKillRS.java   |  108 +-
 .../hbase/rsgroup/TestRSGroupsOfflineMode.java     |   39 +-
 .../hadoop/hbase/rsgroup/TestRSGroupsWithACL.java  |   19 +-
 .../hbase/rsgroup/VerifyingRSGroupAdmin.java}      |  835 ++++++---------
 .../hbase/rsgroup/VerifyingRSGroupAdminClient.java |  193 ----
 .../hadoop/hbase/thrift2/client/ThriftAdmin.java   |    4 +-
 pom.xml                                            |   15 +
 29 files changed, 1263 insertions(+), 2546 deletions(-)

diff --git a/hbase-annotations/src/test/java/org/apache/hadoop/hbase/testclassification/RSGroupTests.java b/hbase-annotations/src/test/java/org/apache/hadoop/hbase/testclassification/RSGroupTests.java
new file mode 100644
index 0000000..80b04eb
--- /dev/null
+++ b/hbase-annotations/src/test/java/org/apache/hadoop/hbase/testclassification/RSGroupTests.java
@@ -0,0 +1,24 @@
+/**
+ * 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.testclassification;
+
+/**
+ * Tag the tests related to rs group feature.
+ */
+public interface RSGroupTests {
+}
diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/Admin.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/Admin.java
index 2b4eaf8..184d588 100644
--- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/Admin.java
+++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/Admin.java
@@ -2315,7 +2315,7 @@ public interface Admin extends Abortable, Closeable {
    * @param servers set of servers to remove
    * @throws IOException if a remote or network exception occurs
    */
-  void removeRSGroup(Set<Address> servers) throws IOException;
+  void removeServersFromRSGroup(Set<Address> servers) throws IOException;
 
   /**
    * Move given set of servers to the specified target RegionServer group
@@ -2323,7 +2323,7 @@ public interface Admin extends Abortable, Closeable {
    * @param targetGroup the group to move servers to
    * @throws IOException if a remote or network exception occurs
    */
-  void moveToRSGroup(Set<Address> servers, String targetGroup) throws IOException;
+  void moveServersToRSGroup(Set<Address> servers, String targetGroup) throws IOException;
 
   /**
    * Set the RegionServer group for tables
diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AdminOverAsyncAdmin.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AdminOverAsyncAdmin.java
index 060170c..9ebc593 100644
--- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AdminOverAsyncAdmin.java
+++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AdminOverAsyncAdmin.java
@@ -968,8 +968,8 @@ class AdminOverAsyncAdmin implements Admin {
   }
 
   @Override
-  public void moveToRSGroup(Set<Address> servers, String groupName) throws IOException {
-    get(admin.moveToRSGroup(servers, groupName));
+  public void moveServersToRSGroup(Set<Address> servers, String groupName) throws IOException {
+    get(admin.moveServersToRSGroup(servers, groupName));
   }
 
   @Override
@@ -998,8 +998,8 @@ class AdminOverAsyncAdmin implements Admin {
   }
 
   @Override
-  public void removeRSGroup(Set<Address> servers) throws IOException {
-    get(admin.removeRSGroup(servers));
+  public void removeServersFromRSGroup(Set<Address> servers) throws IOException {
+    get(admin.removeServersFromRSGroup(servers));
   }
 
   @Override
diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AsyncAdmin.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AsyncAdmin.java
index 12926cc..07cce90 100644
--- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AsyncAdmin.java
+++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AsyncAdmin.java
@@ -1560,7 +1560,7 @@ public interface AsyncAdmin {
    * @param servers set of servers to remove
    * @throws IOException if a remote or network exception occurs
    */
-  CompletableFuture<Void> removeRSGroup(Set<Address> servers);
+  CompletableFuture<Void> removeServersFromRSGroup(Set<Address> servers);
 
   /**
    * Move given set of servers to the specified target RegionServer group
@@ -1568,7 +1568,7 @@ public interface AsyncAdmin {
    * @param groupName the group to move servers to
    * @throws IOException if a remote or network exception occurs
    */
-  CompletableFuture<Void> moveToRSGroup(Set<Address> servers, String groupName);
+  CompletableFuture<Void> moveServersToRSGroup(Set<Address> servers, String groupName);
 
   /**
    * Set the RegionServer group for tables
diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AsyncHBaseAdmin.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AsyncHBaseAdmin.java
index 459c225..6bf2504 100644
--- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AsyncHBaseAdmin.java
+++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/AsyncHBaseAdmin.java
@@ -845,8 +845,8 @@ class AsyncHBaseAdmin implements AsyncAdmin {
   }
 
   @Override
-  public CompletableFuture<Void> moveToRSGroup(Set<Address> servers, String groupName) {
-    return wrap(rawAdmin.moveToRSGroup(servers, groupName));
+  public CompletableFuture<Void> moveServersToRSGroup(Set<Address> servers, String groupName) {
+    return wrap(rawAdmin.moveServersToRSGroup(servers, groupName));
   }
 
   @Override
@@ -875,8 +875,8 @@ class AsyncHBaseAdmin implements AsyncAdmin {
   }
 
   @Override
-  public CompletableFuture<Void> removeRSGroup(Set<Address> servers) {
-    return wrap(rawAdmin.removeRSGroup(servers));
+  public CompletableFuture<Void> removeServersFromRSGroup(Set<Address> servers) {
+    return wrap(rawAdmin.removeServersFromRSGroup(servers));
   }
 
   @Override
diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/RawAsyncHBaseAdmin.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/RawAsyncHBaseAdmin.java
index 8360c6f..5376820 100644
--- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/RawAsyncHBaseAdmin.java
+++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/RawAsyncHBaseAdmin.java
@@ -289,6 +289,12 @@ import org.apache.hadoop.hbase.shaded.protobuf.generated.RSGroupAdminProtos.AddR
 import org.apache.hadoop.hbase.shaded.protobuf.generated.RSGroupAdminProtos.AddRSGroupResponse;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.RSGroupAdminProtos.BalanceRSGroupRequest;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.RSGroupAdminProtos.BalanceRSGroupResponse;
+import org.apache.hadoop.hbase.shaded.protobuf.generated.RSGroupAdminProtos.GetRSGroupInfoOfServerRequest;
+import org.apache.hadoop.hbase.shaded.protobuf.generated.RSGroupAdminProtos.GetRSGroupInfoOfServerResponse;
+import org.apache.hadoop.hbase.shaded.protobuf.generated.RSGroupAdminProtos.GetRSGroupInfoOfTableRequest;
+import org.apache.hadoop.hbase.shaded.protobuf.generated.RSGroupAdminProtos.GetRSGroupInfoOfTableResponse;
+import org.apache.hadoop.hbase.shaded.protobuf.generated.RSGroupAdminProtos.GetRSGroupInfoRequest;
+import org.apache.hadoop.hbase.shaded.protobuf.generated.RSGroupAdminProtos.GetRSGroupInfoResponse;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.RSGroupAdminProtos.ListRSGroupInfosRequest;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.RSGroupAdminProtos.ListRSGroupInfosResponse;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.RSGroupAdminProtos.MoveServersRequest;
@@ -3896,7 +3902,7 @@ class RawAsyncHBaseAdmin implements AsyncAdmin {
   }
 
   @Override
-  public CompletableFuture<Void> moveToRSGroup(Set<Address> servers, String groupName) {
+  public CompletableFuture<Void> moveServersToRSGroup(Set<Address> servers, String groupName) {
     return this.<Void> newMasterCaller()
         .action((controller, stub) -> this.
             <MoveServersRequest, MoveServersResponse, Void> call(controller, stub,
@@ -3950,27 +3956,23 @@ class RawAsyncHBaseAdmin implements AsyncAdmin {
 
   @Override
   public CompletableFuture<RSGroupInfo> getRSGroup(Address hostPort) {
-    CompletableFuture<RSGroupInfo> future = new CompletableFuture<>();
-    addListener(listRSGroups(), (groups, err) -> {
-      if (err != null) {
-        future.completeExceptionally(err);
-        return;
-      }
-      for (RSGroupInfo rsGroupInfo : groups) {
-        if (rsGroupInfo.getServers().contains(hostPort)){
-          future.complete(rsGroupInfo);
-          return;
-        }
-      }
-      future.complete(null);
-    });
-    return future;
+    return this.<RSGroupInfo> newMasterCaller()
+      .action(((controller, stub) -> this
+        .<GetRSGroupInfoOfServerRequest, GetRSGroupInfoOfServerResponse, RSGroupInfo> call(
+          controller, stub,
+          GetRSGroupInfoOfServerRequest.newBuilder()
+            .setServer(HBaseProtos.ServerName.newBuilder().setHostName(hostPort.getHostname())
+              .setPort(hostPort.getPort()).build())
+            .build(),
+          (s, c, req, done) -> s.getRSGroupInfoOfServer(c, req, done),
+          resp -> resp.hasRSGroupInfo() ? ProtobufUtil.toGroupInfo(resp.getRSGroupInfo()) : null)))
+      .call();
   }
 
   @Override
-  public CompletableFuture<Void> removeRSGroup(Set<Address> servers) {
+  public CompletableFuture<Void> removeServersFromRSGroup(Set<Address> servers) {
     return this.<Void> newMasterCaller()
-        .action((controller, stub) -> this.
+      .action((controller, stub) -> this.
             <RemoveServersRequest, RemoveServersResponse, Void> call(controller, stub,
                 RequestConverter.buildRemoveServersRequest(servers),
               (s, c, req, done) -> s.removeServers(c, req, done), resp -> null))
@@ -4021,49 +4023,24 @@ class RawAsyncHBaseAdmin implements AsyncAdmin {
 
   @Override
   public CompletableFuture<RSGroupInfo> getRSGroup(TableName table) {
-    CompletableFuture<RSGroupInfo> future = new CompletableFuture<>();
-    addListener(getDescriptor(table), (td, err) -> {
-      if (err != null) {
-        // return null instead of err to keep compatible with old semantics
-        // todo: need to change both this and UTs
-        future.complete(null);
-        return;
-      }
-      addListener(listRSGroups(), (groups, err2) -> {
-        if (err2 != null) {
-          future.completeExceptionally(err2);
-          return;
-        }
-        for (RSGroupInfo rsGroupInfo : groups) {
-          if (rsGroupInfo.getTables().contains(table)) {
-            future.complete(rsGroupInfo);
-            return;
-          }
-        }
-        future.complete(null);
-      });
-    });
-    return future;
+    return this.<RSGroupInfo> newMasterCaller().action(((controller, stub) -> this
+      .<GetRSGroupInfoOfTableRequest, GetRSGroupInfoOfTableResponse, RSGroupInfo> call(controller,
+        stub,
+        GetRSGroupInfoOfTableRequest.newBuilder().setTableName(ProtobufUtil.toProtoTableName(table))
+          .build(),
+        (s, c, req, done) -> s.getRSGroupInfoOfTable(c, req, done),
+        resp -> resp.hasRSGroupInfo() ? ProtobufUtil.toGroupInfo(resp.getRSGroupInfo()) : null)))
+      .call();
   }
 
   @Override
   public CompletableFuture<RSGroupInfo> getRSGroup(String groupName) {
-    CompletableFuture<RSGroupInfo> future = new CompletableFuture<>();
-    addListener(listRSGroups(), (groups, err) -> {
-      if (err != null) {
-        future.completeExceptionally(err);
-        return;
-      }
-      for (RSGroupInfo rsGroupInfo : groups) {
-        if (rsGroupInfo.getName().equals(groupName)){
-          future.complete(rsGroupInfo);
-          return;
-        }
-      }
-      future.complete(null);
-    });
-    return future;
+    return this.<RSGroupInfo> newMasterCaller()
+      .action(((controller, stub) -> this
+        .<GetRSGroupInfoRequest, GetRSGroupInfoResponse, RSGroupInfo> call(controller, stub,
+          GetRSGroupInfoRequest.newBuilder().setRSGroupName(groupName).build(),
+          (s, c, req, done) -> s.getRSGroupInfo(c, req, done),
+          resp -> resp.hasRSGroupInfo() ? ProtobufUtil.toGroupInfo(resp.getRSGroupInfo()) : null)))
+      .call();
   }
-
-
 }
diff --git a/hbase-it/src/test/java/org/apache/hadoop/hbase/rsgroup/IntegrationTestRSGroup.java b/hbase-it/src/test/java/org/apache/hadoop/hbase/rsgroup/IntegrationTestRSGroup.java
index ef3a93a..551d280 100644
--- a/hbase-it/src/test/java/org/apache/hadoop/hbase/rsgroup/IntegrationTestRSGroup.java
+++ b/hbase-it/src/test/java/org/apache/hadoop/hbase/rsgroup/IntegrationTestRSGroup.java
@@ -41,7 +41,7 @@ public class IntegrationTestRSGroup extends TestRSGroupsBase {
 
   @Before
   public void beforeMethod() throws Exception {
-    if(!initialized) {
+    if (!initialized) {
       LOG.info("Setting up IntegrationTestRSGroup");
       LOG.info("Initializing cluster with " + NUM_SLAVES_BASE + " servers");
       TEST_UTIL = new IntegrationTestingUtility();
@@ -49,16 +49,15 @@ public class IntegrationTestRSGroup extends TestRSGroupsBase {
         RSGroupBasedLoadBalancer.class.getName());
       TEST_UTIL.getConfiguration().set(CoprocessorHost.MASTER_COPROCESSOR_CONF_KEY,
         RSGroupAdminEndpoint.class.getName());
-      ((IntegrationTestingUtility)TEST_UTIL).initializeCluster(NUM_SLAVES_BASE);
-      //set shared configs
-      admin = TEST_UTIL.getAdmin();
-      cluster = TEST_UTIL.getHBaseClusterInterface();
-      rsGroupAdmin = new VerifyingRSGroupAdminClient(new RSGroupAdminClient(TEST_UTIL.getConnection()),
-          TEST_UTIL.getConfiguration());
+      ((IntegrationTestingUtility) TEST_UTIL).initializeCluster(NUM_SLAVES_BASE);
+      // set shared configs
+      ADMIN = TEST_UTIL.getAdmin();
+      CLUSTER = TEST_UTIL.getHBaseClusterInterface();
+      RS_GROUP_ADMIN_CLIENT = new RSGroupAdminClient(TEST_UTIL.getConnection());
       LOG.info("Done initializing cluster");
       initialized = true;
-      //cluster may not be clean
-      //cleanup when initializing
+      // cluster may not be clean
+      // cleanup when initializing
       afterMethod();
     }
   }
@@ -70,7 +69,7 @@ public class IntegrationTestRSGroup extends TestRSGroupsBase {
     deleteTableIfNecessary();
     deleteNamespaceIfNecessary();
     deleteGroups();
-    admin.balancerSwitch(true, true);
+    ADMIN.balancerSwitch(true, true);
 
     LOG.info("Restoring the cluster");
     ((IntegrationTestingUtility)TEST_UTIL).restoreCluster();
@@ -79,10 +78,10 @@ public class IntegrationTestRSGroup extends TestRSGroupsBase {
     TEST_UTIL.waitFor(WAIT_TIMEOUT, new Waiter.Predicate<Exception>() {
       @Override
       public boolean evaluate() throws Exception {
-        LOG.info("Waiting for cleanup to finish "+ rsGroupAdmin.listRSGroups());
+        LOG.info("Waiting for cleanup to finish "+ ADMIN.listRSGroups());
         //Might be greater since moving servers back to default
         //is after starting a server
-        return rsGroupAdmin.getRSGroup(RSGroupInfo.DEFAULT_GROUP).getServers().size()
+        return ADMIN.getRSGroup(RSGroupInfo.DEFAULT_GROUP).getServers().size()
             >= NUM_SLAVES_BASE;
       }
     });
@@ -90,10 +89,10 @@ public class IntegrationTestRSGroup extends TestRSGroupsBase {
     TEST_UTIL.waitFor(WAIT_TIMEOUT, new Waiter.Predicate<Exception>() {
       @Override
       public boolean evaluate() throws Exception {
-        LOG.info("Waiting for regionservers to be registered "+ rsGroupAdmin.listRSGroups());
+        LOG.info("Waiting for regionservers to be registered "+ ADMIN.listRSGroups());
         //Might be greater since moving servers back to default
         //is after starting a server
-        return rsGroupAdmin.getRSGroup(RSGroupInfo.DEFAULT_GROUP).getServers().size()
+        return ADMIN.getRSGroup(RSGroupInfo.DEFAULT_GROUP).getServers().size()
             == getNumServers();
       }
     });
diff --git a/hbase-protocol-shaded/src/main/protobuf/Master.proto b/hbase-protocol-shaded/src/main/protobuf/Master.proto
index 0eb7a3b..c67dfab 100644
--- a/hbase-protocol-shaded/src/main/protobuf/Master.proto
+++ b/hbase-protocol-shaded/src/main/protobuf/Master.proto
@@ -1087,6 +1087,15 @@ service MasterService {
   rpc ListNamespaces(ListNamespacesRequest)
     returns(ListNamespacesResponse);
 
+  rpc GetRSGroupInfo(GetRSGroupInfoRequest)
+    returns (GetRSGroupInfoResponse);
+
+  rpc GetRSGroupInfoOfTable(GetRSGroupInfoOfTableRequest)
+    returns (GetRSGroupInfoOfTableResponse);
+
+  rpc GetRSGroupInfoOfServer(GetRSGroupInfoOfServerRequest)
+    returns (GetRSGroupInfoOfServerResponse);
+
   rpc MoveServers(MoveServersRequest)
     returns (MoveServersResponse);
 
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/MasterRpcServices.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/MasterRpcServices.java
index 51cba6f..39edc42 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/MasterRpcServices.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/MasterRpcServices.java
@@ -311,6 +311,12 @@ import org.apache.hadoop.hbase.shaded.protobuf.generated.RSGroupAdminProtos.AddR
 import org.apache.hadoop.hbase.shaded.protobuf.generated.RSGroupAdminProtos.AddRSGroupResponse;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.RSGroupAdminProtos.BalanceRSGroupRequest;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.RSGroupAdminProtos.BalanceRSGroupResponse;
+import org.apache.hadoop.hbase.shaded.protobuf.generated.RSGroupAdminProtos.GetRSGroupInfoOfServerRequest;
+import org.apache.hadoop.hbase.shaded.protobuf.generated.RSGroupAdminProtos.GetRSGroupInfoOfServerResponse;
+import org.apache.hadoop.hbase.shaded.protobuf.generated.RSGroupAdminProtos.GetRSGroupInfoOfTableRequest;
+import org.apache.hadoop.hbase.shaded.protobuf.generated.RSGroupAdminProtos.GetRSGroupInfoOfTableResponse;
+import org.apache.hadoop.hbase.shaded.protobuf.generated.RSGroupAdminProtos.GetRSGroupInfoRequest;
+import org.apache.hadoop.hbase.shaded.protobuf.generated.RSGroupAdminProtos.GetRSGroupInfoResponse;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.RSGroupAdminProtos.ListRSGroupInfosRequest;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.RSGroupAdminProtos.ListRSGroupInfosResponse;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.RSGroupAdminProtos.MoveServersRequest;
@@ -2914,6 +2920,94 @@ public class MasterRpcServices extends RSRpcServices implements MasterService.Bl
   }
 
   @Override
+  public GetRSGroupInfoResponse getRSGroupInfo(RpcController controller,
+    GetRSGroupInfoRequest request) throws ServiceException {
+    String groupName = request.getRSGroupName();
+    LOG.info(
+      master.getClientIdAuditPrefix() + " initiates rsgroup info retrieval, group=" + groupName);
+    try {
+      if (master.getMasterCoprocessorHost() != null) {
+        master.getMasterCoprocessorHost().preGetRSGroupInfo(groupName);
+      }
+      RSGroupInfo rsGroupInfo = master.getRSGroupInfoManager().getRSGroup(groupName);
+      GetRSGroupInfoResponse resp;
+      if (rsGroupInfo != null) {
+        resp = GetRSGroupInfoResponse.newBuilder()
+          .setRSGroupInfo(ProtobufUtil.toProtoGroupInfo(rsGroupInfo)).build();
+      } else {
+        resp = GetRSGroupInfoResponse.getDefaultInstance();
+      }
+      if (master.getMasterCoprocessorHost() != null) {
+        master.getMasterCoprocessorHost().postGetRSGroupInfo(groupName);
+      }
+      return resp;
+    } catch (IOException e) {
+      throw new ServiceException(e);
+    }
+  }
+
+  @Override
+  public GetRSGroupInfoOfTableResponse getRSGroupInfoOfTable(RpcController controller,
+    GetRSGroupInfoOfTableRequest request) throws ServiceException {
+    TableName tableName = ProtobufUtil.toTableName(request.getTableName());
+    LOG.info(
+      master.getClientIdAuditPrefix() + " initiates rsgroup info retrieval, table=" + tableName);
+    try {
+      if (master.getMasterCoprocessorHost() != null) {
+        master.getMasterCoprocessorHost().preGetRSGroupInfoOfTable(tableName);
+      }
+      GetRSGroupInfoOfTableResponse resp;
+      TableDescriptor td = master.getTableDescriptors().get(tableName);
+      if (td == null) {
+        resp = GetRSGroupInfoOfTableResponse.getDefaultInstance();
+      } else {
+        RSGroupInfo rsGroupInfo = null;
+        if (td.getRegionServerGroup().isPresent()) {
+          rsGroupInfo = master.getRSGroupInfoManager().getRSGroup(td.getRegionServerGroup().get());
+        }
+        if (rsGroupInfo == null) {
+          rsGroupInfo = master.getRSGroupInfoManager().getRSGroup(RSGroupInfo.DEFAULT_GROUP);
+        }
+        resp = GetRSGroupInfoOfTableResponse.newBuilder()
+          .setRSGroupInfo(ProtobufUtil.toProtoGroupInfo(rsGroupInfo)).build();
+      }
+      if (master.getMasterCoprocessorHost() != null) {
+        master.getMasterCoprocessorHost().postGetRSGroupInfoOfTable(tableName);
+      }
+      return resp;
+    } catch (IOException e) {
+      throw new ServiceException(e);
+    }
+  }
+
+  @Override
+  public GetRSGroupInfoOfServerResponse getRSGroupInfoOfServer(RpcController controller,
+    GetRSGroupInfoOfServerRequest request) throws ServiceException {
+    Address hp =
+      Address.fromParts(request.getServer().getHostName(), request.getServer().getPort());
+    LOG.info(master.getClientIdAuditPrefix() + " initiates rsgroup info retrieval, server=" + hp);
+    try {
+      if (master.getMasterCoprocessorHost() != null) {
+        master.getMasterCoprocessorHost().preGetRSGroupInfoOfServer(hp);
+      }
+      RSGroupInfo rsGroupInfo = master.getRSGroupInfoManager().getRSGroupOfServer(hp);
+      GetRSGroupInfoOfServerResponse resp;
+      if (rsGroupInfo != null) {
+        resp = GetRSGroupInfoOfServerResponse.newBuilder()
+          .setRSGroupInfo(ProtobufUtil.toProtoGroupInfo(rsGroupInfo)).build();
+      } else {
+        resp = GetRSGroupInfoOfServerResponse.getDefaultInstance();
+      }
+      if (master.getMasterCoprocessorHost() != null) {
+        master.getMasterCoprocessorHost().postGetRSGroupInfoOfServer(hp);
+      }
+      return resp;
+    } catch (IOException e) {
+      throw new ServiceException(e);
+    }
+  }
+
+  @Override
   public MoveServersResponse moveServers(RpcController controller, MoveServersRequest request)
       throws ServiceException {
     Set<Address> hostPorts = Sets.newHashSet();
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupAdmin.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupAdmin.java
deleted file mode 100644
index 3de6965..0000000
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupAdmin.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/**
- * 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.List;
-import java.util.Set;
-import org.apache.hadoop.hbase.client.Admin;
-import org.apache.hadoop.hbase.net.Address;
-import org.apache.yetus.audience.InterfaceAudience;
-
-/**
- * Group user API interface used between client and server.
- *
- * @deprecated Keep it here only for tests, using {@link Admin} instead.
- */
-@Deprecated
-@InterfaceAudience.Private
-public interface RSGroupAdmin {
-  /**
-   * Gets {@code RSGroupInfo} for given group name.
-   */
-  RSGroupInfo getRSGroupInfo(String groupName) throws IOException;
-
-  /**
-   * Move given set of servers to the specified target RegionServer group.
-   */
-  void moveServers(Set<Address> servers, String targetGroup) throws IOException;
-
-  /**
-   * Creates a new RegionServer group with the given name.
-   */
-  void addRSGroup(String groupName) throws IOException;
-
-  /**
-   * Removes RegionServer group associated with the given name.
-   */
-  void removeRSGroup(String groupName) throws IOException;
-
-  /**
-   * Balance regions in the given RegionServer group.
-   *
-   * @return boolean Whether balance ran or not
-   */
-  boolean balanceRSGroup(String groupName) throws IOException;
-
-  /**
-   * Lists current set of RegionServer groups.
-   */
-  List<RSGroupInfo> listRSGroups() throws IOException;
-
-  /**
-   * Retrieve the RSGroupInfo a server is affiliated to
-   * @param hostPort HostPort to get RSGroupInfo for
-   */
-  RSGroupInfo getRSGroupOfServer(Address hostPort) throws IOException;
-
-  /**
-   * Remove decommissioned servers from rsgroup.
-   * 1. Sometimes we may find the server aborted due to some hardware failure and we must offline
-   * the server for repairing. Or we need to move some servers to join other clusters.
-   * So we need to remove these servers from the rsgroup.
-   * 2. Dead/recovering/live servers will be disallowed.
-   * @param servers set of servers to remove
-   */
-  void removeServers(Set<Address> servers) throws IOException;
-}
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupAdminClient.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupAdminClient.java
index 7cf4ed1..3e17234 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupAdminClient.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupAdminClient.java
@@ -20,33 +20,12 @@ package org.apache.hadoop.hbase.rsgroup;
 import com.google.protobuf.ServiceException;
 import java.io.IOException;
 import java.util.ArrayList;
-import java.util.EnumSet;
 import java.util.List;
-import java.util.Map;
 import java.util.Set;
-import java.util.concurrent.Future;
-import java.util.regex.Pattern;
-import org.apache.hadoop.conf.Configuration;
-import org.apache.hadoop.hbase.CacheEvictionStats;
-import org.apache.hadoop.hbase.ClusterMetrics;
-import org.apache.hadoop.hbase.NamespaceDescriptor;
-import org.apache.hadoop.hbase.NamespaceNotFoundException;
-import org.apache.hadoop.hbase.RegionMetrics;
-import org.apache.hadoop.hbase.ServerName;
-import org.apache.hadoop.hbase.TableExistsException;
 import org.apache.hadoop.hbase.TableName;
 import org.apache.hadoop.hbase.TableNotFoundException;
 import org.apache.hadoop.hbase.client.Admin;
-import org.apache.hadoop.hbase.client.ColumnFamilyDescriptor;
-import org.apache.hadoop.hbase.client.CompactType;
-import org.apache.hadoop.hbase.client.CompactionState;
 import org.apache.hadoop.hbase.client.Connection;
-import org.apache.hadoop.hbase.client.RegionInfo;
-import org.apache.hadoop.hbase.client.SnapshotDescription;
-import org.apache.hadoop.hbase.client.TableDescriptor;
-import org.apache.hadoop.hbase.client.replication.TableCFs;
-import org.apache.hadoop.hbase.client.security.SecurityCapability;
-import org.apache.hadoop.hbase.ipc.CoprocessorRpcChannel;
 import org.apache.hadoop.hbase.net.Address;
 import org.apache.hadoop.hbase.protobuf.ProtobufUtil;
 import org.apache.hadoop.hbase.protobuf.generated.HBaseProtos;
@@ -59,39 +38,24 @@ import org.apache.hadoop.hbase.protobuf.generated.RSGroupAdminProtos.GetRSGroupI
 import org.apache.hadoop.hbase.protobuf.generated.RSGroupAdminProtos.GetRSGroupInfoRequest;
 import org.apache.hadoop.hbase.protobuf.generated.RSGroupAdminProtos.GetRSGroupInfoResponse;
 import org.apache.hadoop.hbase.protobuf.generated.RSGroupAdminProtos.ListRSGroupInfosRequest;
+import org.apache.hadoop.hbase.protobuf.generated.RSGroupAdminProtos.MoveServersAndTablesRequest;
 import org.apache.hadoop.hbase.protobuf.generated.RSGroupAdminProtos.MoveServersRequest;
 import org.apache.hadoop.hbase.protobuf.generated.RSGroupAdminProtos.MoveTablesRequest;
 import org.apache.hadoop.hbase.protobuf.generated.RSGroupAdminProtos.RSGroupAdminService;
 import org.apache.hadoop.hbase.protobuf.generated.RSGroupAdminProtos.RemoveRSGroupRequest;
 import org.apache.hadoop.hbase.protobuf.generated.RSGroupAdminProtos.RemoveServersRequest;
 import org.apache.hadoop.hbase.protobuf.generated.RSGroupProtos;
-import org.apache.hadoop.hbase.quotas.QuotaFilter;
-import org.apache.hadoop.hbase.quotas.QuotaSettings;
-import org.apache.hadoop.hbase.quotas.SpaceQuotaSnapshotView;
-import org.apache.hadoop.hbase.regionserver.wal.FailedLogCloseException;
-import org.apache.hadoop.hbase.replication.ReplicationPeerConfig;
-import org.apache.hadoop.hbase.replication.ReplicationPeerDescription;
-import org.apache.hadoop.hbase.replication.SyncReplicationState;
-import org.apache.hadoop.hbase.security.access.GetUserPermissionsRequest;
-import org.apache.hadoop.hbase.security.access.Permission;
-import org.apache.hadoop.hbase.security.access.UserPermission;
-import org.apache.hadoop.hbase.snapshot.HBaseSnapshotException;
-import org.apache.hadoop.hbase.snapshot.RestoreSnapshotException;
-import org.apache.hadoop.hbase.snapshot.SnapshotCreationException;
-import org.apache.hadoop.hbase.snapshot.UnknownSnapshotException;
 import org.apache.yetus.audience.InterfaceAudience;
 
-import org.apache.hbase.thirdparty.com.google.common.annotations.VisibleForTesting;
 import org.apache.hbase.thirdparty.com.google.common.collect.Sets;
 
 /**
  * Client used for managing region server group information.
- *
  * @deprecated Keep it here only for tests, using {@link Admin} instead.
  */
 @Deprecated
 @InterfaceAudience.Private
-public class RSGroupAdminClient implements RSGroupAdmin, Admin {
+public class RSGroupAdminClient {
   private RSGroupAdminService.BlockingInterface stub;
   private Admin admin;
 
@@ -100,868 +64,13 @@ public class RSGroupAdminClient implements RSGroupAdmin, Admin {
     stub = RSGroupAdminService.newBlockingStub(admin.coprocessorService());
   }
 
-  // for writing UTs
-  @VisibleForTesting
-  protected RSGroupAdminClient() {
-  }
-
-  @Override
-  public int getOperationTimeout() {
-    return 0;
-  }
-
-  @Override
-  public int getSyncWaitTimeout() {
-    return 0;
-  }
-
-  @Override
-  public void abort(String why, Throwable e) {
-
-  }
-
-  @Override
-  public boolean isAborted() {
-    return false;
-  }
-
-  @Override
-  public Connection getConnection() {
-    return null;
-  }
-
-  @Override
-  public boolean tableExists(TableName tableName) throws IOException {
-    return false;
-  }
-
-  @Override
-  public List<TableDescriptor> listTableDescriptors() throws IOException {
-    return null;
-  }
-
-  @Override
-  public List<TableDescriptor> listTableDescriptors(boolean includeSysTables) throws IOException {
-    return null;
-  }
-
-  @Override
-  public List<TableDescriptor> listTableDescriptors(Pattern pattern) throws IOException {
-    return null;
-  }
-
-  @Override
-  public List<TableDescriptor> listTableDescriptors(Pattern pattern, boolean includeSysTables)
-      throws IOException {
-    return null;
-  }
-
-  @Override
-  public TableName[] listTableNames() throws IOException {
-    return new TableName[0];
-  }
-
-  @Override
-  public TableName[] listTableNames(Pattern pattern, boolean includeSysTables) throws IOException {
-    return new TableName[0];
-  }
-
-  @Override
-  public TableDescriptor getDescriptor(TableName tableName)
-      throws TableNotFoundException, IOException {
-    return null;
-  }
-
-  @Override
-  public void createTable(TableDescriptor desc, byte[] startKey, byte[] endKey, int numRegions)
-      throws IOException {
-
-  }
-
-  @Override
-  public Future<Void> createTableAsync(TableDescriptor desc) throws IOException {
-    return null;
-  }
-
-  @Override
-  public Future<Void> createTableAsync(TableDescriptor desc, byte[][] splitKeys)
-      throws IOException {
-    return null;
-  }
-
-  @Override
-  public Future<Void> deleteTableAsync(TableName tableName) throws IOException {
-    return null;
-  }
-
-  @Override
-  public Future<Void> truncateTableAsync(TableName tableName, boolean preserveSplits)
-      throws IOException {
-    return null;
-  }
-
-  @Override
-  public Future<Void> enableTableAsync(TableName tableName) throws IOException {
-    return null;
-  }
-
-  @Override
-  public Future<Void> disableTableAsync(TableName tableName) throws IOException {
-    return null;
-  }
-
-  @Override
-  public boolean isTableEnabled(TableName tableName) throws IOException {
-    return false;
-  }
-
-  @Override
-  public boolean isTableDisabled(TableName tableName) throws IOException {
-    return false;
-  }
-
-  @Override
-  public boolean isTableAvailable(TableName tableName) throws IOException {
-    return false;
-  }
-
-  @Override
-  public Future<Void> addColumnFamilyAsync(TableName tableName, ColumnFamilyDescriptor columnFamily)
-      throws IOException {
-    return null;
-  }
-
-  @Override
-  public Future<Void> deleteColumnFamilyAsync(TableName tableName, byte[] columnFamily)
-      throws IOException {
-    return null;
-  }
-
-  @Override
-  public Future<Void> modifyColumnFamilyAsync(TableName tableName,
-      ColumnFamilyDescriptor columnFamily) throws IOException {
-    return null;
-  }
-
-  @Override
-  public List<RegionInfo> getRegions(ServerName serverName) throws IOException {
-    return null;
-  }
-
-  @Override
-  public void flush(TableName tableName) throws IOException {
-
-  }
-
-  @Override
-  public void flushRegion(byte[] regionName) throws IOException {
-
-  }
-
-  @Override
-  public void flushRegionServer(ServerName serverName) throws IOException {
-
-  }
-
-  @Override
-  public void compact(TableName tableName) throws IOException {
-
-  }
-
-  @Override
-  public void compactRegion(byte[] regionName) throws IOException {
-
-  }
-
-  @Override
-  public void compact(TableName tableName, byte[] columnFamily) throws IOException {
-
-  }
-
-  @Override
-  public void compactRegion(byte[] regionName, byte[] columnFamily) throws IOException {
-
-  }
-
-  @Override
-  public void compact(TableName tableName, CompactType compactType)
-      throws IOException, InterruptedException {
-
-  }
-
-  @Override
-  public void compact(TableName tableName, byte[] columnFamily, CompactType compactType)
-      throws IOException, InterruptedException {
-
-  }
-
-  @Override
-  public void majorCompact(TableName tableName) throws IOException {
-
-  }
-
-  @Override
-  public void majorCompactRegion(byte[] regionName) throws IOException {
-
-  }
-
-  @Override
-  public void majorCompact(TableName tableName, byte[] columnFamily) throws IOException {
-
-  }
-
-  @Override
-  public void majorCompactRegion(byte[] regionName, byte[] columnFamily) throws IOException {
-
-  }
-
-  @Override
-  public void majorCompact(TableName tableName, CompactType compactType)
-      throws IOException, InterruptedException {
-
-  }
-
-  @Override
-  public void majorCompact(TableName tableName, byte[] columnFamily, CompactType compactType)
-      throws IOException, InterruptedException {
-
-  }
-
-  @Override
-  public Map<ServerName, Boolean> compactionSwitch(boolean switchState,
-      List<String> serverNamesList) throws IOException {
-    return null;
-  }
-
-  @Override
-  public void compactRegionServer(ServerName serverName) throws IOException {
-
-  }
-
-  @Override
-  public void majorCompactRegionServer(ServerName serverName) throws IOException {
-
-  }
-
-  @Override
-  public void move(byte[] encodedRegionName) throws IOException {
-
-  }
-
-  @Override
-  public void move(byte[] encodedRegionName, ServerName destServerName) throws IOException {
-
-  }
-
-  @Override
-  public void assign(byte[] regionName) throws IOException {
-
-  }
-
-  @Override
-  public void unassign(byte[] regionName, boolean force) throws IOException {
-
-  }
-
-  @Override
-  public void offline(byte[] regionName) throws IOException {
-
-  }
-
-  @Override
-  public boolean balancerSwitch(boolean onOrOff, boolean synchronous) throws IOException {
-    return false;
-  }
-
-  @Override
-  public boolean balance() throws IOException {
-    return false;
-  }
-
-  @Override
-  public boolean balance(boolean force) throws IOException {
-    return false;
-  }
-
-  @Override
-  public boolean isBalancerEnabled() throws IOException {
-    return false;
-  }
-
-  @Override
-  public CacheEvictionStats clearBlockCache(TableName tableName) throws IOException {
-    return null;
-  }
-
-  @Override
-  public boolean normalize() throws IOException {
-    return false;
-  }
-
-  @Override
-  public boolean isNormalizerEnabled() throws IOException {
-    return false;
-  }
-
-  @Override
-  public boolean normalizerSwitch(boolean on) throws IOException {
-    return false;
-  }
-
-  @Override
-  public boolean catalogJanitorSwitch(boolean onOrOff) throws IOException {
-    return false;
-  }
-
-  @Override
-  public int runCatalogJanitor() throws IOException {
-    return 0;
-  }
-
-  @Override
-  public boolean isCatalogJanitorEnabled() throws IOException {
-    return false;
-  }
-
-  @Override
-  public boolean cleanerChoreSwitch(boolean onOrOff) throws IOException {
-    return false;
-  }
-
-  @Override
-  public boolean runCleanerChore() throws IOException {
-    return false;
-  }
-
-  @Override
-  public boolean isCleanerChoreEnabled() throws IOException {
-    return false;
-  }
-
-  @Override
-  public Future<Void> mergeRegionsAsync(byte[][] nameofRegionsToMerge, boolean forcible)
-      throws IOException {
-    return null;
-  }
-
-  @Override
-  public void split(TableName tableName) throws IOException {
-
-  }
-
-  @Override
-  public void split(TableName tableName, byte[] splitPoint) throws IOException {
-
-  }
-
-  @Override
-  public Future<Void> splitRegionAsync(byte[] regionName) throws IOException {
-    return null;
-  }
-
-  @Override
-  public Future<Void> splitRegionAsync(byte[] regionName, byte[] splitPoint) throws IOException {
-    return null;
-  }
-
-  @Override
-  public Future<Void> modifyTableAsync(TableDescriptor td) throws IOException {
-    return null;
-  }
-
-  @Override
-  public void shutdown() throws IOException {
-
-  }
-
-  @Override
-  public void stopMaster() throws IOException {
-
-  }
-
-  @Override
-  public boolean isMasterInMaintenanceMode() throws IOException {
-    return false;
-  }
-
-  @Override
-  public void stopRegionServer(String hostnamePort) throws IOException {
-
-  }
-
-  @Override
-  public ClusterMetrics getClusterMetrics(EnumSet<ClusterMetrics.Option> options)
-      throws IOException {
-    return null;
-  }
-
-  @Override
-  public List<RegionMetrics> getRegionMetrics(ServerName serverName) throws IOException {
-    return null;
-  }
-
-  @Override
-  public List<RegionMetrics> getRegionMetrics(ServerName serverName, TableName tableName)
-      throws IOException {
-    return null;
-  }
-
-  @Override
-  public Configuration getConfiguration() {
-    return null;
-  }
-
-  @Override
-  public Future<Void> createNamespaceAsync(NamespaceDescriptor descriptor) throws IOException {
-    return null;
-  }
-
-  @Override
-  public Future<Void> modifyNamespaceAsync(NamespaceDescriptor descriptor) throws IOException {
-    return null;
-  }
-
-  @Override
-  public Future<Void> deleteNamespaceAsync(String name) throws IOException {
-    return null;
-  }
-
-  @Override
-  public NamespaceDescriptor getNamespaceDescriptor(String name)
-      throws NamespaceNotFoundException, IOException {
-    return null;
-  }
-
-  @Override
-  public String[] listNamespaces() throws IOException {
-    return new String[0];
-  }
-
-  @Override
-  public NamespaceDescriptor[] listNamespaceDescriptors() throws IOException {
-    return new NamespaceDescriptor[0];
-  }
-
-  @Override
-  public List<TableDescriptor> listTableDescriptorsByNamespace(byte[] name) throws IOException {
-    return null;
-  }
-
-  @Override
-  public TableName[] listTableNamesByNamespace(String name) throws IOException {
-    return new TableName[0];
-  }
-
-  @Override
-  public List<RegionInfo> getRegions(TableName tableName) throws IOException {
-    return null;
-  }
-
-  @Override
-  public void close() {
-
-  }
-
-  @Override
-  public List<TableDescriptor> listTableDescriptors(List<TableName> tableNames) throws IOException {
-    return null;
-  }
-
-  @Override
-  public Future<Boolean> abortProcedureAsync(long procId, boolean mayInterruptIfRunning)
-      throws IOException {
-    return null;
-  }
-
-  @Override
-  public String getProcedures() throws IOException {
-    return null;
-  }
-
-  @Override
-  public String getLocks() throws IOException {
-    return null;
-  }
-
-  @Override
-  public void rollWALWriter(ServerName serverName) throws IOException, FailedLogCloseException {
-
-  }
-
-  @Override
-  public CompactionState getCompactionState(TableName tableName) throws IOException {
-    return null;
-  }
-
-  @Override
-  public CompactionState getCompactionState(TableName tableName, CompactType compactType)
-      throws IOException {
-    return null;
-  }
-
-  @Override
-  public CompactionState getCompactionStateForRegion(byte[] regionName) throws IOException {
-    return null;
-  }
-
-  @Override
-  public long getLastMajorCompactionTimestamp(TableName tableName) throws IOException {
-    return 0;
-  }
-
-  @Override
-  public long getLastMajorCompactionTimestampForRegion(byte[] regionName) throws IOException {
-    return 0;
-  }
-
-  @Override
-  public void snapshot(SnapshotDescription snapshot)
-      throws IOException, SnapshotCreationException, IllegalArgumentException {
-
-  }
-
-  @Override
-  public Future<Void> snapshotAsync(SnapshotDescription snapshot)
-      throws IOException, SnapshotCreationException {
-    return null;
-  }
-
-  @Override
-  public boolean isSnapshotFinished(SnapshotDescription snapshot)
-      throws IOException, HBaseSnapshotException, UnknownSnapshotException {
-    return false;
-  }
-
-  @Override
-  public void restoreSnapshot(String snapshotName) throws IOException, RestoreSnapshotException {
-
-  }
-
-  @Override
-  public void restoreSnapshot(String snapshotName, boolean takeFailSafeSnapshot, boolean restoreAcl)
-      throws IOException, RestoreSnapshotException {
-
-  }
-
-  @Override
-  public Future<Void> cloneSnapshotAsync(String snapshotName, TableName tableName,
-      boolean restoreAcl) throws IOException, TableExistsException, RestoreSnapshotException {
-    return null;
-  }
-
-  @Override
-  public void execProcedure(String signature, String instance, Map<String, String> props)
-      throws IOException {
-
-  }
-
-  @Override
-  public byte[] execProcedureWithReturn(String signature, String instance,
-      Map<String, String> props) throws IOException {
-    return new byte[0];
-  }
-
-  @Override
-  public boolean isProcedureFinished(String signature, String instance, Map<String, String> props)
-      throws IOException {
-    return false;
-  }
-
-  @Override
-  public List<SnapshotDescription> listSnapshots() throws IOException {
-    return null;
-  }
-
-  @Override
-  public List<SnapshotDescription> listSnapshots(Pattern pattern) throws IOException {
-    return null;
-  }
-
-  @Override
-  public List<SnapshotDescription> listTableSnapshots(Pattern tableNamePattern,
-      Pattern snapshotNamePattern) throws IOException {
-    return null;
-  }
-
-  @Override
-  public void deleteSnapshot(String snapshotName) throws IOException {
-
-  }
-
-  @Override
-  public void deleteSnapshots(Pattern pattern) throws IOException {
-
-  }
-
-  @Override
-  public void deleteTableSnapshots(Pattern tableNamePattern, Pattern snapshotNamePattern)
-      throws IOException {
-
-  }
-
-  @Override
-  public void setQuota(QuotaSettings quota) throws IOException {
-
-  }
-
-  @Override
-  public List<QuotaSettings> getQuota(QuotaFilter filter) throws IOException {
-    return null;
-  }
-
-  @Override
-  public CoprocessorRpcChannel coprocessorService() {
-    return null;
-  }
-
-  @Override
-  public CoprocessorRpcChannel coprocessorService(ServerName serverName) {
-    return null;
-  }
-
-  @Override
-  public void updateConfiguration(ServerName server) throws IOException {
-
-  }
-
-  @Override
-  public void updateConfiguration() throws IOException {
-
-  }
-
-  @Override
-  public List<SecurityCapability> getSecurityCapabilities() throws IOException {
-    return null;
-  }
-
-  @Override
-  public boolean splitSwitch(boolean enabled, boolean synchronous) throws IOException {
-    return false;
-  }
-
-  @Override
-  public boolean mergeSwitch(boolean enabled, boolean synchronous) throws IOException {
-    return false;
-  }
-
-  @Override
-  public boolean isSplitEnabled() throws IOException {
-    return false;
-  }
-
-  @Override
-  public boolean isMergeEnabled() throws IOException {
-    return false;
-  }
-
-  @Override
-  public Future<Void> addReplicationPeerAsync(String peerId, ReplicationPeerConfig peerConfig,
-      boolean enabled) throws IOException {
-    return null;
-  }
-
-  @Override
-  public Future<Void> removeReplicationPeerAsync(String peerId) throws IOException {
-    return null;
-  }
-
-  @Override
-  public Future<Void> enableReplicationPeerAsync(String peerId) throws IOException {
-    return null;
-  }
-
-  @Override
-  public Future<Void> disableReplicationPeerAsync(String peerId) throws IOException {
-    return null;
-  }
-
-  @Override
-  public ReplicationPeerConfig getReplicationPeerConfig(String peerId) throws IOException {
-    return null;
-  }
-
-  @Override
-  public Future<Void> updateReplicationPeerConfigAsync(String peerId,
-      ReplicationPeerConfig peerConfig) throws IOException {
-    return null;
-  }
-
-  @Override
-  public List<ReplicationPeerDescription> listReplicationPeers() throws IOException {
-    return null;
-  }
-
-  @Override
-  public List<ReplicationPeerDescription> listReplicationPeers(Pattern pattern) throws IOException {
-    return null;
-  }
-
-  @Override
-  public Future<Void> transitReplicationPeerSyncReplicationStateAsync(String peerId,
-      SyncReplicationState state) throws IOException {
-    return null;
-  }
-
-  @Override
-  public void decommissionRegionServers(List<ServerName> servers, boolean offload)
-      throws IOException {
-
-  }
-
-  @Override
-  public List<ServerName> listDecommissionedRegionServers() throws IOException {
-    return null;
-  }
-
-  @Override
-  public void recommissionRegionServer(ServerName server, List<byte[]> encodedRegionNames)
-      throws IOException {
-
-  }
-
-  @Override
-  public List<TableCFs> listReplicatedTableCFs() throws IOException {
-    return null;
-  }
-
-  @Override
-  public void enableTableReplication(TableName tableName) throws IOException {
-
-  }
-
-  @Override
-  public void disableTableReplication(TableName tableName) throws IOException {
-
-  }
-
-  @Override
-  public void clearCompactionQueues(ServerName serverName, Set<String> queues)
-      throws IOException, InterruptedException {
-
-  }
-
-  @Override
-  public List<ServerName> clearDeadServers(List<ServerName> servers) throws IOException {
-    return null;
-  }
-
-  @Override
-  public void cloneTableSchema(TableName tableName, TableName newTableName, boolean preserveSplits)
-      throws IOException {
-
-  }
-
-  @Override
-  public boolean switchRpcThrottle(boolean enable) throws IOException {
-    return false;
-  }
-
-  @Override
-  public boolean isRpcThrottleEnabled() throws IOException {
-    return false;
-  }
-
-  @Override
-  public boolean exceedThrottleQuotaSwitch(boolean enable) throws IOException {
-    return false;
-  }
-
-  @Override
-  public Map<TableName, Long> getSpaceQuotaTableSizes() throws IOException {
-    return null;
-  }
-
-  @Override
-  public Map<TableName, ? extends SpaceQuotaSnapshotView> getRegionServerSpaceQuotaSnapshots(
-      ServerName serverName) throws IOException {
-    return null;
-  }
-
-  @Override
-  public SpaceQuotaSnapshotView getCurrentSpaceQuotaSnapshot(String namespace) throws IOException {
-    return null;
-  }
-
-  @Override
-  public SpaceQuotaSnapshotView getCurrentSpaceQuotaSnapshot(TableName tableName)
-      throws IOException {
-    return null;
-  }
-
-  @Override
-  public void grant(UserPermission userPermission, boolean mergeExistingPermissions)
-      throws IOException {
-
-  }
-
-  @Override
-  public void revoke(UserPermission userPermission) throws IOException {
-
-  }
-
-  @Override
-  public List<UserPermission> getUserPermissions(
-      GetUserPermissionsRequest getUserPermissionsRequest) throws IOException {
-    return null;
-  }
-
-  @Override
-  public List<Boolean> hasUserPermissions(String userName, List<Permission> permissions)
-      throws IOException {
-    return null;
-  }
-
-  @Override
-  public boolean snapshotCleanupSwitch(boolean on, boolean synchronous) throws IOException {
-    return false;
-  }
-
-  @Override
-  public boolean isSnapshotCleanupEnabled() throws IOException {
-    return false;
-  }
-
-  @Override
+  /**
+   * Gets {@code RSGroupInfo} for given group name.
+   */
   public RSGroupInfo getRSGroupInfo(String groupName) throws IOException {
-    return getRSGroup(groupName);
-  }
-
-  @Override
-  public void moveServers(Set<Address> servers, String targetGroup) throws IOException {
-    moveToRSGroup(servers, targetGroup);
-  }
-
-  @Override
-  public void addRSGroup(String groupName) throws IOException {
-    AddRSGroupRequest request = AddRSGroupRequest.newBuilder().setRSGroupName(groupName).build();
-    try {
-      stub.addRSGroup(null, request);
-    } catch (ServiceException e) {
-      throw ProtobufUtil.handleRemoteException(e);
-    }
-  }
-
-  @Override
-  public RSGroupInfo getRSGroup(String groupName) throws IOException {
     try {
       GetRSGroupInfoResponse resp = stub.getRSGroupInfo(null,
-          GetRSGroupInfoRequest.newBuilder().setRSGroupName(groupName).build());
-      if (resp.hasRSGroupInfo()) {
-        return ProtobufUtil.toGroupInfo(resp.getRSGroupInfo());
-      }
-      return null;
-    } catch (ServiceException e) {
-      throw ProtobufUtil.handleRemoteException(e);
-    }
-  }
-
-  @Override
-  public RSGroupInfo getRSGroup(Address hostPort) throws IOException {
-    GetRSGroupInfoOfServerRequest request = GetRSGroupInfoOfServerRequest.newBuilder().setServer(
-        HBaseProtos.ServerName.newBuilder().setHostName(hostPort.getHostname())
-            .setPort(hostPort.getPort()).build()).build();
-    try {
-      GetRSGroupInfoOfServerResponse resp = stub.getRSGroupInfoOfServer(null, request);
+        GetRSGroupInfoRequest.newBuilder().setRSGroupName(groupName).build());
       if (resp.hasRSGroupInfo()) {
         return ProtobufUtil.toGroupInfo(resp.getRSGroupInfo());
       }
@@ -971,10 +80,12 @@ public class RSGroupAdminClient implements RSGroupAdmin, Admin {
     }
   }
 
-  @Override
-  public RSGroupInfo getRSGroup(TableName tableName) throws IOException {
+  /**
+   * Gets {@code RSGroupInfo} for the given table's group.
+   */
+  public RSGroupInfo getRSGroupInfoOfTable(TableName tableName) throws IOException {
     GetRSGroupInfoOfTableRequest request = GetRSGroupInfoOfTableRequest.newBuilder()
-        .setTableName(ProtobufUtil.toProtoTableName(tableName)).build();
+      .setTableName(ProtobufUtil.toProtoTableName(tableName)).build();
     try {
       GetRSGroupInfoOfTableResponse resp = stub.getRSGroupInfoOfTable(null, request);
       if (resp.hasRSGroupInfo()) {
@@ -986,71 +97,74 @@ public class RSGroupAdminClient implements RSGroupAdmin, Admin {
     }
   }
 
-  @Override
-  public void removeRSGroup(String name) throws IOException {
-    RemoveRSGroupRequest request = RemoveRSGroupRequest.newBuilder().setRSGroupName(name).build();
+  /**
+   * Move given set of servers to the specified target RegionServer group.
+   */
+  public void moveServers(Set<Address> servers, String targetGroup) throws IOException {
+    Set<HBaseProtos.ServerName> hostPorts = Sets.newHashSet();
+    for (Address el : servers) {
+      hostPorts.add(HBaseProtos.ServerName.newBuilder().setHostName(el.getHostname())
+        .setPort(el.getPort()).build());
+    }
+    MoveServersRequest request =
+      MoveServersRequest.newBuilder().setTargetGroup(targetGroup).addAllServers(hostPorts).build();
     try {
-      stub.removeRSGroup(null, request);
+      stub.moveServers(null, request);
     } catch (ServiceException e) {
       throw ProtobufUtil.handleRemoteException(e);
     }
   }
 
-  @Override
-  public void removeRSGroup(Set<Address> servers) throws IOException {
-    Set<HBaseProtos.ServerName> hostPorts = Sets.newHashSet();
-    for (Address el : servers) {
-      hostPorts.add(
-          HBaseProtos.ServerName.newBuilder().setHostName(el.getHostname()).setPort(el.getPort())
-              .build());
+  /**
+   * Move given set of tables to the specified target RegionServer group. This will unassign all of
+   * a table's region so it can be reassigned to the correct group.
+   */
+  public void moveTables(Set<TableName> tables, String targetGroup) throws IOException {
+    MoveTablesRequest.Builder builder = MoveTablesRequest.newBuilder().setTargetGroup(targetGroup);
+    for (TableName tableName : tables) {
+      builder.addTableName(ProtobufUtil.toProtoTableName(tableName));
+      if (!admin.tableExists(tableName)) {
+        throw new TableNotFoundException(tableName);
+      }
     }
-    RemoveServersRequest request =
-        RemoveServersRequest.newBuilder().addAllServers(hostPorts).build();
     try {
-      stub.removeServers(null, request);
+      stub.moveTables(null, builder.build());
     } catch (ServiceException e) {
       throw ProtobufUtil.handleRemoteException(e);
     }
   }
 
-  @Override
-  public void moveToRSGroup(Set<Address> servers, String targetGroup) throws IOException {
-    Set<HBaseProtos.ServerName> hostPorts = Sets.newHashSet();
-    for (Address el : servers) {
-      hostPorts.add(
-          HBaseProtos.ServerName.newBuilder().setHostName(el.getHostname()).setPort(el.getPort())
-              .build());
-    }
-    MoveServersRequest request =
-        MoveServersRequest.newBuilder().setTargetGroup(targetGroup).addAllServers(hostPorts)
-            .build();
+  /**
+   * Creates a new RegionServer group with the given name.
+   */
+  public void addRSGroup(String groupName) throws IOException {
+    AddRSGroupRequest request = AddRSGroupRequest.newBuilder().setRSGroupName(groupName).build();
     try {
-      stub.moveServers(null, request);
+      stub.addRSGroup(null, request);
     } catch (ServiceException e) {
       throw ProtobufUtil.handleRemoteException(e);
     }
   }
 
-  @Override
-  public void setRSGroup(Set<TableName> tables, String groupName) throws IOException {
-    MoveTablesRequest.Builder builder = MoveTablesRequest.newBuilder().setTargetGroup(groupName);
-    for (TableName tableName : tables) {
-      builder.addTableName(ProtobufUtil.toProtoTableName(tableName));
-      if (!admin.tableExists(tableName)) {
-        throw new TableNotFoundException(tableName);
-      }
-    }
+  /**
+   * Removes RegionServer group associated with the given name.
+   */
+  public void removeRSGroup(String name) throws IOException {
+    RemoveRSGroupRequest request = RemoveRSGroupRequest.newBuilder().setRSGroupName(name).build();
     try {
-      stub.moveTables(null, builder.build());
+      stub.removeRSGroup(null, request);
     } catch (ServiceException e) {
       throw ProtobufUtil.handleRemoteException(e);
     }
   }
 
-  @Override
+  /**
+   * Balance regions in the given RegionServer group.
+   * @return boolean Whether balance ran or not
+   */
   public boolean balanceRSGroup(String groupName) throws IOException {
     BalanceRSGroupRequest request =
-        BalanceRSGroupRequest.newBuilder().setRSGroupName(groupName).build();
+      BalanceRSGroupRequest.newBuilder().setRSGroupName(groupName).build();
     try {
       return stub.balanceRSGroup(null, request).getBalanceRan();
     } catch (ServiceException e) {
@@ -1058,12 +172,13 @@ public class RSGroupAdminClient implements RSGroupAdmin, Admin {
     }
   }
 
-  @Override
+  /**
+   * Lists current set of RegionServer groups.
+   */
   public List<RSGroupInfo> listRSGroups() throws IOException {
     try {
-      List<RSGroupProtos.RSGroupInfo> resp =
-          stub.listRSGroupInfos(null, ListRSGroupInfosRequest.getDefaultInstance())
-              .getRSGroupInfoList();
+      List<RSGroupProtos.RSGroupInfo> resp = stub
+        .listRSGroupInfos(null, ListRSGroupInfosRequest.getDefaultInstance()).getRSGroupInfoList();
       List<RSGroupInfo> result = new ArrayList<>(resp.size());
       for (RSGroupProtos.RSGroupInfo entry : resp) {
         result.add(ProtobufUtil.toGroupInfo(entry));
@@ -1074,14 +189,72 @@ public class RSGroupAdminClient implements RSGroupAdmin, Admin {
     }
   }
 
-  @Override
+  /**
+   * Retrieve the RSGroupInfo a server is affiliated to
+   * @param hostPort HostPort to get RSGroupInfo for
+   */
   public RSGroupInfo getRSGroupOfServer(Address hostPort) throws IOException {
-    return getRSGroup(hostPort);
+    GetRSGroupInfoOfServerRequest request =
+      GetRSGroupInfoOfServerRequest.newBuilder().setServer(HBaseProtos.ServerName.newBuilder()
+        .setHostName(hostPort.getHostname()).setPort(hostPort.getPort()).build()).build();
+    try {
+      GetRSGroupInfoOfServerResponse resp = stub.getRSGroupInfoOfServer(null, request);
+      if (resp.hasRSGroupInfo()) {
+        return ProtobufUtil.toGroupInfo(resp.getRSGroupInfo());
+      }
+      return null;
+    } catch (ServiceException e) {
+      throw ProtobufUtil.handleRemoteException(e);
+    }
   }
 
-  @Override
-  public void removeServers(Set<Address> servers) throws IOException {
-    removeRSGroup(servers);
+  /**
+   * Move given set of servers and tables to the specified target RegionServer group.
+   * @param servers set of servers to move
+   * @param tables set of tables to move
+   * @param targetGroup the target group name
+   * @throws IOException if moving the server and tables fail
+   */
+  public void moveServersAndTables(Set<Address> servers, Set<TableName> tables, String targetGroup)
+    throws IOException {
+    MoveServersAndTablesRequest.Builder builder =
+      MoveServersAndTablesRequest.newBuilder().setTargetGroup(targetGroup);
+    for (Address el : servers) {
+      builder.addServers(HBaseProtos.ServerName.newBuilder().setHostName(el.getHostname())
+        .setPort(el.getPort()).build());
+    }
+    for (TableName tableName : tables) {
+      builder.addTableName(ProtobufUtil.toProtoTableName(tableName));
+      if (!admin.tableExists(tableName)) {
+        throw new TableNotFoundException(tableName);
+      }
+    }
+    try {
+      stub.moveServersAndTables(null, builder.build());
+    } catch (ServiceException e) {
+      throw ProtobufUtil.handleRemoteException(e);
+    }
   }
 
+  /**
+   * Remove decommissioned servers from rsgroup. 1. Sometimes we may find the server aborted due to
+   * some hardware failure and we must offline the server for repairing. Or we need to move some
+   * servers to join other clusters. So we need to remove these servers from the rsgroup. 2.
+   * Dead/recovering/live servers will be disallowed.
+   * @param servers set of servers to remove
+   */
+  public void removeServers(Set<Address> servers) throws IOException {
+    Set<HBaseProtos.ServerName> hostPorts = Sets.newHashSet();
+    for (Address el : servers) {
+      hostPorts.add(HBaseProtos.ServerName.newBuilder().setHostName(el.getHostname())
+        .setPort(el.getPort()).build());
+    }
+    RemoveServersRequest request =
+      RemoveServersRequest.newBuilder().addAllServers(hostPorts).build();
+    try {
+      stub.removeServers(null, request);
+    } catch (ServiceException e) {
+      throw ProtobufUtil.handleRemoteException(e);
+    }
+  }
 }
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestEnableRSGroups.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/EnableRSGroupsTestBase.java
similarity index 73%
copy from hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestEnableRSGroups.java
copy to hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/EnableRSGroupsTestBase.java
index d15c7a8..dc3144f 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestEnableRSGroups.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/EnableRSGroupsTestBase.java
@@ -21,32 +21,18 @@ import static java.lang.Thread.sleep;
 import static org.junit.Assert.assertTrue;
 
 import java.io.IOException;
-
 import org.apache.hadoop.conf.Configuration;
-import org.apache.hadoop.hbase.HBaseClassTestRule;
 import org.apache.hadoop.hbase.HBaseTestingUtility;
-import org.apache.hadoop.hbase.HConstants;
 import org.apache.hadoop.hbase.coprocessor.CoprocessorHost;
-import org.apache.hadoop.hbase.testclassification.MediumTests;
 import org.junit.AfterClass;
 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 enable RSGroup
- */
-@Category({ MediumTests.class })
-public class TestEnableRSGroups {
-
-  @ClassRule
-  public static final HBaseClassTestRule CLASS_RULE =
-      HBaseClassTestRule.forClass(TestEnableRSGroups.class);
+public abstract class EnableRSGroupsTestBase {
 
-  protected static final Logger LOG = LoggerFactory.getLogger(TestEnableRSGroups.class);
+  private static final Logger LOG = LoggerFactory.getLogger(TestEnableRSGroupsCompatibility.class);
 
   private static final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
 
@@ -63,16 +49,15 @@ public class TestEnableRSGroups {
     TEST_UTIL.shutdownMiniCluster();
   }
 
+  protected abstract void enableRSGroup(Configuration conf);
+
   @Test
   public void testEnableRSGroup() throws IOException, InterruptedException {
     TEST_UTIL.getMiniHBaseCluster().stopMaster(0);
     TEST_UTIL.getMiniHBaseCluster().waitOnMaster(0);
 
     LOG.info("stopped master...");
-    final Configuration conf = TEST_UTIL.getMiniHBaseCluster().getConfiguration();
-    conf.set(CoprocessorHost.MASTER_COPROCESSOR_CONF_KEY, RSGroupAdminEndpoint.class.getName());
-    conf.set(HConstants.HBASE_MASTER_LOADBALANCER_CLASS,
-      RSGroupBasedLoadBalancer.class.getName());
+    enableRSGroup(TEST_UTIL.getMiniHBaseCluster().getConfiguration());
 
     TEST_UTIL.getMiniHBaseCluster().startMaster();
     TEST_UTIL.getMiniHBaseCluster().waitForActiveAndReadyMaster(60000);
@@ -83,7 +68,7 @@ public class TestEnableRSGroups {
 
     // wait RSGroupBasedLoadBalancer online
     RSGroupBasedLoadBalancer loadBalancer =
-        (RSGroupBasedLoadBalancer) TEST_UTIL.getMiniHBaseCluster().getMaster().getLoadBalancer();
+      (RSGroupBasedLoadBalancer) TEST_UTIL.getMiniHBaseCluster().getMaster().getLoadBalancer();
     long start = System.currentTimeMillis();
     while (System.currentTimeMillis() - start <= 60000 && !loadBalancer.isOnline()) {
       LOG.info("waiting for rsgroup load balancer onLine...");
@@ -92,5 +77,4 @@ public class TestEnableRSGroups {
 
     assertTrue(loadBalancer.isOnline());
   }
-
 }
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestEnableRSGroups.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestEnableRSGroups.java
index d15c7a8..72dacb5 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestEnableRSGroups.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestEnableRSGroups.java
@@ -17,80 +17,23 @@
  */
 package org.apache.hadoop.hbase.rsgroup;
 
-import static java.lang.Thread.sleep;
-import static org.junit.Assert.assertTrue;
-
-import java.io.IOException;
-
 import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.hbase.HBaseClassTestRule;
-import org.apache.hadoop.hbase.HBaseTestingUtility;
-import org.apache.hadoop.hbase.HConstants;
-import org.apache.hadoop.hbase.coprocessor.CoprocessorHost;
 import org.apache.hadoop.hbase.testclassification.MediumTests;
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
+import org.apache.hadoop.hbase.testclassification.RSGroupTests;
 import org.junit.ClassRule;
-import org.junit.Test;
 import org.junit.experimental.categories.Category;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
-/**
- * Test enable RSGroup
- */
-@Category({ MediumTests.class })
-public class TestEnableRSGroups {
+@Category({ RSGroupTests.class, MediumTests.class })
+public class TestEnableRSGroups extends EnableRSGroupsTestBase {
 
   @ClassRule
   public static final HBaseClassTestRule CLASS_RULE =
-      HBaseClassTestRule.forClass(TestEnableRSGroups.class);
-
-  protected static final Logger LOG = LoggerFactory.getLogger(TestEnableRSGroups.class);
-
-  private static final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
-
-  @BeforeClass
-  public static void setUp() throws Exception {
-    final Configuration conf = TEST_UTIL.getConfiguration();
-    conf.setBoolean(CoprocessorHost.COPROCESSORS_ENABLED_CONF_KEY, true);
-    TEST_UTIL.startMiniCluster(5);
-  }
-
-  @AfterClass
-  public static void tearDown() throws Exception {
-    LOG.info("to stop miniCluster");
-    TEST_UTIL.shutdownMiniCluster();
-  }
-
-  @Test
-  public void testEnableRSGroup() throws IOException, InterruptedException {
-    TEST_UTIL.getMiniHBaseCluster().stopMaster(0);
-    TEST_UTIL.getMiniHBaseCluster().waitOnMaster(0);
-
-    LOG.info("stopped master...");
-    final Configuration conf = TEST_UTIL.getMiniHBaseCluster().getConfiguration();
-    conf.set(CoprocessorHost.MASTER_COPROCESSOR_CONF_KEY, RSGroupAdminEndpoint.class.getName());
-    conf.set(HConstants.HBASE_MASTER_LOADBALANCER_CLASS,
-      RSGroupBasedLoadBalancer.class.getName());
-
-    TEST_UTIL.getMiniHBaseCluster().startMaster();
-    TEST_UTIL.getMiniHBaseCluster().waitForActiveAndReadyMaster(60000);
-    LOG.info("started master...");
-
-    // check if master started successfully
-    assertTrue(TEST_UTIL.getMiniHBaseCluster().getMaster() != null);
-
-    // wait RSGroupBasedLoadBalancer online
-    RSGroupBasedLoadBalancer loadBalancer =
-        (RSGroupBasedLoadBalancer) TEST_UTIL.getMiniHBaseCluster().getMaster().getLoadBalancer();
-    long start = System.currentTimeMillis();
-    while (System.currentTimeMillis() - start <= 60000 && !loadBalancer.isOnline()) {
-      LOG.info("waiting for rsgroup load balancer onLine...");
-      sleep(200);
-    }
+    HBaseClassTestRule.forClass(TestEnableRSGroups.class);
 
-    assertTrue(loadBalancer.isOnline());
+  @Override
+  protected void enableRSGroup(Configuration conf) {
+    conf.setBoolean(RSGroupInfoManager.RS_GROUP_ENABLED, true);
   }
 
 }
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestEnableRSGroupsCompatibility.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestEnableRSGroupsCompatibility.java
new file mode 100644
index 0000000..92faa5b
--- /dev/null
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestEnableRSGroupsCompatibility.java
@@ -0,0 +1,45 @@
+/**
+ * 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.conf.Configuration;
+import org.apache.hadoop.hbase.HBaseClassTestRule;
+import org.apache.hadoop.hbase.HConstants;
+import org.apache.hadoop.hbase.coprocessor.CoprocessorHost;
+import org.apache.hadoop.hbase.testclassification.MediumTests;
+import org.apache.hadoop.hbase.testclassification.RSGroupTests;
+import org.junit.ClassRule;
+import org.junit.experimental.categories.Category;
+
+/**
+ * Test enable RSGroup using the old coprocessor way, to make sure that we keep compatible with old
+ * config way.
+ */
+@Category({ RSGroupTests.class, MediumTests.class })
+public class TestEnableRSGroupsCompatibility extends EnableRSGroupsTestBase {
+
+  @ClassRule
+  public static final HBaseClassTestRule CLASS_RULE =
+    HBaseClassTestRule.forClass(TestEnableRSGroupsCompatibility.class);
+
+  @Override
+  protected void enableRSGroup(Configuration conf) {
+    conf.set(CoprocessorHost.MASTER_COPROCESSOR_CONF_KEY, RSGroupAdminEndpoint.class.getName());
+    conf.set(HConstants.HBASE_MASTER_LOADBALANCER_CLASS, RSGroupBasedLoadBalancer.class.getName());
+  }
+}
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestMigrateRSGroupInfo.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestMigrateRSGroupInfo.java
index 122abc4..c3e4fe7 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestMigrateRSGroupInfo.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestMigrateRSGroupInfo.java
@@ -39,6 +39,7 @@ import org.apache.hadoop.hbase.master.HMaster;
 import org.apache.hadoop.hbase.protobuf.ProtobufUtil;
 import org.apache.hadoop.hbase.protobuf.generated.RSGroupProtos;
 import org.apache.hadoop.hbase.testclassification.MediumTests;
+import org.apache.hadoop.hbase.testclassification.RSGroupTests;
 import org.apache.hadoop.hbase.util.Bytes;
 import org.apache.zookeeper.KeeperException;
 import org.junit.After;
@@ -46,14 +47,11 @@ import org.junit.Before;
 import org.junit.ClassRule;
 import org.junit.Test;
 import org.junit.experimental.categories.Category;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
 
 /**
  * Testcase for HBASE-22819
  */
-@RunWith(Parameterized.class)
-@Category({ MediumTests.class })
+@Category({ RSGroupTests.class, MediumTests.class })
 public class TestMigrateRSGroupInfo extends TestRSGroupsBase {
 
   @ClassRule
@@ -109,10 +107,9 @@ public class TestMigrateRSGroupInfo extends TestRSGroupsBase {
 
   @Test
   public void testMigrate() throws IOException, InterruptedException {
-    setAdmin();
     String groupName = getNameWithoutIndex(name.getMethodName());
     addGroup(groupName, TEST_UTIL.getMiniHBaseCluster().getRegionServerThreads().size() - 1);
-    RSGroupInfo rsGroupInfo = rsGroupAdmin.getRSGroup(groupName);
+    RSGroupInfo rsGroupInfo = ADMIN.getRSGroup(groupName);
     assertTrue(rsGroupInfo.getTables().isEmpty());
     for (int i = 0; i < NUM_TABLES; i++) {
       rsGroupInfo.addTable(TableName.valueOf(TABLE_NAME_PREFIX + i));
@@ -130,7 +127,7 @@ public class TestMigrateRSGroupInfo extends TestRSGroupsBase {
     // wait until we can get the rs group info for a table
     TEST_UTIL.waitFor(30000, () -> {
       try {
-        rsGroupAdmin.getRSGroup(TableName.valueOf(TABLE_NAME_PREFIX + 0));
+        RS_GROUP_ADMIN_CLIENT.getRSGroupInfoOfTable(TableName.valueOf(TABLE_NAME_PREFIX + 0));
         return true;
       } catch (IOException e) {
         return false;
@@ -139,7 +136,7 @@ public class TestMigrateRSGroupInfo extends TestRSGroupsBase {
     // confirm that before migrating, we could still get the correct rs group for a table.
     for (int i = 0; i < NUM_TABLES; i++) {
       RSGroupInfo info =
-        rsGroupAdmin.getRSGroup(TableName.valueOf(TABLE_NAME_PREFIX + i));
+        RS_GROUP_ADMIN_CLIENT.getRSGroupInfoOfTable(TableName.valueOf(TABLE_NAME_PREFIX + i));
       assertEquals(rsGroupInfo.getName(), info.getName());
       assertEquals(NUM_TABLES, info.getTables().size());
     }
@@ -175,7 +172,7 @@ public class TestMigrateRSGroupInfo extends TestRSGroupsBase {
     // make sure we could still get the correct rs group info after migration
     for (int i = 0; i < NUM_TABLES; i++) {
       RSGroupInfo info =
-        rsGroupAdmin.getRSGroup(TableName.valueOf(TABLE_NAME_PREFIX + i));
+        RS_GROUP_ADMIN_CLIENT.getRSGroupInfoOfTable(TableName.valueOf(TABLE_NAME_PREFIX + i));
       assertEquals(rsGroupInfo.getName(), info.getName());
       assertEquals(NUM_TABLES, info.getTables().size());
     }
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupMajorCompactionTTL.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupMajorCompactionTTL.java
index 9b3dcbb..6725cf0 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupMajorCompactionTTL.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupMajorCompactionTTL.java
@@ -6,38 +6,41 @@
  * 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
+ *
+ *     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.junit.Assert.assertTrue;
 
 import java.util.List;
-
 import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.hbase.HBaseClassTestRule;
 import org.apache.hadoop.hbase.HBaseTestingUtility;
-import org.apache.hadoop.hbase.HConstants;
 import org.apache.hadoop.hbase.MiniHBaseCluster;
 import org.apache.hadoop.hbase.TableName;
 import org.apache.hadoop.hbase.Waiter;
-import org.apache.hadoop.hbase.coprocessor.CoprocessorHost;
 import org.apache.hadoop.hbase.master.HMaster;
 import org.apache.hadoop.hbase.master.ServerManager;
+import org.apache.hadoop.hbase.testclassification.MediumTests;
+import org.apache.hadoop.hbase.testclassification.RSGroupTests;
 import org.apache.hadoop.hbase.util.compaction.TestMajorCompactorTTL;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.ClassRule;
 import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
 import org.apache.hbase.thirdparty.com.google.common.collect.Lists;
 
+@Category({ RSGroupTests.class, MediumTests.class })
 public class TestRSGroupMajorCompactionTTL extends TestMajorCompactorTTL {
   @ClassRule
   public static final HBaseClassTestRule CLASS_RULE =
@@ -50,8 +53,7 @@ public class TestRSGroupMajorCompactionTTL extends TestMajorCompactorTTL {
   public void setUp() throws Exception {
     utility = new HBaseTestingUtility();
     Configuration conf = utility.getConfiguration();
-    conf.set(HConstants.HBASE_MASTER_LOADBALANCER_CLASS, RSGroupBasedLoadBalancer.class.getName());
-    conf.set(CoprocessorHost.MASTER_COPROCESSOR_CONF_KEY, RSGroupAdminEndpoint.class.getName());
+    conf.setBoolean(RSGroupInfoManager.RS_GROUP_ENABLED, true);
     conf.setInt(ServerManager.WAIT_ON_REGIONSERVERS_MINTOSTART, NUM_SLAVES_BASE);
     conf.setInt("hbase.hfile.compaction.discharger.interval", 10);
     utility.startMiniCluster(NUM_SLAVES_BASE);
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsAdmin1.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsAdmin1.java
index 614b6c4..255f80d 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsAdmin1.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsAdmin1.java
@@ -24,13 +24,11 @@ import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
 import java.io.IOException;
-import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.SortedSet;
 import org.apache.hadoop.hbase.HBaseClassTestRule;
-import org.apache.hadoop.hbase.MiniHBaseCluster;
 import org.apache.hadoop.hbase.NamespaceDescriptor;
 import org.apache.hadoop.hbase.ServerName;
 import org.apache.hadoop.hbase.TableExistsException;
@@ -45,31 +43,28 @@ import org.apache.hadoop.hbase.constraint.ConstraintException;
 import org.apache.hadoop.hbase.master.TableNamespaceManager;
 import org.apache.hadoop.hbase.net.Address;
 import org.apache.hadoop.hbase.testclassification.MediumTests;
+import org.apache.hadoop.hbase.testclassification.RSGroupTests;
 import org.apache.hadoop.hbase.util.Bytes;
 import org.junit.After;
 import org.junit.AfterClass;
-import org.junit.Assert;
 import org.junit.Before;
 import org.junit.BeforeClass;
 import org.junit.ClassRule;
 import org.junit.Test;
 import org.junit.experimental.categories.Category;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import org.apache.hbase.thirdparty.com.google.common.collect.Sets;
 
-@RunWith(Parameterized.class)
-@Category({ MediumTests.class })
+@Category({ RSGroupTests.class, MediumTests.class })
 public class TestRSGroupsAdmin1 extends TestRSGroupsBase {
 
   @ClassRule
   public static final HBaseClassTestRule CLASS_RULE =
     HBaseClassTestRule.forClass(TestRSGroupsAdmin1.class);
 
-  protected static final Logger LOG = LoggerFactory.getLogger(TestRSGroupsAdmin1.class);
+  private static final Logger LOG = LoggerFactory.getLogger(TestRSGroupsAdmin1.class);
 
   @BeforeClass
   public static void setUp() throws Exception {
@@ -98,7 +93,7 @@ public class TestRSGroupsAdmin1 extends TestRSGroupsBase {
 
     for (String entry : badNames) {
       try {
-        rsGroupAdmin.addRSGroup(entry);
+        ADMIN.addRSGroup(entry);
         fail("Expected a constraint exception for: " + entry);
       } catch (ConstraintException ex) {
         // expected
@@ -106,42 +101,41 @@ public class TestRSGroupsAdmin1 extends TestRSGroupsBase {
     }
 
     for (String entry : goodNames) {
-      rsGroupAdmin.addRSGroup(entry);
+      ADMIN.addRSGroup(entry);
     }
   }
 
   @Test
   public void testBogusArgs() throws Exception {
-    assertNull(rsGroupAdmin.getRSGroup(TableName.valueOf("nonexistent")));
-    assertNull(rsGroupAdmin.getRSGroup(Address.fromParts("bogus", 123)));
-    assertNull(rsGroupAdmin.getRSGroup("bogus"));
+    assertNull(ADMIN.getRSGroup(TableName.valueOf("nonexistent")));
+    assertNull(ADMIN.getRSGroup(Address.fromParts("bogus", 123)));
+    assertNull(ADMIN.getRSGroup("bogus"));
 
     try {
-      rsGroupAdmin.removeRSGroup("bogus");
+      ADMIN.removeRSGroup("bogus");
       fail("Expected removing bogus group to fail");
     } catch (ConstraintException ex) {
       // expected
     }
 
     try {
-      rsGroupAdmin.setRSGroup(Sets.newHashSet(TableName.valueOf("bogustable")), "bogus");
+      ADMIN.setRSGroup(Sets.newHashSet(TableName.valueOf("bogustable")), "bogus");
       fail("Expected set table to bogus group fail");
     } catch (ConstraintException | TableNotFoundException ex) {
       // expected
     }
 
     try {
-      rsGroupAdmin.moveToRSGroup(Sets.newHashSet(Address.fromParts("bogus", 123)),
-          "bogus");
+      ADMIN.moveServersToRSGroup(Sets.newHashSet(Address.fromParts("bogus", 123)), "bogus");
       fail("Expected move with bogus group to fail");
     } catch (ConstraintException ex) {
       // expected
     }
 
     try {
-      admin.balancerSwitch(true, true);
-      rsGroupAdmin.balanceRSGroup("bogus");
-      admin.balancerSwitch(false, true);
+      ADMIN.balancerSwitch(true, true);
+      ADMIN.balanceRSGroup("bogus");
+      ADMIN.balancerSwitch(false, true);
       fail("Expected move with bogus group to fail");
     } catch (ConstraintException ex) {
       // expected
@@ -150,36 +144,36 @@ public class TestRSGroupsAdmin1 extends TestRSGroupsBase {
 
   @Test
   public void testNamespaceConstraint() throws Exception {
-    String nsName = tablePrefix + "_foo";
-    String groupName = tablePrefix + "_foo";
+    String nsName = TABLE_PREFIX + "_foo";
+    String groupName = TABLE_PREFIX + "_foo";
     LOG.info("testNamespaceConstraint");
     addGroup(groupName, 1);
-    assertTrue(observer.preAddRSGroupCalled);
-    assertTrue(observer.postAddRSGroupCalled);
+    assertTrue(OBSERVER.preAddRSGroupCalled);
+    assertTrue(OBSERVER.postAddRSGroupCalled);
 
-    admin.createNamespace(NamespaceDescriptor.create(nsName)
+    ADMIN.createNamespace(NamespaceDescriptor.create(nsName)
       .addConfiguration(RSGroupInfo.NAMESPACE_DESC_PROP_GROUP, groupName).build());
-    RSGroupInfo rsGroupInfo = rsGroupAdmin.getRSGroup(groupName);
-    rsGroupAdmin.moveToRSGroup(rsGroupInfo.getServers(), RSGroupInfo.DEFAULT_GROUP);
+    RSGroupInfo rsGroupInfo = ADMIN.getRSGroup(groupName);
+    ADMIN.moveServersToRSGroup(rsGroupInfo.getServers(), RSGroupInfo.DEFAULT_GROUP);
     // test removing a referenced group
     try {
-      rsGroupAdmin.removeRSGroup(groupName);
+      ADMIN.removeRSGroup(groupName);
       fail("Expected a constraint exception");
     } catch (IOException ex) {
     }
     // test modify group
     // changing with the same name is fine
-    admin.modifyNamespace(NamespaceDescriptor.create(nsName)
+    ADMIN.modifyNamespace(NamespaceDescriptor.create(nsName)
       .addConfiguration(RSGroupInfo.NAMESPACE_DESC_PROP_GROUP, groupName).build());
-    String anotherGroup = tablePrefix + "_anotherGroup";
-    rsGroupAdmin.addRSGroup(anotherGroup);
+    String anotherGroup = TABLE_PREFIX + "_anotherGroup";
+    ADMIN.addRSGroup(anotherGroup);
     // test add non-existent group
-    admin.deleteNamespace(nsName);
-    rsGroupAdmin.removeRSGroup(groupName);
-    assertTrue(observer.preRemoveRSGroupCalled);
-    assertTrue(observer.postRemoveRSGroupCalled);
+    ADMIN.deleteNamespace(nsName);
+    ADMIN.removeRSGroup(groupName);
+    assertTrue(OBSERVER.preRemoveRSGroupCalled);
+    assertTrue(OBSERVER.postRemoveRSGroupCalled);
     try {
-      admin.createNamespace(NamespaceDescriptor.create(nsName)
+      ADMIN.createNamespace(NamespaceDescriptor.create(nsName)
         .addConfiguration(RSGroupInfo.NAMESPACE_DESC_PROP_GROUP, "foo").build());
       fail("Expected a constraint exception");
     } catch (IOException ex) {
@@ -187,90 +181,44 @@ public class TestRSGroupsAdmin1 extends TestRSGroupsBase {
   }
 
   @Test
-  public void testGroupInfoMultiAccessing() throws Exception {
-    RSGroupInfoManager manager = rsGroupAdminEndpoint.getGroupInfoManager();
-    RSGroupInfo defaultGroup = manager.getRSGroup("default");
-    // getRSGroup updates default group's server list
-    // this process must not affect other threads iterating the list
-    Iterator<Address> it = defaultGroup.getServers().iterator();
-    manager.getRSGroup("default");
-    it.next();
-  }
-
-  @Test
-  public void testGetRSGroupInfoCPHookCalled() throws Exception {
-    rsGroupAdmin.getRSGroup(RSGroupInfo.DEFAULT_GROUP);
-    if (rsGroupAdmin instanceof RSGroupAdminClient) {
-      assertTrue(observer.preGetRSGroupInfoCalled);
-      assertTrue(observer.postGetRSGroupInfoCalled);
-    }
-  }
-
-  @Test
-  public void testGetRSGroupInfoOfTableCPHookCalled() throws Exception {
-    rsGroupAdmin.getRSGroup(TableName.META_TABLE_NAME);
-    if (rsGroupAdmin instanceof RSGroupAdminClient) {
-      assertTrue(observer.preGetRSGroupInfoOfTableCalled);
-      assertTrue(observer.postGetRSGroupInfoOfTableCalled);
-    }
-  }
-
-  @Test
-  public void testListRSGroupsCPHookCalled() throws Exception {
-    rsGroupAdmin.listRSGroups();
-    assertTrue(observer.preListRSGroupsCalled);
-    assertTrue(observer.postListRSGroupsCalled);
-  }
-
-  @Test
-  public void testGetRSGroupInfoOfServerCPHookCalled() throws Exception {
-    ServerName masterServerName = ((MiniHBaseCluster) cluster).getMaster().getServerName();
-    rsGroupAdmin.getRSGroup(masterServerName.getAddress());
-    if (rsGroupAdmin instanceof RSGroupAdminClient) {
-      assertTrue(observer.preGetRSGroupInfoOfServerCalled);
-      assertTrue(observer.postGetRSGroupInfoOfServerCalled);
-    }
-  }
-
-  @Test
   public void testFailRemoveGroup() throws IOException, InterruptedException {
-    int initNumGroups = rsGroupAdmin.listRSGroups().size();
+    int initNumGroups = ADMIN.listRSGroups().size();
     addGroup("bar", 3);
     TEST_UTIL.createTable(tableName, Bytes.toBytes("f"));
-    rsGroupAdmin.setRSGroup(Sets.newHashSet(tableName), "bar");
-    RSGroupInfo barGroup = rsGroupAdmin.getRSGroup("bar");
+    ADMIN.setRSGroup(Sets.newHashSet(tableName), "bar");
+    RSGroupInfo barGroup = RS_GROUP_ADMIN_CLIENT.getRSGroupInfo("bar");
     // group is not empty therefore it should fail
     try {
-      rsGroupAdmin.removeRSGroup(barGroup.getName());
+      ADMIN.removeRSGroup(barGroup.getName());
       fail("Expected remove group to fail");
     } catch (IOException e) {
     }
     // group cannot lose all it's servers therefore it should fail
     try {
-      rsGroupAdmin.moveToRSGroup(barGroup.getServers(), RSGroupInfo.DEFAULT_GROUP);
+      ADMIN.moveServersToRSGroup(barGroup.getServers(), RSGroupInfo.DEFAULT_GROUP);
       fail("Expected move servers to fail");
     } catch (IOException e) {
     }
 
-    rsGroupAdmin.setRSGroup(barGroup.getTables(), RSGroupInfo.DEFAULT_GROUP);
+    ADMIN.setRSGroup(barGroup.getTables(), RSGroupInfo.DEFAULT_GROUP);
     try {
-      rsGroupAdmin.removeRSGroup(barGroup.getName());
+      ADMIN.removeRSGroup(barGroup.getName());
       fail("Expected move servers to fail");
     } catch (IOException e) {
     }
 
-    rsGroupAdmin.moveToRSGroup(barGroup.getServers(), RSGroupInfo.DEFAULT_GROUP);
-    rsGroupAdmin.removeRSGroup(barGroup.getName());
+    ADMIN.moveServersToRSGroup(barGroup.getServers(), RSGroupInfo.DEFAULT_GROUP);
+    ADMIN.removeRSGroup(barGroup.getName());
 
-    Assert.assertEquals(initNumGroups, rsGroupAdmin.listRSGroups().size());
+    assertEquals(initNumGroups, ADMIN.listRSGroups().size());
   }
 
   @Test
   public void testMultiTableMove() throws Exception {
-    final TableName tableNameA = TableName.valueOf(tablePrefix +
-        getNameWithoutIndex(name.getMethodName()) + "A");
-    final TableName tableNameB = TableName.valueOf(tablePrefix +
-        getNameWithoutIndex(name.getMethodName()) + "B");
+    final TableName tableNameA =
+      TableName.valueOf(TABLE_PREFIX + getNameWithoutIndex(name.getMethodName()) + "A");
+    final TableName tableNameB =
+      TableName.valueOf(TABLE_PREFIX + getNameWithoutIndex(name.getMethodName()) + "B");
     final byte[] familyNameBytes = Bytes.toBytes("f");
     String newGroupName = getGroupName(getNameWithoutIndex(name.getMethodName()));
     final RSGroupInfo newGroup = addGroup(newGroupName, 1);
@@ -293,30 +241,28 @@ public class TestRSGroupsAdmin1 extends TestRSGroupsBase {
       }
     });
 
-    RSGroupInfo tableGrpA = rsGroupAdmin.getRSGroup(tableNameA);
+    RSGroupInfo tableGrpA = ADMIN.getRSGroup(tableNameA);
     assertTrue(tableGrpA.getName().equals(RSGroupInfo.DEFAULT_GROUP));
 
-    RSGroupInfo tableGrpB = rsGroupAdmin.getRSGroup(tableNameB);
+    RSGroupInfo tableGrpB = ADMIN.getRSGroup(tableNameB);
     assertTrue(tableGrpB.getName().equals(RSGroupInfo.DEFAULT_GROUP));
     // change table's group
     LOG.info("Moving table [" + tableNameA + "," + tableNameB + "] to " + newGroup.getName());
-    rsGroupAdmin.setRSGroup(Sets.newHashSet(tableNameA, tableNameB), newGroup.getName());
+    ADMIN.setRSGroup(Sets.newHashSet(tableNameA, tableNameB), newGroup.getName());
 
     // verify group change
-    Assert.assertEquals(newGroup.getName(),
-      rsGroupAdmin.getRSGroup(tableNameA).getName());
+    assertEquals(newGroup.getName(), ADMIN.getRSGroup(tableNameA).getName());
 
-    Assert.assertEquals(newGroup.getName(),
-      rsGroupAdmin.getRSGroup(tableNameB).getName());
+    assertEquals(newGroup.getName(), ADMIN.getRSGroup(tableNameB).getName());
 
     // verify tables' not exist in old group
-    Set<TableName> DefaultTables =
-      rsGroupAdmin.getRSGroup(RSGroupInfo.DEFAULT_GROUP).getTables();
-    assertFalse(DefaultTables.contains(tableNameA));
-    assertFalse(DefaultTables.contains(tableNameB));
+    Set<TableName> defaultTables =
+      RS_GROUP_ADMIN_CLIENT.getRSGroupInfo(RSGroupInfo.DEFAULT_GROUP).getTables();
+    assertFalse(defaultTables.contains(tableNameA));
+    assertFalse(defaultTables.contains(tableNameB));
 
     // verify tables' exist in new group
-    Set<TableName> newGroupTables = rsGroupAdmin.getRSGroup(newGroupName).getTables();
+    Set<TableName> newGroupTables = RS_GROUP_ADMIN_CLIENT.getRSGroupInfo(newGroupName).getTables();
     assertTrue(newGroupTables.contains(tableNameA));
     assertTrue(newGroupTables.contains(tableNameB));
   }
@@ -340,17 +286,16 @@ public class TestRSGroupsAdmin1 extends TestRSGroupsBase {
       }
     });
 
-    RSGroupInfo tableGrp = rsGroupAdmin.getRSGroup(tableName);
+    RSGroupInfo tableGrp = ADMIN.getRSGroup(tableName);
     LOG.info("got table group info is {}", tableGrp);
     assertTrue(tableGrp.getName().equals(RSGroupInfo.DEFAULT_GROUP));
 
     // change table's group
     LOG.info("Moving table " + tableName + " to " + newGroup.getName());
-    rsGroupAdmin.setRSGroup(Sets.newHashSet(tableName), newGroup.getName());
+    ADMIN.setRSGroup(Sets.newHashSet(tableName), newGroup.getName());
 
     // verify group change
-    Assert.assertEquals(newGroup.getName(),
-      rsGroupAdmin.getRSGroup(tableName).getName());
+    assertEquals(newGroup.getName(), ADMIN.getRSGroup(tableName).getName());
 
     TEST_UTIL.waitFor(WAIT_TIMEOUT, new Waiter.Predicate<Exception>() {
       @Override
@@ -369,16 +314,15 @@ public class TestRSGroupsAdmin1 extends TestRSGroupsBase {
     });
 
     // test truncate
-    admin.disableTable(tableName);
-    admin.truncateTable(tableName, true);
-    Assert.assertEquals(1, rsGroupAdmin.getRSGroup(newGroup.getName()).getTables().size());
-    Assert.assertEquals(tableName,
-      rsGroupAdmin.getRSGroup(newGroup.getName()).getTables().first());
+    ADMIN.disableTable(tableName);
+    ADMIN.truncateTable(tableName, true);
+    assertEquals(1, RS_GROUP_ADMIN_CLIENT.getRSGroupInfo(newGroup.getName()).getTables().size());
+    assertEquals(tableName,
+      RS_GROUP_ADMIN_CLIENT.getRSGroupInfo(newGroup.getName()).getTables().first());
 
     // verify removed table is removed from group
     TEST_UTIL.deleteTable(tableName);
-    Assert.assertEquals(0, rsGroupAdmin.getRSGroup(newGroup.getName()).getTables().size());
-
+    assertEquals(0, RS_GROUP_ADMIN_CLIENT.getRSGroupInfo(newGroup.getName()).getTables().size());
   }
 
   @Test
@@ -399,50 +343,49 @@ public class TestRSGroupsAdmin1 extends TestRSGroupsBase {
       }
     });
 
-    RSGroupInfo tableGrp = rsGroupAdmin.getRSGroup(tableName);
+    RSGroupInfo tableGrp = ADMIN.getRSGroup(tableName);
     assertTrue(tableGrp.getName().equals(RSGroupInfo.DEFAULT_GROUP));
 
     // test disable table
-    admin.disableTable(tableName);
+    ADMIN.disableTable(tableName);
 
     // change table's group
     LOG.info("Moving table " + tableName + " to " + newGroup.getName());
-    rsGroupAdmin.setRSGroup(Sets.newHashSet(tableName), newGroup.getName());
+    ADMIN.setRSGroup(Sets.newHashSet(tableName), newGroup.getName());
 
     // verify group change
-    Assert.assertEquals(newGroup.getName(),
-      rsGroupAdmin.getRSGroup(tableName).getName());
+    assertEquals(newGroup.getName(), ADMIN.getRSGroup(tableName).getName());
   }
 
   @Test
   public void testNonExistentTableMove() throws Exception {
-    TableName tableName = TableName.valueOf(tablePrefix +
-        getNameWithoutIndex(name.getMethodName()));
-    RSGroupInfo tableGrp = rsGroupAdmin.getRSGroup(tableName);
+    TableName tableName =
+      TableName.valueOf(TABLE_PREFIX + getNameWithoutIndex(name.getMethodName()));
+    RSGroupInfo tableGrp = ADMIN.getRSGroup(tableName);
     assertNull(tableGrp);
 
     // test if table exists already.
-    boolean exist = admin.tableExists(tableName);
+    boolean exist = ADMIN.tableExists(tableName);
     assertFalse(exist);
 
     LOG.info("Moving table " + tableName + " to " + RSGroupInfo.DEFAULT_GROUP);
     try {
-      rsGroupAdmin.setRSGroup(Sets.newHashSet(tableName), RSGroupInfo.DEFAULT_GROUP);
+      ADMIN.setRSGroup(Sets.newHashSet(tableName), RSGroupInfo.DEFAULT_GROUP);
       fail("Table " + tableName + " shouldn't have been successfully moved.");
     } catch (IOException ex) {
       assertTrue(ex instanceof TableNotFoundException);
     }
 
     try {
-      rsGroupAdmin.setRSGroup(Sets.newHashSet(tableName), RSGroupInfo.DEFAULT_GROUP);
-      rsGroupAdmin.moveToRSGroup(Sets.newHashSet(Address.fromParts("bogus", 123)),
-          RSGroupInfo.DEFAULT_GROUP);
+      ADMIN.setRSGroup(Sets.newHashSet(tableName), RSGroupInfo.DEFAULT_GROUP);
+      ADMIN.moveServersToRSGroup(Sets.newHashSet(Address.fromParts("bogus", 123)),
+        RSGroupInfo.DEFAULT_GROUP);
       fail("Table " + tableName + " shouldn't have been successfully moved.");
     } catch (IOException ex) {
       assertTrue(ex instanceof TableNotFoundException);
     }
     // verify group change
-    assertNull(rsGroupAdmin.getRSGroup(tableName));
+    assertNull(ADMIN.getRSGroup(tableName));
   }
 
   @Test
@@ -452,13 +395,13 @@ public class TestRSGroupsAdmin1 extends TestRSGroupsBase {
     NamespaceDescriptor nspDesc =
       NamespaceDescriptor.create(nsp).addConfiguration(TableNamespaceManager.KEY_MAX_REGIONS, "5")
         .addConfiguration(TableNamespaceManager.KEY_MAX_TABLES, "2").build();
-    admin.createNamespace(nspDesc);
-    assertEquals(3, admin.listNamespaceDescriptors().length);
+    ADMIN.createNamespace(nspDesc);
+    assertEquals(3, ADMIN.listNamespaceDescriptors().length);
     ColumnFamilyDescriptor fam1 = ColumnFamilyDescriptorBuilder.of("fam1");
     TableDescriptor tableDescOne = TableDescriptorBuilder
       .newBuilder(TableName.valueOf(nsp + TableName.NAMESPACE_DELIM + "table1"))
       .setColumnFamily(fam1).build();
-    admin.createTable(tableDescOne);
+    ADMIN.createTable(tableDescOne);
 
     TableDescriptor tableDescTwo = TableDescriptorBuilder
       .newBuilder(TableName.valueOf(nsp + TableName.NAMESPACE_DELIM + "table2"))
@@ -466,8 +409,8 @@ public class TestRSGroupsAdmin1 extends TestRSGroupsBase {
     boolean constraintViolated = false;
 
     try {
-      admin.createTable(tableDescTwo, Bytes.toBytes("AAA"), Bytes.toBytes("ZZZ"), 6);
-      Assert.fail("Creation table should fail because of quota violation.");
+      ADMIN.createTable(tableDescTwo, Bytes.toBytes("AAA"), Bytes.toBytes("ZZZ"), 6);
+      fail("Creation table should fail because of quota violation.");
     } catch (Exception exp) {
       assertTrue(exp instanceof IOException);
       constraintViolated = true;
@@ -475,7 +418,7 @@ public class TestRSGroupsAdmin1 extends TestRSGroupsBase {
       assertTrue("Constraint not violated for table " + tableDescTwo.getTableName(),
         constraintViolated);
     }
-    List<RSGroupInfo> rsGroupInfoList = rsGroupAdmin.listRSGroups();
+    List<RSGroupInfo> rsGroupInfoList = RS_GROUP_ADMIN_CLIENT.listRSGroups();
     boolean foundTable2 = false;
     boolean foundTable1 = false;
     for (int i = 0; i < rsGroupInfoList.size(); i++) {
@@ -490,14 +433,13 @@ public class TestRSGroupsAdmin1 extends TestRSGroupsBase {
     assertTrue("Did not find table1 in rsgroup list", foundTable1);
 
     TEST_UTIL.deleteTable(tableDescOne.getTableName());
-    admin.deleteNamespace(nspDesc.getName());
+    ADMIN.deleteNamespace(nspDesc.getName());
     toggleQuotaCheckAndRestartMiniCluster(false);
 
   }
 
   @Test
-  public void testNotMoveTableToNullRSGroupWhenCreatingExistingTable()
-      throws Exception {
+  public void testNotMoveTableToNullRSGroupWhenCreatingExistingTable() throws Exception {
     // Trigger
     TableName tn1 = TableName.valueOf("t1");
     TEST_UTIL.createTable(tn1, "cf1");
@@ -509,19 +451,18 @@ public class TestRSGroupsAdmin1 extends TestRSGroupsBase {
     }
 
     // Wait then verify
-    //   Could not verify until the rollback of CreateTableProcedure is done
-    //   (that is, the coprocessor finishes its work),
-    //   or the table is still in the "default" rsgroup even though HBASE-21866
-    //   is not fixed.
+    // Could not verify until the rollback of CreateTableProcedure is done
+    // (that is, the coprocessor finishes its work),
+    // or the table is still in the "default" rsgroup even though HBASE-21866
+    // is not fixed.
     TEST_UTIL.waitFor(5000, new Waiter.Predicate<Exception>() {
       @Override
       public boolean evaluate() throws Exception {
-        return
-            (master.getMasterProcedureExecutor().getActiveExecutorCount() == 0);
+        return (MASTER.getMasterProcedureExecutor().getActiveExecutorCount() == 0);
       }
     });
-    SortedSet<TableName> tables
-        = rsGroupAdmin.getRSGroup(RSGroupInfo.DEFAULT_GROUP).getTables();
+    SortedSet<TableName> tables =
+      RS_GROUP_ADMIN_CLIENT.getRSGroupInfo(RSGroupInfo.DEFAULT_GROUP).getTables();
     assertTrue("Table 't1' must be in 'default' rsgroup", tables.contains(tn1));
 
     // Cleanup
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsAdmin2.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsAdmin2.java
index eb808e3..2faa786 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsAdmin2.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsAdmin2.java
@@ -44,33 +44,30 @@ import org.apache.hadoop.hbase.master.RegionState;
 import org.apache.hadoop.hbase.master.assignment.RegionStateNode;
 import org.apache.hadoop.hbase.net.Address;
 import org.apache.hadoop.hbase.testclassification.LargeTests;
+import org.apache.hadoop.hbase.testclassification.RSGroupTests;
 import org.apache.hadoop.hbase.util.Bytes;
 import org.apache.hadoop.hbase.util.Pair;
 import org.junit.After;
 import org.junit.AfterClass;
-import org.junit.Assert;
 import org.junit.Before;
 import org.junit.BeforeClass;
 import org.junit.ClassRule;
 import org.junit.Test;
 import org.junit.experimental.categories.Category;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import org.apache.hbase.thirdparty.com.google.common.base.Preconditions;
 import org.apache.hbase.thirdparty.com.google.common.collect.Sets;
 
-@RunWith(Parameterized.class)
-@Category({ LargeTests.class })
+@Category({ RSGroupTests.class, LargeTests.class })
 public class TestRSGroupsAdmin2 extends TestRSGroupsBase {
 
   @ClassRule
   public static final HBaseClassTestRule CLASS_RULE =
     HBaseClassTestRule.forClass(TestRSGroupsAdmin2.class);
 
-  protected static final Logger LOG = LoggerFactory.getLogger(TestRSGroupsAdmin2.class);
+  private static final Logger LOG = LoggerFactory.getLogger(TestRSGroupsAdmin2.class);
 
   @BeforeClass
   public static void setUp() throws Exception {
@@ -121,7 +118,7 @@ public class TestRSGroupsAdmin2 extends TestRSGroupsBase {
     }
     // get server which is not a member of new group
     ServerName tmpTargetServer = null;
-    for (ServerName server : admin.getClusterMetrics(EnumSet.of(Option.LIVE_SERVERS))
+    for (ServerName server : ADMIN.getClusterMetrics(EnumSet.of(Option.LIVE_SERVERS))
       .getLiveServerMetrics().keySet()) {
       if (!newGroup.containsServer(server.getAddress())) {
         tmpTargetServer = server;
@@ -130,11 +127,11 @@ public class TestRSGroupsAdmin2 extends TestRSGroupsBase {
     }
     final ServerName targetServer = tmpTargetServer;
     // move target server to group
-    rsGroupAdmin.moveToRSGroup(Sets.newHashSet(targetServer.getAddress()), newGroup.getName());
+    ADMIN.moveServersToRSGroup(Sets.newHashSet(targetServer.getAddress()), newGroup.getName());
     TEST_UTIL.waitFor(WAIT_TIMEOUT, new Waiter.Predicate<Exception>() {
       @Override
       public boolean evaluate() throws Exception {
-        return admin.getRegions(targetServer).size() <= 0;
+        return ADMIN.getRegions(targetServer).size() <= 0;
       }
     });
 
@@ -146,13 +143,13 @@ public class TestRSGroupsAdmin2 extends TestRSGroupsBase {
       public boolean evaluate() throws Exception {
         return getTableRegionMap().get(tableName) != null &&
           getTableRegionMap().get(tableName).size() == 6 &&
-          admin.getClusterMetrics(EnumSet.of(Option.REGIONS_IN_TRANSITION))
+          ADMIN.getClusterMetrics(EnumSet.of(Option.REGIONS_IN_TRANSITION))
             .getRegionStatesInTransition().size() < 1;
       }
     });
 
     // verify that targetServer didn't open it
-    for (RegionInfo region : admin.getRegions(targetServer)) {
+    for (RegionInfo region : ADMIN.getRegions(targetServer)) {
       if (targetRegion.equals(region.getRegionNameAsString())) {
         fail("Target server opened region");
       }
@@ -161,35 +158,35 @@ public class TestRSGroupsAdmin2 extends TestRSGroupsBase {
 
   @Test
   public void testRegionServerMove() throws IOException, InterruptedException {
-    int initNumGroups = rsGroupAdmin.listRSGroups().size();
+    int initNumGroups = ADMIN.listRSGroups().size();
     RSGroupInfo appInfo = addGroup(getGroupName(name.getMethodName()), 1);
     RSGroupInfo adminInfo = addGroup(getGroupName(name.getMethodName()), 1);
-    RSGroupInfo dInfo = rsGroupAdmin.getRSGroup(RSGroupInfo.DEFAULT_GROUP);
-    Assert.assertEquals(initNumGroups + 2, rsGroupAdmin.listRSGroups().size());
+    RSGroupInfo dInfo = ADMIN.getRSGroup(RSGroupInfo.DEFAULT_GROUP);
+    assertEquals(initNumGroups + 2, ADMIN.listRSGroups().size());
     assertEquals(1, adminInfo.getServers().size());
     assertEquals(1, appInfo.getServers().size());
     assertEquals(getNumServers() - 2, dInfo.getServers().size());
-    rsGroupAdmin.moveToRSGroup(appInfo.getServers(), RSGroupInfo.DEFAULT_GROUP);
-    rsGroupAdmin.removeRSGroup(appInfo.getName());
-    rsGroupAdmin.moveToRSGroup(adminInfo.getServers(), RSGroupInfo.DEFAULT_GROUP);
-    rsGroupAdmin.removeRSGroup(adminInfo.getName());
-    Assert.assertEquals(rsGroupAdmin.listRSGroups().size(), initNumGroups);
+    ADMIN.moveServersToRSGroup(appInfo.getServers(), RSGroupInfo.DEFAULT_GROUP);
+    ADMIN.removeRSGroup(appInfo.getName());
+    ADMIN.moveServersToRSGroup(adminInfo.getServers(), RSGroupInfo.DEFAULT_GROUP);
+    ADMIN.removeRSGroup(adminInfo.getName());
+    assertEquals(ADMIN.listRSGroups().size(), initNumGroups);
   }
 
   @Test
   public void testMoveServers() throws Exception {
     // create groups and assign servers
     addGroup("bar", 3);
-    rsGroupAdmin.addRSGroup("foo");
+    ADMIN.addRSGroup("foo");
 
-    RSGroupInfo barGroup = rsGroupAdmin.getRSGroup("bar");
-    RSGroupInfo fooGroup = rsGroupAdmin.getRSGroup("foo");
+    RSGroupInfo barGroup = ADMIN.getRSGroup("bar");
+    RSGroupInfo fooGroup = ADMIN.getRSGroup("foo");
     assertEquals(3, barGroup.getServers().size());
     assertEquals(0, fooGroup.getServers().size());
 
     // test fail bogus server move
     try {
-      rsGroupAdmin.moveToRSGroup(Sets.newHashSet(Address.fromString("foo:9999")), "foo");
+      ADMIN.moveServersToRSGroup(Sets.newHashSet(Address.fromString("foo:9999")), "foo");
       fail("Bogus servers shouldn't have been successfully moved.");
     } catch (IOException ex) {
       String exp = "Source RSGroup for server foo:9999 does not exist.";
@@ -199,34 +196,33 @@ public class TestRSGroupsAdmin2 extends TestRSGroupsBase {
 
     // test success case
     LOG.info("moving servers " + barGroup.getServers() + " to group foo");
-    rsGroupAdmin.moveToRSGroup(barGroup.getServers(), fooGroup.getName());
+    ADMIN.moveServersToRSGroup(barGroup.getServers(), fooGroup.getName());
 
-    barGroup = rsGroupAdmin.getRSGroup("bar");
-    fooGroup = rsGroupAdmin.getRSGroup("foo");
+    barGroup = ADMIN.getRSGroup("bar");
+    fooGroup = ADMIN.getRSGroup("foo");
     assertEquals(0, barGroup.getServers().size());
     assertEquals(3, fooGroup.getServers().size());
 
     LOG.info("moving servers " + fooGroup.getServers() + " to group default");
-    rsGroupAdmin.moveToRSGroup(fooGroup.getServers(), RSGroupInfo.DEFAULT_GROUP);
+    ADMIN.moveServersToRSGroup(fooGroup.getServers(), RSGroupInfo.DEFAULT_GROUP);
 
     TEST_UTIL.waitFor(WAIT_TIMEOUT, new Waiter.Predicate<Exception>() {
       @Override
       public boolean evaluate() throws Exception {
-        return getNumServers() == rsGroupAdmin.getRSGroup(RSGroupInfo.DEFAULT_GROUP)
-          .getServers().size();
+        return getNumServers() == ADMIN.getRSGroup(RSGroupInfo.DEFAULT_GROUP).getServers().size();
       }
     });
 
-    fooGroup = rsGroupAdmin.getRSGroup("foo");
+    fooGroup = ADMIN.getRSGroup("foo");
     assertEquals(0, fooGroup.getServers().size());
 
     // test group removal
     LOG.info("Remove group " + barGroup.getName());
-    rsGroupAdmin.removeRSGroup(barGroup.getName());
-    Assert.assertEquals(null, rsGroupAdmin.getRSGroup(barGroup.getName()));
+    ADMIN.removeRSGroup(barGroup.getName());
+    assertEquals(null, ADMIN.getRSGroup(barGroup.getName()));
     LOG.info("Remove group " + fooGroup.getName());
-    rsGroupAdmin.removeRSGroup(fooGroup.getName());
-    Assert.assertEquals(null, rsGroupAdmin.getRSGroup(fooGroup.getName()));
+    ADMIN.removeRSGroup(fooGroup.getName());
+    assertEquals(null, ADMIN.getRSGroup(fooGroup.getName()));
   }
 
   @Test
@@ -238,7 +234,7 @@ public class TestRSGroupsAdmin2 extends TestRSGroupsBase {
 
     // remove online servers
     try {
-      rsGroupAdmin.removeRSGroup(Sets.newHashSet(targetServer.getAddress()));
+      ADMIN.removeServersFromRSGroup(Sets.newHashSet(targetServer.getAddress()));
       fail("Online servers shouldn't have been successfully removed.");
     } catch (IOException ex) {
       String exp =
@@ -249,12 +245,12 @@ public class TestRSGroupsAdmin2 extends TestRSGroupsBase {
     assertTrue(newGroup.getServers().contains(targetServer.getAddress()));
 
     // remove dead servers
-    NUM_DEAD_SERVERS = cluster.getClusterMetrics().getDeadServerNames().size();
+    NUM_DEAD_SERVERS = CLUSTER.getClusterMetrics().getDeadServerNames().size();
     try {
       // stopping may cause an exception
       // due to the connection loss
       LOG.info("stopping server " + targetServer.getServerName());
-      admin.stopRegionServer(targetServer.getAddress().toString());
+      ADMIN.stopRegionServer(targetServer.getAddress().toString());
       NUM_DEAD_SERVERS++;
     } catch (Exception e) {
     }
@@ -263,13 +259,13 @@ public class TestRSGroupsAdmin2 extends TestRSGroupsBase {
     TEST_UTIL.waitFor(WAIT_TIMEOUT, new Waiter.Predicate<Exception>() {
       @Override
       public boolean evaluate() throws Exception {
-        return !master.getServerManager().areDeadServersInProgress() &&
-          cluster.getClusterMetrics().getDeadServerNames().size() == NUM_DEAD_SERVERS;
+        return !MASTER.getServerManager().areDeadServersInProgress() &&
+          CLUSTER.getClusterMetrics().getDeadServerNames().size() == NUM_DEAD_SERVERS;
       }
     });
 
     try {
-      rsGroupAdmin.removeRSGroup(Sets.newHashSet(targetServer.getAddress()));
+      ADMIN.removeServersFromRSGroup(Sets.newHashSet(targetServer.getAddress()));
       fail("Dead servers shouldn't have been successfully removed.");
     } catch (IOException ex) {
       String exp = "Server " + targetServer.getAddress() + " is on the dead servers list," +
@@ -282,20 +278,20 @@ public class TestRSGroupsAdmin2 extends TestRSGroupsBase {
     // remove decommissioned servers
     List<ServerName> serversToDecommission = new ArrayList<>();
     targetServer = getServerName(iterator.next());
-    assertTrue(master.getServerManager().getOnlineServers().containsKey(targetServer));
+    assertTrue(MASTER.getServerManager().getOnlineServers().containsKey(targetServer));
     serversToDecommission.add(targetServer);
 
-    admin.decommissionRegionServers(serversToDecommission, true);
-    assertEquals(1, admin.listDecommissionedRegionServers().size());
+    ADMIN.decommissionRegionServers(serversToDecommission, true);
+    assertEquals(1, ADMIN.listDecommissionedRegionServers().size());
 
     assertTrue(newGroup.getServers().contains(targetServer.getAddress()));
-    rsGroupAdmin.removeRSGroup(Sets.newHashSet(targetServer.getAddress()));
-    Set<Address> newGroupServers = rsGroupAdmin.getRSGroup(newGroup.getName()).getServers();
+    ADMIN.removeServersFromRSGroup(Sets.newHashSet(targetServer.getAddress()));
+    Set<Address> newGroupServers = ADMIN.getRSGroup(newGroup.getName()).getServers();
     assertFalse(newGroupServers.contains(targetServer.getAddress()));
     assertEquals(2, newGroupServers.size());
 
-    assertTrue(observer.preRemoveServersCalled);
-    assertTrue(observer.postRemoveServersCalled);
+    assertTrue(OBSERVER.preRemoveServersCalled);
+    assertTrue(OBSERVER.postRemoveServersCalled);
   }
 
   @Test
@@ -319,26 +315,25 @@ public class TestRSGroupsAdmin2 extends TestRSGroupsBase {
 
     // get server which is not a member of new group
     ServerName targetServer = null;
-    for (ServerName server : admin.getClusterMetrics(EnumSet.of(Option.LIVE_SERVERS))
+    for (ServerName server : ADMIN.getClusterMetrics(EnumSet.of(Option.LIVE_SERVERS))
       .getLiveServerMetrics().keySet()) {
       if (!newGroup.containsServer(server.getAddress()) &&
-        !rsGroupAdmin.getRSGroup("master").containsServer(server.getAddress())) {
+        !ADMIN.getRSGroup("master").containsServer(server.getAddress())) {
         targetServer = server;
         break;
       }
     }
 
-    LOG.debug("Print group info : " + rsGroupAdmin.listRSGroups());
-    int oldDefaultGroupServerSize =
-      rsGroupAdmin.getRSGroup(RSGroupInfo.DEFAULT_GROUP).getServers().size();
+    LOG.debug("Print group info : " + ADMIN.listRSGroups());
+    int oldDefaultGroupServerSize = ADMIN.getRSGroup(RSGroupInfo.DEFAULT_GROUP).getServers().size();
     int oldDefaultGroupTableSize =
-      rsGroupAdmin.getRSGroup(RSGroupInfo.DEFAULT_GROUP).getTables().size();
+      RS_GROUP_ADMIN_CLIENT.getRSGroupInfo(RSGroupInfo.DEFAULT_GROUP).getTables().size();
 
     // test fail bogus server move
     try {
-      rsGroupAdmin.moveToRSGroup(Sets.newHashSet(Address.fromString("foo:9999")),
-          newGroup.getName());
-      rsGroupAdmin.setRSGroup(Sets.newHashSet(tableName), newGroup.getName());
+      ADMIN.moveServersToRSGroup(Sets.newHashSet(Address.fromString("foo:9999")),
+        newGroup.getName());
+      ADMIN.setRSGroup(Sets.newHashSet(tableName), newGroup.getName());
       fail("Bogus servers shouldn't have been successfully moved.");
     } catch (IOException ex) {
       String exp = "Source RSGroup for server foo:9999 does not exist.";
@@ -347,19 +342,19 @@ public class TestRSGroupsAdmin2 extends TestRSGroupsBase {
     }
 
     // test move when src = dst
-    rsGroupAdmin.moveToRSGroup(Sets.newHashSet(targetServer.getAddress()),
-        RSGroupInfo.DEFAULT_GROUP);
-    rsGroupAdmin.setRSGroup(Sets.newHashSet(tableName), RSGroupInfo.DEFAULT_GROUP);
+    ADMIN.moveServersToRSGroup(Sets.newHashSet(targetServer.getAddress()),
+      RSGroupInfo.DEFAULT_GROUP);
+    ADMIN.setRSGroup(Sets.newHashSet(tableName), RSGroupInfo.DEFAULT_GROUP);
 
     // verify default group info
-    Assert.assertEquals(oldDefaultGroupServerSize,
-      rsGroupAdmin.getRSGroup(RSGroupInfo.DEFAULT_GROUP).getServers().size());
-    Assert.assertEquals(oldDefaultGroupTableSize,
-      rsGroupAdmin.getRSGroup(RSGroupInfo.DEFAULT_GROUP).getTables().size());
+    assertEquals(oldDefaultGroupServerSize,
+      ADMIN.getRSGroup(RSGroupInfo.DEFAULT_GROUP).getServers().size());
+    assertEquals(oldDefaultGroupTableSize,
+      RS_GROUP_ADMIN_CLIENT.getRSGroupInfo(RSGroupInfo.DEFAULT_GROUP).getTables().size());
 
     // verify new group info
-    Assert.assertEquals(1, rsGroupAdmin.getRSGroup(newGroup.getName()).getServers().size());
-    Assert.assertEquals(0, rsGroupAdmin.getRSGroup(newGroup.getName()).getTables().size());
+    assertEquals(1, ADMIN.getRSGroup(newGroup.getName()).getServers().size());
+    assertEquals(0, RS_GROUP_ADMIN_CLIENT.getRSGroupInfo(newGroup.getName()).getTables().size());
 
     // get all region to move targetServer
     List<String> regionList = getTableRegionMap().get(tableName);
@@ -375,63 +370,61 @@ public class TestRSGroupsAdmin2 extends TestRSGroupsBase {
         return getTableRegionMap().get(tableName) != null &&
           getTableRegionMap().get(tableName).size() == 5 &&
           getTableServerRegionMap().get(tableName).size() == 1 &&
-          admin.getClusterMetrics(EnumSet.of(Option.REGIONS_IN_TRANSITION))
+          ADMIN.getClusterMetrics(EnumSet.of(Option.REGIONS_IN_TRANSITION))
             .getRegionStatesInTransition().size() < 1;
       }
     });
 
     // verify that all region move to targetServer
-    Assert.assertEquals(5, getTableServerRegionMap().get(tableName).get(targetServer).size());
+    assertEquals(5, getTableServerRegionMap().get(tableName).get(targetServer).size());
 
     // move targetServer and table to newGroup
     LOG.info("moving server and table to newGroup");
-    rsGroupAdmin.moveToRSGroup(Sets.newHashSet(targetServer.getAddress()),
-        newGroup.getName());
-    rsGroupAdmin.setRSGroup(Sets.newHashSet(tableName), newGroup.getName());
+    ADMIN.moveServersToRSGroup(Sets.newHashSet(targetServer.getAddress()), newGroup.getName());
+    ADMIN.setRSGroup(Sets.newHashSet(tableName), newGroup.getName());
 
     // verify group change
-    Assert.assertEquals(newGroup.getName(),
-      rsGroupAdmin.getRSGroup(tableName).getName());
+    assertEquals(newGroup.getName(), ADMIN.getRSGroup(tableName).getName());
 
     // verify servers' not exist in old group
-    Set<Address> defaultServers =
-      rsGroupAdmin.getRSGroup(RSGroupInfo.DEFAULT_GROUP).getServers();
+    Set<Address> defaultServers = ADMIN.getRSGroup(RSGroupInfo.DEFAULT_GROUP).getServers();
     assertFalse(defaultServers.contains(targetServer.getAddress()));
 
     // verify servers' exist in new group
-    Set<Address> newGroupServers = rsGroupAdmin.getRSGroup(newGroup.getName()).getServers();
+    Set<Address> newGroupServers = ADMIN.getRSGroup(newGroup.getName()).getServers();
     assertTrue(newGroupServers.contains(targetServer.getAddress()));
 
     // verify tables' not exist in old group
     Set<TableName> defaultTables =
-      rsGroupAdmin.getRSGroup(RSGroupInfo.DEFAULT_GROUP).getTables();
+      RS_GROUP_ADMIN_CLIENT.getRSGroupInfo(RSGroupInfo.DEFAULT_GROUP).getTables();
     assertFalse(defaultTables.contains(tableName));
 
     // verify tables' exist in new group
-    Set<TableName> newGroupTables = rsGroupAdmin.getRSGroup(newGroup.getName()).getTables();
+    Set<TableName> newGroupTables =
+      RS_GROUP_ADMIN_CLIENT.getRSGroupInfo(newGroup.getName()).getTables();
     assertTrue(newGroupTables.contains(tableName));
 
-    // verify that all region still assgin on targetServer
+    // verify that all region still assign on targetServer
     // TODO: uncomment after we reimplement moveServersAndTables, now the implementation is
     // moveToRSGroup first and then moveTables, so the region will be moved to other region servers.
-    // Assert.assertEquals(5, getTableServerRegionMap().get(tableName).get(targetServer).size());
+    // assertEquals(5, getTableServerRegionMap().get(tableName).get(targetServer).size());
 
-    assertTrue(observer.preMoveServersCalled);
-    assertTrue(observer.postMoveServersCalled);
+    assertTrue(OBSERVER.preMoveServersCalled);
+    assertTrue(OBSERVER.postMoveServersCalled);
   }
 
   @Test
   public void testMoveServersFromDefaultGroup() throws Exception {
     // create groups and assign servers
-    rsGroupAdmin.addRSGroup("foo");
+    ADMIN.addRSGroup("foo");
 
-    RSGroupInfo fooGroup = rsGroupAdmin.getRSGroup("foo");
+    RSGroupInfo fooGroup = ADMIN.getRSGroup("foo");
     assertEquals(0, fooGroup.getServers().size());
-    RSGroupInfo defaultGroup = rsGroupAdmin.getRSGroup(RSGroupInfo.DEFAULT_GROUP);
+    RSGroupInfo defaultGroup = ADMIN.getRSGroup(RSGroupInfo.DEFAULT_GROUP);
 
     // test remove all servers from default
     try {
-      rsGroupAdmin.moveToRSGroup(defaultGroup.getServers(), fooGroup.getName());
+      ADMIN.moveServersToRSGroup(defaultGroup.getServers(), fooGroup.getName());
       fail(RSGroupInfoManagerImpl.KEEP_ONE_SERVER_IN_DEFAULT_ERROR_MESSAGE);
     } catch (ConstraintException ex) {
       assertTrue(
@@ -443,51 +436,49 @@ public class TestRSGroupsAdmin2 extends TestRSGroupsBase {
       Address serverInDefaultGroup = defaultGroup.getServers().iterator().next();
       LOG.info("moving server " + serverInDefaultGroup + " from group default to group " +
         fooGroup.getName());
-      rsGroupAdmin.moveToRSGroup(Sets.newHashSet(serverInDefaultGroup), fooGroup.getName());
+      ADMIN.moveServersToRSGroup(Sets.newHashSet(serverInDefaultGroup), fooGroup.getName());
     }
 
-    fooGroup = rsGroupAdmin.getRSGroup("foo");
+    fooGroup = ADMIN.getRSGroup("foo");
     LOG.info("moving servers " + fooGroup.getServers() + " to group default");
-    rsGroupAdmin.moveToRSGroup(fooGroup.getServers(), RSGroupInfo.DEFAULT_GROUP);
+    ADMIN.moveServersToRSGroup(fooGroup.getServers(), RSGroupInfo.DEFAULT_GROUP);
 
     TEST_UTIL.waitFor(WAIT_TIMEOUT, new Waiter.Predicate<Exception>() {
       @Override
       public boolean evaluate() throws Exception {
-        return getNumServers() == rsGroupAdmin.getRSGroup(RSGroupInfo.DEFAULT_GROUP)
-          .getServers().size();
+        return getNumServers() == ADMIN.getRSGroup(RSGroupInfo.DEFAULT_GROUP).getServers().size();
       }
     });
 
-    fooGroup = rsGroupAdmin.getRSGroup("foo");
+    fooGroup = ADMIN.getRSGroup("foo");
     assertEquals(0, fooGroup.getServers().size());
 
     // test group removal
     LOG.info("Remove group " + fooGroup.getName());
-    rsGroupAdmin.removeRSGroup(fooGroup.getName());
-    Assert.assertEquals(null, rsGroupAdmin.getRSGroup(fooGroup.getName()));
+    ADMIN.removeRSGroup(fooGroup.getName());
+    assertEquals(null, ADMIN.getRSGroup(fooGroup.getName()));
   }
 
   @Test
   public void testFailedMoveBeforeRetryExhaustedWhenMoveServer() throws Exception {
     String groupName = getGroupName(name.getMethodName());
-    rsGroupAdmin.addRSGroup(groupName);
-    final RSGroupInfo newGroup = rsGroupAdmin.getRSGroup(groupName);
-    Pair<ServerName, RegionStateNode> gotPair = createTableWithRegionSplitting(newGroup,
-        10);
+    ADMIN.addRSGroup(groupName);
+    final RSGroupInfo newGroup = ADMIN.getRSGroup(groupName);
+    Pair<ServerName, RegionStateNode> gotPair = createTableWithRegionSplitting(newGroup, 10);
 
     // start thread to recover region state
     final ServerName movedServer = gotPair.getFirst();
     final RegionStateNode rsn = gotPair.getSecond();
     AtomicBoolean changed = new AtomicBoolean(false);
     Thread t1 = recoverRegionStateThread(movedServer,
-      server -> master.getAssignmentManager().getRegionsOnServer(movedServer), rsn, changed);
+      server -> MASTER.getAssignmentManager().getRegionsOnServer(movedServer), rsn, changed);
     t1.start();
 
     // move target server to group
     Thread t2 = new Thread(() -> {
       LOG.info("thread2 start running, to move regions");
       try {
-        rsGroupAdmin.moveToRSGroup(Sets.newHashSet(movedServer.getAddress()), newGroup.getName());
+        ADMIN.moveServersToRSGroup(Sets.newHashSet(movedServer.getAddress()), newGroup.getName());
       } catch (IOException e) {
         LOG.error("move server error", e);
       }
@@ -501,8 +492,8 @@ public class TestRSGroupsAdmin2 extends TestRSGroupsBase {
       @Override
       public boolean evaluate() {
         if (changed.get()) {
-          return master.getAssignmentManager().getRegionsOnServer(movedServer).size() == 0 && !rsn
-              .getRegionLocation().equals(movedServer);
+          return MASTER.getAssignmentManager().getRegionsOnServer(movedServer).size() == 0 &&
+            !rsn.getRegionLocation().equals(movedServer);
         }
         return false;
       }
@@ -510,15 +501,15 @@ public class TestRSGroupsAdmin2 extends TestRSGroupsBase {
   }
 
   private <T> Thread recoverRegionStateThread(T owner, Function<T, List<RegionInfo>> getRegions,
-      RegionStateNode rsn, AtomicBoolean changed){
+    RegionStateNode rsn, AtomicBoolean changed) {
     return new Thread(() -> {
       LOG.info("thread1 start running, will recover region state");
       long current = System.currentTimeMillis();
       // wait until there is only left the region we changed state and recover its state.
       // wait time is set according to the number of max retries, all except failed regions will be
       // moved in one retry, and will sleep 1s until next retry.
-      while (System.currentTimeMillis() - current <=
-          RSGroupInfoManagerImpl.DEFAULT_MAX_RETRY_VALUE * 1000) {
+      while (System.currentTimeMillis() -
+        current <= RSGroupInfoManagerImpl.DEFAULT_MAX_RETRY_VALUE * 1000) {
         List<RegionInfo> regions = getRegions.apply(owner);
         LOG.debug("server table region size is:{}", regions.size());
         assert regions.size() >= 1;
@@ -526,7 +517,7 @@ public class TestRSGroupsAdmin2 extends TestRSGroupsBase {
         // exception caused by the strange region state.
         if (regions.size() == 1) {
           assertEquals(regions.get(0).getRegionNameAsString(),
-              rsn.getRegionInfo().getRegionNameAsString());
+            rsn.getRegionInfo().getRegionNameAsString());
           rsn.setState(RegionState.State.OPEN);
           LOG.info("set region {} state OPEN", rsn.getRegionInfo().getRegionNameAsString());
           changed.set(true);
@@ -538,7 +529,7 @@ public class TestRSGroupsAdmin2 extends TestRSGroupsBase {
   }
 
   private Pair<ServerName, RegionStateNode> createTableWithRegionSplitting(RSGroupInfo rsGroupInfo,
-      int tableRegionCount) throws Exception{
+    int tableRegionCount) throws Exception {
     final byte[] familyNameBytes = Bytes.toBytes("f");
     // All the regions created below will be assigned to the default group.
     TEST_UTIL.createMultiRegionTable(tableName, familyNameBytes, tableRegionCount);
@@ -562,11 +553,11 @@ public class TestRSGroupsAdmin2 extends TestRSGroupsBase {
    * @return source server of region, and region state
    * @throws IOException if methods called throw
    */
-  private Pair<ServerName, RegionStateNode> randomlySetOneRegionStateToSplitting(
-      RSGroupInfo newGroup) throws IOException{
+  private Pair<ServerName, RegionStateNode>
+    randomlySetOneRegionStateToSplitting(RSGroupInfo newGroup) throws IOException {
     // get target server to move, which should has more than one regions
     // randomly set a region state to SPLITTING to make move fail
-    return randomlySetRegionState(newGroup, RegionState.State.SPLITTING,tableName);
+    return randomlySetRegionState(newGroup, RegionState.State.SPLITTING, tableName);
   }
 
   private Pair<ServerName, RegionStateNode> randomlySetRegionState(RSGroupInfo groupInfo,
@@ -605,7 +596,7 @@ public class TestRSGroupsAdmin2 extends TestRSGroupsBase {
   }
 
   @Test
-  public void testFailedMoveServersAndRepair() throws Exception{
+  public void testFailedMoveServersAndRepair() throws Exception {
     // This UT calls moveToRSGroup() twice to test the idempotency of it.
     // The first time, movement fails because a region is made in SPLITTING state
     // which will not be moved.
@@ -615,35 +606,36 @@ public class TestRSGroupsAdmin2 extends TestRSGroupsBase {
 
     // create table
     // randomly set a region state to SPLITTING to make move abort
-    Pair<ServerName, RegionStateNode> gotPair = createTableWithRegionSplitting(newGroup,
-        new Random().nextInt(8) + 4);
+    Pair<ServerName, RegionStateNode> gotPair =
+      createTableWithRegionSplitting(newGroup, new Random().nextInt(8) + 4);
     RegionStateNode rsn = gotPair.getSecond();
     ServerName srcServer = rsn.getRegionLocation();
 
     // move server to newGroup and check regions
     try {
-      rsGroupAdmin.moveToRSGroup(Sets.newHashSet(srcServer.getAddress()), newGroup.getName());
-      fail("should get IOException when retry exhausted but there still exists failed moved "
-          + "regions");
-    }catch (Exception e){
-      assertTrue(e.getMessage().contains(
-          gotPair.getSecond().getRegionInfo().getRegionNameAsString()));
+      ADMIN.moveServersToRSGroup(Sets.newHashSet(srcServer.getAddress()), newGroup.getName());
+      fail("should get IOException when retry exhausted but there still exists failed moved " +
+        "regions");
+    } catch (Exception e) {
+      assertTrue(
+        e.getMessage().contains(gotPair.getSecond().getRegionInfo().getRegionNameAsString()));
     }
-    for(RegionInfo regionInfo : master.getAssignmentManager().getAssignedRegions()){
+    for (RegionInfo regionInfo : MASTER.getAssignmentManager().getAssignedRegions()) {
       if (regionInfo.getTable().equals(tableName) && regionInfo.equals(rsn.getRegionInfo())) {
-        assertEquals(master.getAssignmentManager().getRegionStates()
-            .getRegionServerOfRegion(regionInfo), srcServer);
+        assertEquals(
+          MASTER.getAssignmentManager().getRegionStates().getRegionServerOfRegion(regionInfo),
+          srcServer);
       }
     }
 
     // retry move server to newGroup and check if all regions on srcServer was moved
     rsn.setState(RegionState.State.OPEN);
-    rsGroupAdmin.moveToRSGroup(Sets.newHashSet(srcServer.getAddress()), newGroup.getName());
-    assertEquals(master.getAssignmentManager().getRegionsOnServer(srcServer).size(), 0);
+    ADMIN.moveServersToRSGroup(Sets.newHashSet(srcServer.getAddress()), newGroup.getName());
+    assertEquals(MASTER.getAssignmentManager().getRegionsOnServer(srcServer).size(), 0);
   }
 
   @Test
-  public void testFailedMoveServersTablesAndRepair() throws Exception{
+  public void testFailedMoveServersTablesAndRepair() throws Exception {
     // This UT calls moveTablesAndServers() twice to test the idempotency of it.
     // The first time, movement fails because a region is made in SPLITTING state
     // which will not be moved.
@@ -654,40 +646,39 @@ public class TestRSGroupsAdmin2 extends TestRSGroupsBase {
     final byte[] familyNameBytes = Bytes.toBytes("f");
     TableName table1 = TableName.valueOf(tableName.getNameAsString() + "_1");
     TableName table2 = TableName.valueOf(tableName.getNameAsString() + "_2");
-    TEST_UTIL.createMultiRegionTable(table1, familyNameBytes,
-        new Random().nextInt(12) + 4);
-    TEST_UTIL.createMultiRegionTable(table2, familyNameBytes,
-        new Random().nextInt(12) + 4);
+    TEST_UTIL.createMultiRegionTable(table1, familyNameBytes, new Random().nextInt(12) + 4);
+    TEST_UTIL.createMultiRegionTable(table2, familyNameBytes, new Random().nextInt(12) + 4);
 
     // randomly set a region state to SPLITTING to make move abort
     Pair<ServerName, RegionStateNode> gotPair =
-        randomlySetRegionState(newGroup, RegionState.State.SPLITTING, table1, table2);
+      randomlySetRegionState(newGroup, RegionState.State.SPLITTING, table1, table2);
     RegionStateNode rsn = gotPair.getSecond();
     ServerName srcServer = rsn.getRegionLocation();
 
     // move server and table to newGroup and check regions
     try {
-      rsGroupAdmin.moveToRSGroup(Sets.newHashSet(srcServer.getAddress()), newGroup.getName());
-      rsGroupAdmin.setRSGroup(Sets.newHashSet(table2), newGroup.getName());
-      fail("should get IOException when retry exhausted but there still exists failed moved "
-          + "regions");
-    }catch (Exception e){
-      assertTrue(e.getMessage().contains(
-          gotPair.getSecond().getRegionInfo().getRegionNameAsString()));
+      ADMIN.moveServersToRSGroup(Sets.newHashSet(srcServer.getAddress()), newGroup.getName());
+      ADMIN.setRSGroup(Sets.newHashSet(table2), newGroup.getName());
+      fail("should get IOException when retry exhausted but there still exists failed moved " +
+        "regions");
+    } catch (Exception e) {
+      assertTrue(
+        e.getMessage().contains(gotPair.getSecond().getRegionInfo().getRegionNameAsString()));
     }
-    for(RegionInfo regionInfo : master.getAssignmentManager().getAssignedRegions()){
+    for (RegionInfo regionInfo : MASTER.getAssignmentManager().getAssignedRegions()) {
       if (regionInfo.getTable().equals(table1) && regionInfo.equals(rsn.getRegionInfo())) {
-        assertEquals(master.getAssignmentManager().getRegionStates()
-            .getRegionServerOfRegion(regionInfo), srcServer);
+        assertEquals(
+          MASTER.getAssignmentManager().getRegionStates().getRegionServerOfRegion(regionInfo),
+          srcServer);
       }
     }
 
     // retry moveServersAndTables to newGroup and check if all regions on srcServer belongs to
     // table2
     rsn.setState(RegionState.State.OPEN);
-    rsGroupAdmin.moveToRSGroup(Sets.newHashSet(srcServer.getAddress()), newGroup.getName());
-    rsGroupAdmin.setRSGroup(Sets.newHashSet(table2), newGroup.getName());
-    for(RegionInfo regionsInfo : master.getAssignmentManager().getRegionsOnServer(srcServer)){
+    ADMIN.moveServersToRSGroup(Sets.newHashSet(srcServer.getAddress()), newGroup.getName());
+    ADMIN.setRSGroup(Sets.newHashSet(table2), newGroup.getName());
+    for (RegionInfo regionsInfo : MASTER.getAssignmentManager().getRegionsOnServer(srcServer)) {
       assertEquals(regionsInfo.getTable(), table2);
     }
   }
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsBalance.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsBalance.java
index be93186..0230671 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsBalance.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsBalance.java
@@ -34,6 +34,7 @@ import org.apache.hadoop.hbase.client.RegionInfo;
 import org.apache.hadoop.hbase.client.TableDescriptor;
 import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
 import org.apache.hadoop.hbase.testclassification.MediumTests;
+import org.apache.hadoop.hbase.testclassification.RSGroupTests;
 import org.apache.hadoop.hbase.util.Bytes;
 import org.junit.After;
 import org.junit.AfterClass;
@@ -42,13 +43,10 @@ import org.junit.BeforeClass;
 import org.junit.ClassRule;
 import org.junit.Test;
 import org.junit.experimental.categories.Category;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-@RunWith(Parameterized.class)
-@Category({ MediumTests.class })
+@Category({ RSGroupTests.class, MediumTests.class })
 public class TestRSGroupsBalance extends TestRSGroupsBase {
 
   @ClassRule
@@ -83,15 +81,15 @@ public class TestRSGroupsBalance extends TestRSGroupsBase {
     String newGroupName = getGroupName(name.getMethodName());
     addGroup(newGroupName, 3);
 
-    final TableName tableName = TableName.valueOf(tablePrefix + "_ns",
-        getNameWithoutIndex(name.getMethodName()));
-    admin.createNamespace(NamespaceDescriptor.create(tableName.getNamespaceAsString())
+    final TableName tableName =
+      TableName.valueOf(TABLE_PREFIX + "_ns", getNameWithoutIndex(name.getMethodName()));
+    ADMIN.createNamespace(NamespaceDescriptor.create(tableName.getNamespaceAsString())
       .addConfiguration(RSGroupInfo.NAMESPACE_DESC_PROP_GROUP, newGroupName).build());
     final TableDescriptor desc = TableDescriptorBuilder.newBuilder(tableName)
       .setColumnFamily(ColumnFamilyDescriptorBuilder.of("f")).build();
     byte[] startKey = Bytes.toBytes("aaaaa");
     byte[] endKey = Bytes.toBytes("zzzzz");
-    admin.createTable(desc, startKey, endKey, 6);
+    ADMIN.createTable(desc, startKey, endKey, 6);
     TEST_UTIL.waitFor(WAIT_TIMEOUT, new Waiter.Predicate<Exception>() {
       @Override
       public boolean evaluate() throws Exception {
@@ -106,9 +104,9 @@ public class TestRSGroupsBalance extends TestRSGroupsBase {
     // make assignment uneven, move all regions to one server
     Map<ServerName, List<String>> assignMap = getTableServerRegionMap().get(tableName);
     final ServerName first = assignMap.entrySet().iterator().next().getKey();
-    for (RegionInfo region : admin.getRegions(tableName)) {
+    for (RegionInfo region : ADMIN.getRegions(tableName)) {
       if (!assignMap.get(first).contains(region.getRegionNameAsString())) {
-        admin.move(region.getEncodedNameAsBytes(), first);
+        ADMIN.move(region.getEncodedNameAsBytes(), first);
       }
     }
     TEST_UTIL.waitFor(WAIT_TIMEOUT, new Waiter.Predicate<Exception>() {
@@ -127,18 +125,18 @@ public class TestRSGroupsBalance extends TestRSGroupsBase {
     });
 
     // balance the other group and make sure it doesn't affect the new group
-    admin.balancerSwitch(true, true);
-    rsGroupAdmin.balanceRSGroup(RSGroupInfo.DEFAULT_GROUP);
+    ADMIN.balancerSwitch(true, true);
+    ADMIN.balanceRSGroup(RSGroupInfo.DEFAULT_GROUP);
     assertEquals(6, getTableServerRegionMap().get(tableName).get(first).size());
 
     // disable balance, balancer will not be run and return false
-    admin.balancerSwitch(false, true);
-    assertFalse(rsGroupAdmin.balanceRSGroup(newGroupName));
+    ADMIN.balancerSwitch(false, true);
+    assertFalse(ADMIN.balanceRSGroup(newGroupName));
     assertEquals(6, getTableServerRegionMap().get(tableName).get(first).size());
 
     // enable balance
-    admin.balancerSwitch(true, true);
-    rsGroupAdmin.balanceRSGroup(newGroupName);
+    ADMIN.balancerSwitch(true, true);
+    ADMIN.balanceRSGroup(newGroupName);
     TEST_UTIL.waitFor(WAIT_TIMEOUT, new Waiter.Predicate<Exception>() {
       @Override
       public boolean evaluate() throws Exception {
@@ -150,35 +148,35 @@ public class TestRSGroupsBalance extends TestRSGroupsBase {
         return true;
       }
     });
-    admin.balancerSwitch(false, true);
+    ADMIN.balancerSwitch(false, true);
   }
 
   @Test
   public void testMisplacedRegions() throws Exception {
-    String namespace = tablePrefix + "_" + getNameWithoutIndex(name.getMethodName());
+    String namespace = TABLE_PREFIX + "_" + getNameWithoutIndex(name.getMethodName());
     TEST_UTIL.getAdmin().createNamespace(NamespaceDescriptor.create(namespace).build());
-    final TableName tableName = TableName.valueOf(namespace, tablePrefix + "_" +
-        getNameWithoutIndex(name.getMethodName()));
+    final TableName tableName =
+      TableName.valueOf(namespace, TABLE_PREFIX + "_" + getNameWithoutIndex(name.getMethodName()));
 
     final RSGroupInfo rsGroupInfo = addGroup(getGroupName(name.getMethodName()), 1);
 
     TEST_UTIL.createMultiRegionTable(tableName, new byte[] { 'f' }, 15);
     TEST_UTIL.waitUntilAllRegionsAssigned(tableName);
     TEST_UTIL.getAdmin().modifyNamespace(NamespaceDescriptor.create(namespace)
-        .addConfiguration(RSGroupInfo.NAMESPACE_DESC_PROP_GROUP, rsGroupInfo.getName()).build());
+      .addConfiguration(RSGroupInfo.NAMESPACE_DESC_PROP_GROUP, rsGroupInfo.getName()).build());
 
-    admin.balancerSwitch(true, true);
-    assertTrue(rsGroupAdmin.balanceRSGroup(rsGroupInfo.getName()));
-    admin.balancerSwitch(false, true);
-    assertTrue(observer.preBalanceRSGroupCalled);
-    assertTrue(observer.postBalanceRSGroupCalled);
+    ADMIN.balancerSwitch(true, true);
+    assertTrue(ADMIN.balanceRSGroup(rsGroupInfo.getName()));
+    ADMIN.balancerSwitch(false, true);
+    assertTrue(OBSERVER.preBalanceRSGroupCalled);
+    assertTrue(OBSERVER.postBalanceRSGroupCalled);
 
     TEST_UTIL.waitFor(60000, new Predicate<Exception>() {
       @Override
       public boolean evaluate() throws Exception {
         ServerName serverName =
-            ServerName.valueOf(rsGroupInfo.getServers().iterator().next().toString(), 1);
-        return admin.getConnection().getAdmin().getRegions(serverName).size() == 15;
+          ServerName.valueOf(rsGroupInfo.getServers().iterator().next().toString(), 1);
+        return ADMIN.getConnection().getAdmin().getRegions(serverName).size() == 15;
       }
     });
   }
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsBase.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsBase.java
index 6626fba..7963126 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsBase.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsBase.java
@@ -21,23 +21,20 @@ import static org.junit.Assert.assertTrue;
 
 import java.io.IOException;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.EnumSet;
 import java.util.HashSet;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
-import java.util.Random;
 import java.util.Set;
 import java.util.TreeMap;
-import java.util.function.Supplier;
+import java.util.concurrent.ThreadLocalRandom;
 import java.util.regex.Pattern;
 import org.apache.hadoop.hbase.ClusterMetrics;
 import org.apache.hadoop.hbase.ClusterMetrics.Option;
 import org.apache.hadoop.hbase.HBaseCluster;
 import org.apache.hadoop.hbase.HBaseTestingUtility;
-import org.apache.hadoop.hbase.HConstants;
 import org.apache.hadoop.hbase.MiniHBaseCluster;
 import org.apache.hadoop.hbase.NamespaceDescriptor;
 import org.apache.hadoop.hbase.ServerName;
@@ -59,7 +56,6 @@ import org.apache.hadoop.hbase.net.Address;
 import org.apache.hadoop.hbase.quotas.QuotaUtil;
 import org.junit.Rule;
 import org.junit.rules.TestName;
-import org.junit.runners.Parameterized;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -69,22 +65,21 @@ import org.apache.hbase.thirdparty.com.google.common.collect.Sets;
 public abstract class TestRSGroupsBase {
   protected static final Logger LOG = LoggerFactory.getLogger(TestRSGroupsBase.class);
 
-  //shared
-  protected final static String groupPrefix = "Group";
-  protected final static String tablePrefix = "Group";
-  protected final static Random rand = new Random();
+  // shared
+  protected static final String GROUP_PREFIX = "Group";
+  protected static final String TABLE_PREFIX = "Group";
 
-  //shared, cluster type specific
+  // shared, cluster type specific
   protected static HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
-  protected static Admin admin;
-  protected static HBaseCluster cluster;
-  protected static HMaster master;
+  protected static Admin ADMIN;
+  protected static HBaseCluster CLUSTER;
+  protected static HMaster MASTER;
   protected boolean INIT = false;
-  protected static RSGroupAdminEndpoint rsGroupAdminEndpoint;
-  protected static CPMasterObserver observer;
+  protected static CPMasterObserver OBSERVER;
+  protected static RSGroupAdminClient RS_GROUP_ADMIN_CLIENT;
 
   public final static long WAIT_TIMEOUT = 60000;
-  public final static int NUM_SLAVES_BASE = 4; //number of slaves for the smallest cluster
+  public final static int NUM_SLAVES_BASE = 4; // number of slaves for the smallest cluster
   public static int NUM_DEAD_SERVERS = 0;
 
   // Per test variables
@@ -92,59 +87,17 @@ public abstract class TestRSGroupsBase {
   public TestName name = new TestName();
   protected TableName tableName;
 
-  protected Admin rsGroupAdmin;
-
-  @Parameterized.Parameter
-  public Supplier<Object> getAdmin;
-
-  private static RSGroupAdminClient getRSGroupAdmin(){
-    try {
-      return new VerifyingRSGroupAdminClient(
-          new RSGroupAdminClient(TEST_UTIL.getConnection()), TEST_UTIL.getConfiguration());
-    } catch (IOException e) {
-      LOG.error("Get group admin failed", e);
-      return null;
-    }
-  }
-
-  private static Admin getAdmin(){
-    try {
-      return TEST_UTIL.getAdmin();
-    } catch (IOException e) {
-      LOG.error("Get hbase admin failed", e);
-      return null;
-    }
-  }
-
-  public static Object resetAdminConnection(Object admin) {
-    if(admin instanceof RSGroupAdminClient) {
-      return getRSGroupAdmin();
-    }else {
-      return getAdmin();
-    }
-  }
-
   public static String getNameWithoutIndex(String name) {
     return name.split("\\[")[0];
   }
 
-  @Parameterized.Parameters
-  public static List<Object[]> params() {
-    return Arrays.asList(new Supplier<?>[] { TestRSGroupsBase::getRSGroupAdmin },
-        new Supplier<?>[] { TestRSGroupsBase::getAdmin });
-  }
-
   public static void setUpTestBeforeClass() throws Exception {
-    TEST_UTIL.getConfiguration().setFloat(
-            "hbase.master.balancer.stochastic.tableSkewCost", 6000);
-    TEST_UTIL.getConfiguration().set(
-        HConstants.HBASE_MASTER_LOADBALANCER_CLASS,
-        RSGroupBasedLoadBalancer.class.getName());
+    TEST_UTIL.getConfiguration().setFloat("hbase.master.balancer.stochastic.tableSkewCost", 6000);
+    TEST_UTIL.getConfiguration().setBoolean(RSGroupInfoManager.RS_GROUP_ENABLED, true);
     TEST_UTIL.getConfiguration().set(CoprocessorHost.MASTER_COPROCESSOR_CONF_KEY,
-        RSGroupAdminEndpoint.class.getName() + "," + CPMasterObserver.class.getName());
-    TEST_UTIL.getConfiguration().setInt(
-        ServerManager.WAIT_ON_REGIONSERVERS_MINTOSTART,
-        NUM_SLAVES_BASE - 1);
+      RSGroupAdminEndpoint.class.getName() + "," + CPMasterObserver.class.getName());
+    TEST_UTIL.getConfiguration().setInt(ServerManager.WAIT_ON_REGIONSERVERS_MINTOSTART,
+      NUM_SLAVES_BASE - 1);
     TEST_UTIL.getConfiguration().setBoolean(SnapshotManager.HBASE_SNAPSHOT_ENABLED, true);
     TEST_UTIL.getConfiguration().setInt("hbase.rpc.timeout", 100000);
 
@@ -152,28 +105,23 @@ public abstract class TestRSGroupsBase {
     initialize();
   }
 
-  public void setAdmin(){
-    rsGroupAdmin = (Admin) getAdmin.get();
-  }
-
   protected static void initialize() throws Exception {
-    admin = TEST_UTIL.getAdmin();
-    cluster = TEST_UTIL.getHBaseCluster();
-    master = TEST_UTIL.getMiniHBaseCluster().getMaster();
+    ADMIN = new VerifyingRSGroupAdmin(TEST_UTIL.getConfiguration());
+    CLUSTER = TEST_UTIL.getHBaseCluster();
+    MASTER = TEST_UTIL.getMiniHBaseCluster().getMaster();
 
-    //wait for balancer to come online
+    // wait for balancer to come online
     TEST_UTIL.waitFor(WAIT_TIMEOUT, new Waiter.Predicate<Exception>() {
       @Override
       public boolean evaluate() throws Exception {
-        return master.isInitialized() &&
-            ((RSGroupBasedLoadBalancer) master.getLoadBalancer()).isOnline();
+        return MASTER.isInitialized() &&
+          ((RSGroupBasedLoadBalancer) MASTER.getLoadBalancer()).isOnline();
       }
     });
-    admin.balancerSwitch(false, true);
-    MasterCoprocessorHost host = master.getMasterCoprocessorHost();
-    observer = (CPMasterObserver) host.findCoprocessor(CPMasterObserver.class.getName());
-    rsGroupAdminEndpoint = (RSGroupAdminEndpoint)
-        host.findCoprocessor(RSGroupAdminEndpoint.class.getName());
+    ADMIN.balancerSwitch(false, true);
+    MasterCoprocessorHost host = MASTER.getMasterCoprocessorHost();
+    OBSERVER = (CPMasterObserver) host.findCoprocessor(CPMasterObserver.class.getName());
+    RS_GROUP_ADMIN_CLIENT = new RSGroupAdminClient(TEST_UTIL.getConnection());
   }
 
   public static void tearDownAfterClass() throws Exception {
@@ -181,14 +129,13 @@ public abstract class TestRSGroupsBase {
   }
 
   public void setUpBeforeMethod() throws Exception {
-    setAdmin();
     LOG.info(name.getMethodName());
-    tableName = TableName.valueOf(tablePrefix + "_" + name.getMethodName().split("\\[")[0]);
+    tableName = TableName.valueOf(TABLE_PREFIX + "_" + name.getMethodName().split("\\[")[0]);
     if (!INIT) {
       INIT = true;
       tearDownAfterMethod();
     }
-    observer.resetFlags();
+    OBSERVER.resetFlags();
   }
 
   public void tearDownAfterMethod() throws Exception {
@@ -196,45 +143,40 @@ public abstract class TestRSGroupsBase {
     deleteNamespaceIfNecessary();
     deleteGroups();
 
-    for(ServerName sn : admin.listDecommissionedRegionServers()){
-      admin.recommissionRegionServer(sn, null);
+    for (ServerName sn : ADMIN.listDecommissionedRegionServers()) {
+      ADMIN.recommissionRegionServer(sn, null);
     }
-    assertTrue(admin.listDecommissionedRegionServers().isEmpty());
+    assertTrue(ADMIN.listDecommissionedRegionServers().isEmpty());
 
     int missing = NUM_SLAVES_BASE - getNumServers();
-    LOG.info("Restoring servers: "+missing);
-    for(int i=0; i<missing; i++) {
-      ((MiniHBaseCluster)cluster).startRegionServer();
+    LOG.info("Restoring servers: " + missing);
+    for (int i = 0; i < missing; i++) {
+      ((MiniHBaseCluster) CLUSTER).startRegionServer();
     }
-
-    rsGroupAdmin.addRSGroup("master");
-    ServerName masterServerName =
-        ((MiniHBaseCluster)cluster).getMaster().getServerName();
-
+    ADMIN.addRSGroup("master");
+    ServerName masterServerName = ((MiniHBaseCluster) CLUSTER).getMaster().getServerName();
     try {
-      rsGroupAdmin.moveToRSGroup(Sets.newHashSet(masterServerName.getAddress()),
-          "master");
+      ADMIN.moveServersToRSGroup(Sets.newHashSet(masterServerName.getAddress()), "master");
     } catch (Exception ex) {
       LOG.warn("Got this on setup, FYI", ex);
     }
-    assertTrue(observer.preMoveServersCalled);
+    assertTrue(OBSERVER.preMoveServersCalled);
     TEST_UTIL.waitFor(WAIT_TIMEOUT, new Waiter.Predicate<Exception>() {
       @Override
       public boolean evaluate() throws Exception {
-        LOG.info("Waiting for cleanup to finish " + rsGroupAdmin.listRSGroups());
-        //Might be greater since moving servers back to default
-        //is after starting a server
+        LOG.info("Waiting for cleanup to finish " + ADMIN.listRSGroups());
+        // Might be greater since moving servers back to default
+        // is after starting a server
 
-        return rsGroupAdmin.getRSGroup(RSGroupInfo.DEFAULT_GROUP).getServers().size()
-            == NUM_SLAVES_BASE;
+        return ADMIN.getRSGroup(RSGroupInfo.DEFAULT_GROUP).getServers().size() == NUM_SLAVES_BASE;
       }
     });
   }
 
-  protected RSGroupInfo addGroup(String groupName, int serverCount)
-      throws IOException, InterruptedException {
-    RSGroupInfo defaultInfo = rsGroupAdmin.getRSGroup(RSGroupInfo.DEFAULT_GROUP);
-    rsGroupAdmin.addRSGroup(groupName);
+  protected final RSGroupInfo addGroup(String groupName, int serverCount)
+    throws IOException, InterruptedException {
+    RSGroupInfo defaultInfo = ADMIN.getRSGroup(RSGroupInfo.DEFAULT_GROUP);
+    ADMIN.addRSGroup(groupName);
     Set<Address> set = new HashSet<>();
     for (Address server : defaultInfo.getServers()) {
       if (set.size() == serverCount) {
@@ -242,53 +184,56 @@ public abstract class TestRSGroupsBase {
       }
       set.add(server);
     }
-    rsGroupAdmin.moveToRSGroup(set, groupName);
-    RSGroupInfo result = rsGroupAdmin.getRSGroup(groupName);
+    ADMIN.moveServersToRSGroup(set, groupName);
+    RSGroupInfo result = ADMIN.getRSGroup(groupName);
     return result;
   }
 
-  public void removeGroup(String groupName) throws IOException {
-    RSGroupInfo groupInfo = rsGroupAdmin.getRSGroup(groupName);
-    rsGroupAdmin.setRSGroup(groupInfo.getTables(), RSGroupInfo.DEFAULT_GROUP);
-    rsGroupAdmin.moveToRSGroup(groupInfo.getServers(), RSGroupInfo.DEFAULT_GROUP);
-    rsGroupAdmin.removeRSGroup(groupName);
+  protected final void removeGroup(String groupName) throws IOException {
+    Set<TableName> tables = new HashSet<>();
+    for (TableDescriptor td : ADMIN.listTableDescriptors(true)) {
+      RSGroupInfo groupInfo = ADMIN.getRSGroup(td.getTableName());
+      if (groupInfo != null && groupInfo.getName().equals(groupName)) {
+        tables.add(td.getTableName());
+      }
+    }
+    ADMIN.setRSGroup(tables, RSGroupInfo.DEFAULT_GROUP);
+    RSGroupInfo groupInfo = ADMIN.getRSGroup(groupName);
+    ADMIN.moveServersToRSGroup(groupInfo.getServers(), RSGroupInfo.DEFAULT_GROUP);
+    ADMIN.removeRSGroup(groupName);
   }
 
-  protected void deleteTableIfNecessary() throws IOException {
+  protected final void deleteTableIfNecessary() throws IOException {
     for (TableDescriptor desc : TEST_UTIL.getAdmin()
-      .listTableDescriptors(Pattern.compile(tablePrefix + ".*"))) {
+      .listTableDescriptors(Pattern.compile(TABLE_PREFIX + ".*"))) {
       TEST_UTIL.deleteTable(desc.getTableName());
     }
   }
 
-  protected void deleteNamespaceIfNecessary() throws IOException {
+  protected final void deleteNamespaceIfNecessary() throws IOException {
     for (NamespaceDescriptor desc : TEST_UTIL.getAdmin().listNamespaceDescriptors()) {
-      if(desc.getName().startsWith(tablePrefix)) {
-        admin.deleteNamespace(desc.getName());
+      if (desc.getName().startsWith(TABLE_PREFIX)) {
+        ADMIN.deleteNamespace(desc.getName());
       }
     }
   }
 
-  protected void deleteGroups() throws IOException {
-    RSGroupAdminClient groupAdmin = new RSGroupAdminClient(TEST_UTIL.getConnection());
-    for(RSGroupInfo group: groupAdmin.listRSGroups()) {
-      if(!group.getName().equals(RSGroupInfo.DEFAULT_GROUP)) {
-        groupAdmin.setRSGroup(group.getTables(), RSGroupInfo.DEFAULT_GROUP);
-        groupAdmin.moveServers(group.getServers(), RSGroupInfo.DEFAULT_GROUP);
-        groupAdmin.removeRSGroup(group.getName());
+  protected final void deleteGroups() throws IOException {
+    for (RSGroupInfo groupInfo : ADMIN.listRSGroups()) {
+      if (!groupInfo.getName().equals(RSGroupInfo.DEFAULT_GROUP)) {
+        removeGroup(groupInfo.getName());
       }
     }
   }
 
   protected Map<TableName, List<String>> getTableRegionMap() throws IOException {
     Map<TableName, List<String>> map = Maps.newTreeMap();
-    Map<TableName, Map<ServerName, List<String>>> tableServerRegionMap
-        = getTableServerRegionMap();
-    for(TableName tableName : tableServerRegionMap.keySet()) {
-      if(!map.containsKey(tableName)) {
+    Map<TableName, Map<ServerName, List<String>>> tableServerRegionMap = getTableServerRegionMap();
+    for (TableName tableName : tableServerRegionMap.keySet()) {
+      if (!map.containsKey(tableName)) {
         map.put(tableName, new LinkedList<>());
       }
-      for(List<String> subset: tableServerRegionMap.get(tableName).values()) {
+      for (List<String> subset : tableServerRegionMap.get(tableName).values()) {
         map.get(tableName).addAll(subset);
       }
     }
@@ -313,8 +258,7 @@ public abstract class TestRSGroupsBase {
 
   // return the real number of region servers, excluding the master embedded region server in 2.0+
   protected int getNumServers() throws IOException {
-    ClusterMetrics status =
-        admin.getClusterMetrics(EnumSet.of(Option.MASTER, Option.LIVE_SERVERS));
+    ClusterMetrics status = ADMIN.getClusterMetrics(EnumSet.of(Option.MASTER, Option.LIVE_SERVERS));
     ServerName masterName = status.getMasterName();
     int count = 0;
     for (ServerName sn : status.getLiveServerMetrics().keySet()) {
@@ -325,30 +269,29 @@ public abstract class TestRSGroupsBase {
     return count;
   }
 
-  public String getGroupName(String baseName) {
-    return groupPrefix + "_" + getNameWithoutIndex(baseName) + "_" +
-        rand.nextInt(Integer.MAX_VALUE);
+  protected final String getGroupName(String baseName) {
+    return GROUP_PREFIX + "_" + getNameWithoutIndex(baseName) + "_" +
+      ThreadLocalRandom.current().nextInt(Integer.MAX_VALUE);
   }
 
   /**
    * The server name in group does not contain the start code, this method will find out the start
    * code and construct the ServerName object.
    */
-  protected ServerName getServerName(Address addr) {
+  protected final ServerName getServerName(Address addr) {
     return TEST_UTIL.getMiniHBaseCluster().getRegionServerThreads().stream()
       .map(t -> t.getRegionServer().getServerName()).filter(sn -> sn.getAddress().equals(addr))
       .findFirst().get();
   }
 
-  protected void toggleQuotaCheckAndRestartMiniCluster(boolean enable) throws Exception {
+  protected final void toggleQuotaCheckAndRestartMiniCluster(boolean enable) throws Exception {
     TEST_UTIL.shutdownMiniCluster();
     TEST_UTIL.getConfiguration().setBoolean(QuotaUtil.QUOTA_CONF_KEY, enable);
     TEST_UTIL.startMiniCluster(NUM_SLAVES_BASE - 1);
     TEST_UTIL.getConfiguration().setInt(ServerManager.WAIT_ON_REGIONSERVERS_MINTOSTART,
-        NUM_SLAVES_BASE - 1);
+      NUM_SLAVES_BASE - 1);
     TEST_UTIL.getConfiguration().setBoolean(SnapshotManager.HBASE_SNAPSHOT_ENABLED, true);
     initialize();
-    rsGroupAdmin = (Admin) resetAdminConnection(rsGroupAdmin);
   }
 
   public static class CPMasterObserver implements MasterCoprocessor, MasterObserver {
@@ -411,135 +354,133 @@ public abstract class TestRSGroupsBase {
 
     @Override
     public void preMoveServersAndTables(final ObserverContext<MasterCoprocessorEnvironment> ctx,
-        Set<Address> servers, Set<TableName> tables, String targetGroup) throws IOException {
+      Set<Address> servers, Set<TableName> tables, String targetGroup) throws IOException {
       preMoveServersAndTables = true;
     }
 
     @Override
     public void postMoveServersAndTables(final ObserverContext<MasterCoprocessorEnvironment> ctx,
-        Set<Address> servers, Set<TableName> tables, String targetGroup) throws IOException {
+      Set<Address> servers, Set<TableName> tables, String targetGroup) throws IOException {
       postMoveServersAndTables = true;
     }
 
     @Override
-    public void preRemoveServers(
-        final ObserverContext<MasterCoprocessorEnvironment> ctx,
-        Set<Address> servers) throws IOException {
+    public void preRemoveServers(final ObserverContext<MasterCoprocessorEnvironment> ctx,
+      Set<Address> servers) throws IOException {
       preRemoveServersCalled = true;
     }
 
     @Override
-    public void postRemoveServers(
-        final ObserverContext<MasterCoprocessorEnvironment> ctx,
-        Set<Address> servers) throws IOException {
+    public void postRemoveServers(final ObserverContext<MasterCoprocessorEnvironment> ctx,
+      Set<Address> servers) throws IOException {
       postRemoveServersCalled = true;
     }
 
     @Override
     public void preRemoveRSGroup(final ObserverContext<MasterCoprocessorEnvironment> ctx,
-        String name) throws IOException {
+      String name) throws IOException {
       preRemoveRSGroupCalled = true;
     }
 
     @Override
     public void postRemoveRSGroup(final ObserverContext<MasterCoprocessorEnvironment> ctx,
-        String name) throws IOException {
+      String name) throws IOException {
       postRemoveRSGroupCalled = true;
     }
 
     @Override
-    public void preAddRSGroup(final ObserverContext<MasterCoprocessorEnvironment> ctx,
-        String name) throws IOException {
+    public void preAddRSGroup(final ObserverContext<MasterCoprocessorEnvironment> ctx, String name)
+      throws IOException {
       preAddRSGroupCalled = true;
     }
 
     @Override
-    public void postAddRSGroup(final ObserverContext<MasterCoprocessorEnvironment> ctx,
-        String name) throws IOException {
+    public void postAddRSGroup(final ObserverContext<MasterCoprocessorEnvironment> ctx, String name)
+      throws IOException {
       postAddRSGroupCalled = true;
     }
 
     @Override
     public void preMoveTables(final ObserverContext<MasterCoprocessorEnvironment> ctx,
-        Set<TableName> tables, String targetGroup) throws IOException {
+      Set<TableName> tables, String targetGroup) throws IOException {
       preMoveTablesCalled = true;
     }
 
     @Override
     public void postMoveTables(final ObserverContext<MasterCoprocessorEnvironment> ctx,
-        Set<TableName> tables, String targetGroup) throws IOException {
+      Set<TableName> tables, String targetGroup) throws IOException {
       postMoveTablesCalled = true;
     }
 
     @Override
     public void preMoveServers(final ObserverContext<MasterCoprocessorEnvironment> ctx,
-        Set<Address> servers, String targetGroup) throws IOException {
+      Set<Address> servers, String targetGroup) throws IOException {
       preMoveServersCalled = true;
     }
 
     @Override
     public void postMoveServers(final ObserverContext<MasterCoprocessorEnvironment> ctx,
-        Set<Address> servers, String targetGroup) throws IOException {
+      Set<Address> servers, String targetGroup) throws IOException {
       postMoveServersCalled = true;
     }
 
     @Override
     public void preBalanceRSGroup(final ObserverContext<MasterCoprocessorEnvironment> ctx,
-        String groupName) throws IOException {
+      String groupName) throws IOException {
       preBalanceRSGroupCalled = true;
     }
 
     @Override
     public void postBalanceRSGroup(final ObserverContext<MasterCoprocessorEnvironment> ctx,
-        String groupName, boolean balancerRan) throws IOException {
+      String groupName, boolean balancerRan) throws IOException {
       postBalanceRSGroupCalled = true;
     }
 
     @Override
     public void preGetRSGroupInfo(final ObserverContext<MasterCoprocessorEnvironment> ctx,
-        final String groupName) throws IOException {
+      final String groupName) throws IOException {
       preGetRSGroupInfoCalled = true;
     }
 
     @Override
     public void postGetRSGroupInfo(final ObserverContext<MasterCoprocessorEnvironment> ctx,
-        final String groupName) throws IOException {
+      final String groupName) throws IOException {
       postGetRSGroupInfoCalled = true;
     }
 
     @Override
     public void preGetRSGroupInfoOfTable(final ObserverContext<MasterCoprocessorEnvironment> ctx,
-        final TableName tableName) throws IOException {
+      final TableName tableName) throws IOException {
       preGetRSGroupInfoOfTableCalled = true;
     }
 
     @Override
     public void postGetRSGroupInfoOfTable(final ObserverContext<MasterCoprocessorEnvironment> ctx,
-        final TableName tableName) throws IOException {
+      final TableName tableName) throws IOException {
       postGetRSGroupInfoOfTableCalled = true;
     }
 
     @Override
     public void preListRSGroups(final ObserverContext<MasterCoprocessorEnvironment> ctx)
-        throws IOException {
+      throws IOException {
       preListRSGroupsCalled = true;
     }
 
     @Override
     public void postListRSGroups(final ObserverContext<MasterCoprocessorEnvironment> ctx)
-        throws IOException {
+      throws IOException {
       postListRSGroupsCalled = true;
     }
 
     @Override
     public void preGetRSGroupInfoOfServer(final ObserverContext<MasterCoprocessorEnvironment> ctx,
-        final Address server) throws IOException {
+      final Address server) throws IOException {
       preGetRSGroupInfoOfServerCalled = true;
     }
 
     @Override
     public void postGetRSGroupInfoOfServer(final ObserverContext<MasterCoprocessorEnvironment> ctx,
-        final Address server) throws IOException {
+      final Address server) throws IOException {
       postGetRSGroupInfoOfServerCalled = true;
     }
   }
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsBasics.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsBasics.java
index bd1db52..88503ff 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsBasics.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsBasics.java
@@ -35,6 +35,7 @@ import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
 import org.apache.hadoop.hbase.net.Address;
 import org.apache.hadoop.hbase.quotas.QuotaTableUtil;
 import org.apache.hadoop.hbase.testclassification.MediumTests;
+import org.apache.hadoop.hbase.testclassification.RSGroupTests;
 import org.apache.hadoop.hbase.util.Bytes;
 import org.junit.After;
 import org.junit.AfterClass;
@@ -44,15 +45,12 @@ import org.junit.BeforeClass;
 import org.junit.ClassRule;
 import org.junit.Test;
 import org.junit.experimental.categories.Category;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import org.apache.hbase.thirdparty.com.google.common.collect.Lists;
 
-@RunWith(Parameterized.class)
-@Category({ MediumTests.class })
+@Category({ RSGroupTests.class, MediumTests.class })
 public class TestRSGroupsBasics extends TestRSGroupsBase {
 
   @ClassRule
@@ -83,12 +81,12 @@ public class TestRSGroupsBasics extends TestRSGroupsBase {
 
   @Test
   public void testBasicStartUp() throws IOException {
-    RSGroupInfo defaultInfo = rsGroupAdmin.getRSGroup(RSGroupInfo.DEFAULT_GROUP);
+    RSGroupInfo defaultInfo = ADMIN.getRSGroup(RSGroupInfo.DEFAULT_GROUP);
     assertEquals(NUM_SLAVES_BASE, defaultInfo.getServers().size());
     // Assignment of meta and rsgroup regions.
-    int count = master.getAssignmentManager().getRegionStates().getRegionAssignments().size();
+    int count = MASTER.getAssignmentManager().getRegionStates().getRegionAssignments().size();
     LOG.info("regions assignments are" +
-        master.getAssignmentManager().getRegionStates().getRegionAssignments().toString());
+      MASTER.getAssignmentManager().getRegionStates().getRegionAssignments().toString());
     // 2 (meta and rsgroup)
     assertEquals(2, count);
   }
@@ -117,14 +115,14 @@ public class TestRSGroupsBasics extends TestRSGroupsBase {
   @Test
   public void testNamespaceCreateAndAssign() throws Exception {
     LOG.info("testNamespaceCreateAndAssign");
-    String nsName = tablePrefix + "_foo";
-    final TableName tableName = TableName.valueOf(nsName, tablePrefix + "_testCreateAndAssign");
+    String nsName = TABLE_PREFIX + "_foo";
+    final TableName tableName = TableName.valueOf(nsName, TABLE_PREFIX + "_testCreateAndAssign");
     RSGroupInfo appInfo = addGroup("appInfo", 1);
-    admin.createNamespace(NamespaceDescriptor.create(nsName)
+    ADMIN.createNamespace(NamespaceDescriptor.create(nsName)
       .addConfiguration(RSGroupInfo.NAMESPACE_DESC_PROP_GROUP, "appInfo").build());
     final TableDescriptor desc = TableDescriptorBuilder.newBuilder(tableName)
       .setColumnFamily(ColumnFamilyDescriptorBuilder.of("f")).build();
-    admin.createTable(desc);
+    ADMIN.createTable(desc);
     // wait for created table to be assigned
     TEST_UTIL.waitFor(WAIT_TIMEOUT, new Waiter.Predicate<Exception>() {
       @Override
@@ -134,18 +132,18 @@ public class TestRSGroupsBasics extends TestRSGroupsBase {
     });
     ServerName targetServer = getServerName(appInfo.getServers().iterator().next());
     // verify it was assigned to the right group
-    Assert.assertEquals(1, admin.getRegions(targetServer).size());
+    Assert.assertEquals(1, ADMIN.getRegions(targetServer).size());
   }
 
   @Test
   public void testDefaultNamespaceCreateAndAssign() throws Exception {
     LOG.info("testDefaultNamespaceCreateAndAssign");
-    String tableName = tablePrefix + "_testCreateAndAssign";
-    admin.modifyNamespace(NamespaceDescriptor.create("default")
+    String tableName = TABLE_PREFIX + "_testCreateAndAssign";
+    ADMIN.modifyNamespace(NamespaceDescriptor.create("default")
       .addConfiguration(RSGroupInfo.NAMESPACE_DESC_PROP_GROUP, "default").build());
     final TableDescriptor desc = TableDescriptorBuilder.newBuilder(TableName.valueOf(tableName))
       .setColumnFamily(ColumnFamilyDescriptorBuilder.of("f")).build();
-    admin.createTable(desc);
+    ADMIN.createTable(desc);
     // wait for created table to be assigned
     TEST_UTIL.waitFor(WAIT_TIMEOUT, new Waiter.Predicate<Exception>() {
       @Override
@@ -165,11 +163,11 @@ public class TestRSGroupsBasics extends TestRSGroupsBase {
     TEST_UTIL.createTable(tableName, FAMILY);
 
     // create snapshot
-    admin.snapshot(snapshotName, tableName);
+    ADMIN.snapshot(snapshotName, tableName);
 
     // clone
-    admin.cloneSnapshot(snapshotName, clonedTableName);
-    admin.deleteSnapshot(snapshotName);
+    ADMIN.cloneSnapshot(snapshotName, clonedTableName);
+    ADMIN.deleteSnapshot(snapshotName);
   }
 
   @Test
@@ -179,17 +177,17 @@ public class TestRSGroupsBasics extends TestRSGroupsBase {
     // move region servers from default group to new group
     final int serverCountToMoveToNewGroup = 3;
     final RSGroupInfo newGroup =
-        addGroup(getGroupName(name.getMethodName()), serverCountToMoveToNewGroup);
+      addGroup(getGroupName(name.getMethodName()), serverCountToMoveToNewGroup);
 
     // get the existing dead servers
-    NUM_DEAD_SERVERS = cluster.getClusterMetrics().getDeadServerNames().size();
+    NUM_DEAD_SERVERS = CLUSTER.getClusterMetrics().getDeadServerNames().size();
 
     // stop 1 region server in new group
     ServerName serverToStop = getServerName(newGroup.getServers().iterator().next());
     try {
       // stopping may cause an exception
       // due to the connection loss
-      admin.stopRegionServer(serverToStop.getAddress().toString());
+      ADMIN.stopRegionServer(serverToStop.getAddress().toString());
       NUM_DEAD_SERVERS++;
     } catch (Exception e) {
     }
@@ -198,20 +196,20 @@ public class TestRSGroupsBasics extends TestRSGroupsBase {
     TEST_UTIL.waitFor(WAIT_TIMEOUT, new Waiter.Predicate<Exception>() {
       @Override
       public boolean evaluate() throws Exception {
-        return cluster.getClusterMetrics().getDeadServerNames().size() == NUM_DEAD_SERVERS &&
-          !master.getServerManager().areDeadServersInProgress();
+        return CLUSTER.getClusterMetrics().getDeadServerNames().size() == NUM_DEAD_SERVERS &&
+          !MASTER.getServerManager().areDeadServersInProgress();
       }
     });
-    assertFalse(cluster.getClusterMetrics().getLiveServerMetrics().containsKey(serverToStop));
-    assertTrue(cluster.getClusterMetrics().getDeadServerNames().contains(serverToStop));
+    assertFalse(CLUSTER.getClusterMetrics().getLiveServerMetrics().containsKey(serverToStop));
+    assertTrue(CLUSTER.getClusterMetrics().getDeadServerNames().contains(serverToStop));
     assertTrue(newGroup.getServers().contains(serverToStop.getAddress()));
 
     // clear dead servers list
-    List<ServerName> notClearedServers = admin.clearDeadServers(Lists.newArrayList(serverToStop));
+    List<ServerName> notClearedServers = ADMIN.clearDeadServers(Lists.newArrayList(serverToStop));
     assertEquals(0, notClearedServers.size());
 
     // the stopped region server gets cleared and removed from the group
-    Set<Address> newGroupServers = rsGroupAdmin.getRSGroup(newGroup.getName()).getServers();
+    Set<Address> newGroupServers = ADMIN.getRSGroup(newGroup.getName()).getServers();
     assertFalse(newGroupServers.contains(serverToStop.getAddress()));
     assertEquals(serverCountToMoveToNewGroup - 1 /* 1 stopped */, newGroupServers.size());
   }
@@ -221,31 +219,34 @@ public class TestRSGroupsBasics extends TestRSGroupsBase {
     LOG.info("testClearNotProcessedDeadServer");
 
     // get the existing dead servers
-    NUM_DEAD_SERVERS = cluster.getClusterMetrics().getDeadServerNames().size();
+    NUM_DEAD_SERVERS = CLUSTER.getClusterMetrics().getDeadServerNames().size();
 
     // move region servers from default group to "dead server" group
     final int serverCountToMoveToDeadServerGroup = 1;
-    RSGroupInfo deadServerGroup =
-        addGroup("deadServerGroup", serverCountToMoveToDeadServerGroup);
+    RSGroupInfo deadServerGroup = addGroup("deadServerGroup", serverCountToMoveToDeadServerGroup);
 
     // stop 1 region servers in "dead server" group
     ServerName serverToStop = getServerName(deadServerGroup.getServers().iterator().next());
     try {
       // stopping may cause an exception
       // due to the connection loss
-      admin.stopRegionServer(serverToStop.getAddress().toString());
+      ADMIN.stopRegionServer(serverToStop.getAddress().toString());
       NUM_DEAD_SERVERS++;
     } catch (Exception e) {
     }
     TEST_UTIL.waitFor(WAIT_TIMEOUT, new Waiter.Predicate<Exception>() {
       @Override
       public boolean evaluate() throws Exception {
-        return cluster.getClusterMetrics().getDeadServerNames().size() == NUM_DEAD_SERVERS;
+        return CLUSTER.getClusterMetrics().getDeadServerNames().size() == NUM_DEAD_SERVERS;
       }
     });
 
+    // the one and only region server in the group does not get cleared, even though it is stopped
+    List<ServerName> notClearedServers = ADMIN.clearDeadServers(Lists.newArrayList(serverToStop));
+    assertEquals(serverCountToMoveToDeadServerGroup, notClearedServers.size());
+
     Set<Address> ServersInDeadServerGroup =
-        rsGroupAdmin.getRSGroup(deadServerGroup.getName()).getServers();
+      ADMIN.getRSGroup(deadServerGroup.getName()).getServers();
     assertEquals(serverCountToMoveToDeadServerGroup, ServersInDeadServerGroup.size());
     assertTrue(ServersInDeadServerGroup.contains(serverToStop.getAddress()));
   }
@@ -256,7 +257,7 @@ public class TestRSGroupsBasics extends TestRSGroupsBase {
     TEST_UTIL.waitFor(90000, new Waiter.Predicate<Exception>() {
       @Override
       public boolean evaluate() throws Exception {
-        return admin.isTableAvailable(QuotaTableUtil.QUOTA_TABLE_NAME);
+        return ADMIN.isTableAvailable(QuotaTableUtil.QUOTA_TABLE_NAME);
       }
     });
     toggleQuotaCheckAndRestartMiniCluster(false);
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsCPHookCalled.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsCPHookCalled.java
new file mode 100644
index 0000000..4322784
--- /dev/null
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsCPHookCalled.java
@@ -0,0 +1,91 @@
+/**
+ * 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.assertTrue;
+
+import org.apache.hadoop.hbase.HBaseClassTestRule;
+import org.apache.hadoop.hbase.MiniHBaseCluster;
+import org.apache.hadoop.hbase.ServerName;
+import org.apache.hadoop.hbase.TableName;
+import org.apache.hadoop.hbase.testclassification.MediumTests;
+import org.apache.hadoop.hbase.testclassification.RSGroupTests;
+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;
+
+@Category({ RSGroupTests.class, MediumTests.class })
+public class TestRSGroupsCPHookCalled extends TestRSGroupsBase {
+
+  @ClassRule
+  public static final HBaseClassTestRule CLASS_RULE =
+    HBaseClassTestRule.forClass(TestRSGroupsCPHookCalled.class);
+
+  @BeforeClass
+  public static void setUp() throws Exception {
+    setUpTestBeforeClass();
+  }
+
+  @AfterClass
+  public static void tearDown() throws Exception {
+    tearDownAfterClass();
+  }
+
+  @Before
+  public void beforeMethod() throws Exception {
+    setUpBeforeMethod();
+  }
+
+  @After
+  public void afterMethod() throws Exception {
+    tearDownAfterMethod();
+  }
+
+  @Test
+  public void testGetRSGroupInfoCPHookCalled() throws Exception {
+    ADMIN.getRSGroup(RSGroupInfo.DEFAULT_GROUP);
+    assertTrue(OBSERVER.preGetRSGroupInfoCalled);
+    assertTrue(OBSERVER.postGetRSGroupInfoCalled);
+  }
+
+  @Test
+  public void testGetRSGroupInfoOfTableCPHookCalled() throws Exception {
+    ADMIN.getRSGroup(TableName.META_TABLE_NAME);
+    assertTrue(OBSERVER.preGetRSGroupInfoOfTableCalled);
+    assertTrue(OBSERVER.postGetRSGroupInfoOfTableCalled);
+  }
+
+  @Test
+  public void testListRSGroupsCPHookCalled() throws Exception {
+    ADMIN.listRSGroups();
+    assertTrue(OBSERVER.preListRSGroupsCalled);
+    assertTrue(OBSERVER.postListRSGroupsCalled);
+  }
+
+  @Test
+  public void testGetRSGroupInfoOfServerCPHookCalled() throws Exception {
+    ServerName masterServerName = ((MiniHBaseCluster) CLUSTER).getMaster().getServerName();
+    ADMIN.getRSGroup(masterServerName.getAddress());
+    assertTrue(OBSERVER.preGetRSGroupInfoOfServerCalled);
+    assertTrue(OBSERVER.postGetRSGroupInfoOfServerCalled);
+  }
+}
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsKillRS.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsKillRS.java
index fb9a2a9..bb4f441 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsKillRS.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsKillRS.java
@@ -42,6 +42,7 @@ import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
 import org.apache.hadoop.hbase.master.procedure.ServerCrashProcedure;
 import org.apache.hadoop.hbase.net.Address;
 import org.apache.hadoop.hbase.testclassification.MediumTests;
+import org.apache.hadoop.hbase.testclassification.RSGroupTests;
 import org.apache.hadoop.hbase.util.Bytes;
 import org.apache.hadoop.hbase.util.JVMClusterUtil;
 import org.apache.hadoop.hbase.util.VersionInfo;
@@ -50,24 +51,22 @@ import org.junit.AfterClass;
 import org.junit.Before;
 import org.junit.BeforeClass;
 import org.junit.ClassRule;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.experimental.categories.Category;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import org.apache.hbase.thirdparty.com.google.common.collect.Sets;
 
-@RunWith(Parameterized.class)
-@Category({ MediumTests.class })
+@Category({ RSGroupTests.class, MediumTests.class })
 public class TestRSGroupsKillRS extends TestRSGroupsBase {
 
   @ClassRule
   public static final HBaseClassTestRule CLASS_RULE =
     HBaseClassTestRule.forClass(TestRSGroupsKillRS.class);
 
-  protected static final Logger LOG = LoggerFactory.getLogger(TestRSGroupsKillRS.class);
+  private static final Logger LOG = LoggerFactory.getLogger(TestRSGroupsKillRS.class);
 
   @BeforeClass
   public static void setUp() throws Exception {
@@ -92,13 +91,13 @@ public class TestRSGroupsKillRS extends TestRSGroupsBase {
   @Test
   public void testKillRS() throws Exception {
     RSGroupInfo appInfo = addGroup("appInfo", 1);
-    final TableName tableName = TableName.valueOf(tablePrefix + "_ns",
-        getNameWithoutIndex(name.getMethodName()));
-    admin.createNamespace(NamespaceDescriptor.create(tableName.getNamespaceAsString())
+    final TableName tableName =
+      TableName.valueOf(TABLE_PREFIX + "_ns", getNameWithoutIndex(name.getMethodName()));
+    ADMIN.createNamespace(NamespaceDescriptor.create(tableName.getNamespaceAsString())
       .addConfiguration(RSGroupInfo.NAMESPACE_DESC_PROP_GROUP, appInfo.getName()).build());
     final TableDescriptor desc = TableDescriptorBuilder.newBuilder(tableName)
       .setColumnFamily(ColumnFamilyDescriptorBuilder.of("f")).build();
-    admin.createTable(desc);
+    ADMIN.createTable(desc);
     // wait for created table to be assigned
     TEST_UTIL.waitFor(WAIT_TIMEOUT, new Waiter.Predicate<Exception>() {
       @Override
@@ -108,19 +107,19 @@ public class TestRSGroupsKillRS extends TestRSGroupsBase {
     });
 
     ServerName targetServer = getServerName(appInfo.getServers().iterator().next());
-    assertEquals(1, admin.getRegions(targetServer).size());
+    assertEquals(1, ADMIN.getRegions(targetServer).size());
 
     try {
       // stopping may cause an exception
       // due to the connection loss
-      admin.stopRegionServer(targetServer.getAddress().toString());
+      ADMIN.stopRegionServer(targetServer.getAddress().toString());
     } catch (Exception e) {
     }
     // wait until the server is actually down
     TEST_UTIL.waitFor(WAIT_TIMEOUT, new Waiter.Predicate<Exception>() {
       @Override
       public boolean evaluate() throws Exception {
-        return !cluster.getClusterMetrics().getLiveServerMetrics().containsKey(targetServer);
+        return !CLUSTER.getClusterMetrics().getLiveServerMetrics().containsKey(targetServer);
       }
     });
     // there is only one rs in the group and we killed it, so the region can not be online, until
@@ -128,30 +127,29 @@ public class TestRSGroupsKillRS extends TestRSGroupsBase {
     TEST_UTIL.waitFor(WAIT_TIMEOUT, new Waiter.Predicate<Exception>() {
       @Override
       public boolean evaluate() throws Exception {
-        return !cluster.getClusterMetrics().getRegionStatesInTransition().isEmpty();
+        return !CLUSTER.getClusterMetrics().getRegionStatesInTransition().isEmpty();
       }
     });
     Set<Address> newServers = Sets.newHashSet();
-    newServers
-      .add(rsGroupAdmin.getRSGroup(RSGroupInfo.DEFAULT_GROUP).getServers().iterator().next());
-    rsGroupAdmin.moveToRSGroup(newServers, appInfo.getName());
+    newServers.add(ADMIN.getRSGroup(RSGroupInfo.DEFAULT_GROUP).getServers().iterator().next());
+    ADMIN.moveServersToRSGroup(newServers, appInfo.getName());
 
     // Make sure all the table's regions get reassigned
     // disabling the table guarantees no conflicting assign/unassign (ie SSH) happens
-    admin.disableTable(tableName);
-    admin.enableTable(tableName);
+    ADMIN.disableTable(tableName);
+    ADMIN.enableTable(tableName);
 
     // wait for region to be assigned
     TEST_UTIL.waitFor(WAIT_TIMEOUT, new Waiter.Predicate<Exception>() {
       @Override
       public boolean evaluate() throws Exception {
-        return cluster.getClusterMetrics().getRegionStatesInTransition().isEmpty();
+        return CLUSTER.getClusterMetrics().getRegionStatesInTransition().isEmpty();
       }
     });
 
     ServerName targetServer1 = getServerName(newServers.iterator().next());
-    assertEquals(1, admin.getRegions(targetServer1).size());
-    assertEquals(tableName, admin.getRegions(targetServer1).get(0).getTable());
+    assertEquals(1, ADMIN.getRegions(targetServer1).size());
+    assertEquals(tableName, ADMIN.getRegions(targetServer1).get(0).getTable());
   }
 
   @Test
@@ -166,34 +164,33 @@ public class TestRSGroupsKillRS extends TestRSGroupsBase {
     TEST_UTIL.loadTable(t, Bytes.toBytes("f"));
     Set<TableName> toAddTables = new HashSet<>();
     toAddTables.add(tableName);
-    rsGroupAdmin.setRSGroup(toAddTables, groupName);
-    assertTrue(rsGroupAdmin.getRSGroup(groupName).getTables().contains(tableName));
+    ADMIN.setRSGroup(toAddTables, groupName);
+    assertTrue(RS_GROUP_ADMIN_CLIENT.getRSGroupInfo(groupName).getTables().contains(tableName));
     TEST_UTIL.waitTableAvailable(tableName, 30000);
 
     // check my_group servers and table regions
-    Set<Address> servers = rsGroupAdmin.getRSGroup(groupName).getServers();
+    Set<Address> servers = ADMIN.getRSGroup(groupName).getServers();
     assertEquals(2, servers.size());
     LOG.debug("group servers {}", servers);
-    for (RegionInfo tr :
-        master.getAssignmentManager().getRegionStates().getRegionsOfTable(tableName)) {
-      assertTrue(servers.contains(
-          master.getAssignmentManager().getRegionStates().getRegionAssignments()
-              .get(tr).getAddress()));
+    for (RegionInfo tr : MASTER.getAssignmentManager().getRegionStates()
+      .getRegionsOfTable(tableName)) {
+      assertTrue(servers.contains(MASTER.getAssignmentManager().getRegionStates()
+        .getRegionAssignments().get(tr).getAddress()));
     }
 
     // Move a region, to ensure there exists a region whose 'lastHost' is in my_group
     // ('lastHost' of other regions are in 'default' group)
     // and check if all table regions are online
     List<ServerName> gsn = new ArrayList<>();
-    for(Address addr : servers){
+    for (Address addr : servers) {
       gsn.add(getServerName(addr));
     }
     assertEquals(2, gsn.size());
-    for(Map.Entry<RegionInfo, ServerName> entry :
-        master.getAssignmentManager().getRegionStates().getRegionAssignments().entrySet()){
-      if(entry.getKey().getTable().equals(tableName)){
+    for (Map.Entry<RegionInfo, ServerName> entry : MASTER.getAssignmentManager().getRegionStates()
+      .getRegionAssignments().entrySet()) {
+      if (entry.getKey().getTable().equals(tableName)) {
         LOG.debug("move region {} from {} to {}", entry.getKey().getRegionNameAsString(),
-            entry.getValue(), gsn.get(1 - gsn.indexOf(entry.getValue())));
+          entry.getValue(), gsn.get(1 - gsn.indexOf(entry.getValue())));
         TEST_UTIL.moveRegionAndWait(entry.getKey(), gsn.get(1 - gsn.indexOf(entry.getValue())));
         break;
       }
@@ -202,40 +199,43 @@ public class TestRSGroupsKillRS extends TestRSGroupsBase {
 
     // case 1: stop all the regionservers in my_group, and restart a regionserver in my_group,
     // and then check if all table regions are online
-    for(Address addr : rsGroupAdmin.getRSGroup(groupName).getServers()) {
+    for (Address addr : ADMIN.getRSGroup(groupName).getServers()) {
       TEST_UTIL.getMiniHBaseCluster().stopRegionServer(getServerName(addr));
     }
     // better wait for a while for region reassign
     sleep(10000);
     assertEquals(NUM_SLAVES_BASE - gsn.size(),
-        TEST_UTIL.getMiniHBaseCluster().getLiveRegionServerThreads().size());
+      TEST_UTIL.getMiniHBaseCluster().getLiveRegionServerThreads().size());
     TEST_UTIL.getMiniHBaseCluster().startRegionServer(gsn.get(0).getHostname(),
-        gsn.get(0).getPort());
+      gsn.get(0).getPort());
     assertEquals(NUM_SLAVES_BASE - gsn.size() + 1,
-        TEST_UTIL.getMiniHBaseCluster().getLiveRegionServerThreads().size());
+      TEST_UTIL.getMiniHBaseCluster().getLiveRegionServerThreads().size());
     TEST_UTIL.waitTableAvailable(tableName, 30000);
 
     // case 2: stop all the regionservers in my_group, and move another
     // regionserver(from the 'default' group) to my_group,
     // and then check if all table regions are online
-    for(JVMClusterUtil.RegionServerThread rst :
-        TEST_UTIL.getMiniHBaseCluster().getLiveRegionServerThreads()){
-      if(rst.getRegionServer().getServerName().getAddress().equals(gsn.get(0).getAddress())){
+    for (JVMClusterUtil.RegionServerThread rst : TEST_UTIL.getMiniHBaseCluster()
+      .getLiveRegionServerThreads()) {
+      if (rst.getRegionServer().getServerName().getAddress().equals(gsn.get(0).getAddress())) {
         TEST_UTIL.getMiniHBaseCluster().stopRegionServer(rst.getRegionServer().getServerName());
         break;
       }
     }
     sleep(10000);
     assertEquals(NUM_SLAVES_BASE - gsn.size(),
-        TEST_UTIL.getMiniHBaseCluster().getLiveRegionServerThreads().size());
-    ServerName newServer = master.getServerManager().getOnlineServersList().get(0);
-    rsGroupAdmin.moveToRSGroup(Sets.newHashSet(newServer.getAddress()), groupName);
+      TEST_UTIL.getMiniHBaseCluster().getLiveRegionServerThreads().size());
+    ServerName newServer = MASTER.getServerManager().getOnlineServersList().get(0);
+    ADMIN.moveServersToRSGroup(Sets.newHashSet(newServer.getAddress()), groupName);
     // wait and check if table regions are online
     TEST_UTIL.waitTableAvailable(tableName, 30000);
   }
 
+  // TODO: can not change meta group for now as we can not change the table descriptor of meta
+  // table, this has to be done before we merge back to master.
+  @Ignore
   @Test
-  public void testLowerMetaGroupVersion() throws Exception{
+  public void testLowerMetaGroupVersion() throws Exception {
     // create a rsgroup and move one regionserver to it
     String groupName = "meta_group";
     int groupRSCount = 1;
@@ -244,33 +244,33 @@ public class TestRSGroupsKillRS extends TestRSGroupsBase {
     // move hbase:meta to meta_group
     Set<TableName> toAddTables = new HashSet<>();
     toAddTables.add(TableName.META_TABLE_NAME);
-    rsGroupAdmin.setRSGroup(toAddTables, groupName);
-    assertTrue(rsGroupAdmin.getRSGroup(groupName).getTables().contains(TableName.META_TABLE_NAME));
+    ADMIN.setRSGroup(toAddTables, groupName);
+    assertTrue(RS_GROUP_ADMIN_CLIENT.getRSGroupInfo(groupName).getTables()
+      .contains(TableName.META_TABLE_NAME));
     TEST_UTIL.waitTableAvailable(tableName, 30000);
 
     // restart the regionserver in meta_group, and lower its version
     String originVersion = "";
     Set<Address> servers = new HashSet<>();
-    for(Address addr : rsGroupAdmin.getRSGroup(groupName).getServers()) {
+    for (Address addr : ADMIN.getRSGroup(groupName).getServers()) {
       servers.add(addr);
       TEST_UTIL.getMiniHBaseCluster().stopRegionServer(getServerName(addr));
-      originVersion = master.getRegionServerVersion(getServerName(addr));
+      originVersion = MASTER.getRegionServerVersion(getServerName(addr));
     }
     // better wait for a while for region reassign
     sleep(10000);
     assertEquals(NUM_SLAVES_BASE - groupRSCount,
-        TEST_UTIL.getMiniHBaseCluster().getLiveRegionServerThreads().size());
+      TEST_UTIL.getMiniHBaseCluster().getLiveRegionServerThreads().size());
     Address address = servers.iterator().next();
     int majorVersion = VersionInfo.getMajorVersion(originVersion);
     assertTrue(majorVersion >= 1);
     String lowerVersion = String.valueOf(majorVersion - 1) + originVersion.split("\\.")[1];
     setFinalStatic(Version.class.getField("version"), lowerVersion);
-    TEST_UTIL.getMiniHBaseCluster().startRegionServer(address.getHostname(),
-        address.getPort());
+    TEST_UTIL.getMiniHBaseCluster().startRegionServer(address.getHostname(), address.getPort());
     assertEquals(NUM_SLAVES_BASE,
-        TEST_UTIL.getMiniHBaseCluster().getLiveRegionServerThreads().size());
+      TEST_UTIL.getMiniHBaseCluster().getLiveRegionServerThreads().size());
     assertTrue(VersionInfo.compareVersion(originVersion,
-        master.getRegionServerVersion(getServerName(servers.iterator().next()))) > 0);
+      MASTER.getRegionServerVersion(getServerName(servers.iterator().next()))) > 0);
     LOG.debug("wait for META assigned...");
     // SCP finished, which means all regions assigned too.
     TEST_UTIL.waitFor(60000, () -> !TEST_UTIL.getHBaseCluster().getMaster().getProcedures().stream()
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsOfflineMode.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsOfflineMode.java
index cfc721f..c4a2031 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsOfflineMode.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsOfflineMode.java
@@ -17,26 +17,25 @@
  */
 package org.apache.hadoop.hbase.rsgroup;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 
 import org.apache.hadoop.hbase.HBaseClassTestRule;
 import org.apache.hadoop.hbase.HBaseCluster;
 import org.apache.hadoop.hbase.HBaseTestingUtility;
-import org.apache.hadoop.hbase.HConstants;
 import org.apache.hadoop.hbase.MiniHBaseCluster;
 import org.apache.hadoop.hbase.StartMiniClusterOption;
 import org.apache.hadoop.hbase.TableName;
 import org.apache.hadoop.hbase.Waiter;
 import org.apache.hadoop.hbase.client.Admin;
 import org.apache.hadoop.hbase.client.RegionInfo;
-import org.apache.hadoop.hbase.coprocessor.CoprocessorHost;
 import org.apache.hadoop.hbase.master.HMaster;
 import org.apache.hadoop.hbase.master.ServerManager;
 import org.apache.hadoop.hbase.regionserver.HRegionServer;
 import org.apache.hadoop.hbase.testclassification.MediumTests;
+import org.apache.hadoop.hbase.testclassification.RSGroupTests;
 import org.apache.hadoop.hbase.util.Bytes;
 import org.junit.AfterClass;
-import org.junit.Assert;
 import org.junit.BeforeClass;
 import org.junit.ClassRule;
 import org.junit.Rule;
@@ -48,14 +47,17 @@ import org.slf4j.LoggerFactory;
 
 import org.apache.hbase.thirdparty.com.google.common.collect.Sets;
 
-// This tests that GroupBasedBalancer will use data in zk to do balancing during master startup.
-// This does not test retain assignment.
-// The tests brings up 3 RS, creates a new RS group 'my_group', moves 1 RS to 'my_group', assigns
-// 'hbase:rsgroup' to 'my_group', and kill the only server in that group so that 'hbase:rsgroup'
-// table isn't available. It then kills the active master and waits for backup master to come
-// online. In new master, RSGroupInfoManagerImpl gets the data from zk and waits for the expected
-// assignment with a timeout.
-@Category(MediumTests.class)
+/**
+ * This tests that GroupBasedBalancer will use data in zk to do balancing during master startup.
+ * This does not test retain assignment.
+ * <p/>
+ * The tests brings up 3 RS, creates a new RS group 'my_group', moves 1 RS to 'my_group', assigns
+ * 'hbase:rsgroup' to 'my_group', and kill the only server in that group so that 'hbase:rsgroup'
+ * table isn't available. It then kills the active master and waits for backup master to come
+ * online. In new master, RSGroupInfoManagerImpl gets the data from zk and waits for the expected
+ * assignment with a timeout.
+ */
+@Category({ RSGroupTests.class, MediumTests.class })
 public class TestRSGroupsOfflineMode extends TestRSGroupsBase {
 
   @ClassRule
@@ -75,10 +77,7 @@ public class TestRSGroupsOfflineMode extends TestRSGroupsBase {
   @BeforeClass
   public static void setUp() throws Exception {
     TEST_UTIL = new HBaseTestingUtility();
-    TEST_UTIL.getConfiguration().set(HConstants.HBASE_MASTER_LOADBALANCER_CLASS,
-      RSGroupBasedLoadBalancer.class.getName());
-    TEST_UTIL.getConfiguration().set(CoprocessorHost.MASTER_COPROCESSOR_CONF_KEY,
-      RSGroupAdminEndpoint.class.getName());
+    TEST_UTIL.getConfiguration().setBoolean(RSGroupInfoManager.RS_GROUP_ENABLED, true);
     TEST_UTIL.getConfiguration().set(ServerManager.WAIT_ON_REGIONSERVERS_MINTOSTART, "1");
     StartMiniClusterOption option =
       StartMiniClusterOption.builder().numMasters(2).numRegionServers(3).numDataNodes(3).build();
@@ -112,8 +111,8 @@ public class TestRSGroupsOfflineMode extends TestRSGroupsBase {
     final HRegionServer groupRS = ((MiniHBaseCluster) cluster).getRegionServer(1);
     final HRegionServer failoverRS = ((MiniHBaseCluster) cluster).getRegionServer(2);
     String newGroup = "my_group";
-    RSGroupAdminClient groupAdmin = new RSGroupAdminClient(TEST_UTIL.getConnection());
-    groupAdmin.addRSGroup(newGroup);
+    Admin admin = TEST_UTIL.getAdmin();
+    admin.addRSGroup(newGroup);
     if (master.getAssignmentManager().getRegionStates().getRegionAssignments()
       .containsValue(failoverRS.getServerName())) {
       for (RegionInfo regionInfo : hbaseAdmin.getRegions(failoverRS.getServerName())) {
@@ -130,7 +129,7 @@ public class TestRSGroupsOfflineMode extends TestRSGroupsBase {
     }
 
     // Move server to group and make sure all tables are assigned.
-    groupAdmin.moveServers(Sets.newHashSet(groupRS.getServerName().getAddress()), newGroup);
+    admin.moveServersToRSGroup(Sets.newHashSet(groupRS.getServerName().getAddress()), newGroup);
     TEST_UTIL.waitFor(WAIT_TIMEOUT, new Waiter.Predicate<Exception>() {
       @Override
       public boolean evaluate() throws Exception {
@@ -139,7 +138,7 @@ public class TestRSGroupsOfflineMode extends TestRSGroupsBase {
       }
     });
     // Move table to group and wait.
-    groupAdmin.setRSGroup(Sets.newHashSet(RSGroupInfoManagerImpl.RSGROUP_TABLE_NAME), newGroup);
+    admin.setRSGroup(Sets.newHashSet(RSGroupInfoManagerImpl.RSGROUP_TABLE_NAME), newGroup);
     LOG.info("Waiting for move table...");
     TEST_UTIL.waitFor(WAIT_TIMEOUT, new Waiter.Predicate<Exception>() {
       @Override
@@ -177,7 +176,7 @@ public class TestRSGroupsOfflineMode extends TestRSGroupsBase {
         return failoverRS.getRegions(failoverTable).size() >= 1;
       }
     });
-    Assert.assertEquals(0, failoverRS.getRegions(RSGroupInfoManagerImpl.RSGROUP_TABLE_NAME).size());
+    assertEquals(0, failoverRS.getRegions(RSGroupInfoManagerImpl.RSGROUP_TABLE_NAME).size());
 
     // Need this for minicluster to shutdown cleanly.
     master.stopMaster();
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsWithACL.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsWithACL.java
index 06c4e4e..12ad7f5 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsWithACL.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsWithACL.java
@@ -26,13 +26,11 @@ import java.util.Optional;
 import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.hbase.HBaseClassTestRule;
 import org.apache.hadoop.hbase.HBaseTestingUtility;
-import org.apache.hadoop.hbase.HConstants;
 import org.apache.hadoop.hbase.TableName;
 import org.apache.hadoop.hbase.TableNotFoundException;
 import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder;
 import org.apache.hadoop.hbase.client.Connection;
 import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
-import org.apache.hadoop.hbase.coprocessor.CoprocessorHost;
 import org.apache.hadoop.hbase.ipc.RpcServer;
 import org.apache.hadoop.hbase.master.HMaster;
 import org.apache.hadoop.hbase.security.User;
@@ -96,8 +94,6 @@ public class TestRSGroupsWithACL extends SecureTestUtil {
   private static User USER_GROUP_WRITE;
 
   private static byte[] TEST_FAMILY = Bytes.toBytes("f1");
-
-  private static RSGroupAdminEndpoint rsGroupAdminEndpoint;
   private static HMaster master;
   private static AccessChecker accessChecker;
   private static UserProvider userProvider;
@@ -106,17 +102,14 @@ public class TestRSGroupsWithACL extends SecureTestUtil {
   public static void setupBeforeClass() throws Exception {
     // setup configuration
     conf = TEST_UTIL.getConfiguration();
-    conf.set(HConstants.HBASE_MASTER_LOADBALANCER_CLASS, RSGroupBasedLoadBalancer.class.getName());
     // Enable security
     enableSecurity(conf);
     // Verify enableSecurity sets up what we require
     verifyConfiguration(conf);
     // Enable rsgroup
-    configureRSGroupAdminEndpoint(conf);
+    conf.setBoolean(RSGroupInfoManager.RS_GROUP_ENABLED, true);
 
     TEST_UTIL.startMiniCluster();
-    rsGroupAdminEndpoint = (RSGroupAdminEndpoint) TEST_UTIL.getMiniHBaseCluster().getMaster()
-        .getMasterCoprocessorHost().findCoprocessor(RSGroupAdminEndpoint.class.getName());
     // Wait for the ACL table to become available
     TEST_UTIL.waitUntilAllRegionsAssigned(PermissionStorage.ACL_TABLE_NAME);
 
@@ -216,16 +209,6 @@ public class TestRSGroupsWithACL extends SecureTestUtil {
     TEST_UTIL.shutdownMiniCluster();
   }
 
-  private static void configureRSGroupAdminEndpoint(Configuration conf) {
-    String currentCoprocessors = conf.get(CoprocessorHost.MASTER_COPROCESSOR_CONF_KEY);
-    String coprocessors = RSGroupAdminEndpoint.class.getName();
-    if (currentCoprocessors != null) {
-      coprocessors += "," + currentCoprocessors;
-    }
-    conf.set(CoprocessorHost.MASTER_COPROCESSOR_CONF_KEY, coprocessors);
-    conf.set(HConstants.HBASE_MASTER_LOADBALANCER_CLASS, RSGroupBasedLoadBalancer.class.getName());
-  }
-
   @Test
   public void testGetRSGroupInfo() throws Exception {
     AccessTestAction action = () -> {
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupAdminClient.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/VerifyingRSGroupAdmin.java
similarity index 53%
copy from hbase-server/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupAdminClient.java
copy to hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/VerifyingRSGroupAdmin.java
index 7cf4ed1..6fe2c0a 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupAdminClient.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/VerifyingRSGroupAdmin.java
@@ -17,18 +17,24 @@
  */
 package org.apache.hadoop.hbase.rsgroup;
 
-import com.google.protobuf.ServiceException;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.io.ByteArrayInputStream;
+import java.io.Closeable;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.EnumSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.SortedSet;
 import java.util.concurrent.Future;
 import java.util.regex.Pattern;
 import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.hbase.CacheEvictionStats;
 import org.apache.hadoop.hbase.ClusterMetrics;
+import org.apache.hadoop.hbase.ClusterMetrics.Option;
 import org.apache.hadoop.hbase.NamespaceDescriptor;
 import org.apache.hadoop.hbase.NamespaceNotFoundException;
 import org.apache.hadoop.hbase.RegionMetrics;
@@ -41,29 +47,20 @@ import org.apache.hadoop.hbase.client.ColumnFamilyDescriptor;
 import org.apache.hadoop.hbase.client.CompactType;
 import org.apache.hadoop.hbase.client.CompactionState;
 import org.apache.hadoop.hbase.client.Connection;
+import org.apache.hadoop.hbase.client.ConnectionFactory;
 import org.apache.hadoop.hbase.client.RegionInfo;
+import org.apache.hadoop.hbase.client.Result;
+import org.apache.hadoop.hbase.client.ResultScanner;
+import org.apache.hadoop.hbase.client.Scan;
 import org.apache.hadoop.hbase.client.SnapshotDescription;
+import org.apache.hadoop.hbase.client.Table;
 import org.apache.hadoop.hbase.client.TableDescriptor;
 import org.apache.hadoop.hbase.client.replication.TableCFs;
 import org.apache.hadoop.hbase.client.security.SecurityCapability;
+import org.apache.hadoop.hbase.exceptions.DeserializationException;
 import org.apache.hadoop.hbase.ipc.CoprocessorRpcChannel;
 import org.apache.hadoop.hbase.net.Address;
 import org.apache.hadoop.hbase.protobuf.ProtobufUtil;
-import org.apache.hadoop.hbase.protobuf.generated.HBaseProtos;
-import org.apache.hadoop.hbase.protobuf.generated.RSGroupAdminProtos.AddRSGroupRequest;
-import org.apache.hadoop.hbase.protobuf.generated.RSGroupAdminProtos.BalanceRSGroupRequest;
-import org.apache.hadoop.hbase.protobuf.generated.RSGroupAdminProtos.GetRSGroupInfoOfServerRequest;
-import org.apache.hadoop.hbase.protobuf.generated.RSGroupAdminProtos.GetRSGroupInfoOfServerResponse;
-import org.apache.hadoop.hbase.protobuf.generated.RSGroupAdminProtos.GetRSGroupInfoOfTableRequest;
-import org.apache.hadoop.hbase.protobuf.generated.RSGroupAdminProtos.GetRSGroupInfoOfTableResponse;
-import org.apache.hadoop.hbase.protobuf.generated.RSGroupAdminProtos.GetRSGroupInfoRequest;
-import org.apache.hadoop.hbase.protobuf.generated.RSGroupAdminProtos.GetRSGroupInfoResponse;
-import org.apache.hadoop.hbase.protobuf.generated.RSGroupAdminProtos.ListRSGroupInfosRequest;
-import org.apache.hadoop.hbase.protobuf.generated.RSGroupAdminProtos.MoveServersRequest;
-import org.apache.hadoop.hbase.protobuf.generated.RSGroupAdminProtos.MoveTablesRequest;
-import org.apache.hadoop.hbase.protobuf.generated.RSGroupAdminProtos.RSGroupAdminService;
-import org.apache.hadoop.hbase.protobuf.generated.RSGroupAdminProtos.RemoveRSGroupRequest;
-import org.apache.hadoop.hbase.protobuf.generated.RSGroupAdminProtos.RemoveServersRequest;
 import org.apache.hadoop.hbase.protobuf.generated.RSGroupProtos;
 import org.apache.hadoop.hbase.quotas.QuotaFilter;
 import org.apache.hadoop.hbase.quotas.QuotaSettings;
@@ -79,1009 +76,797 @@ import org.apache.hadoop.hbase.snapshot.HBaseSnapshotException;
 import org.apache.hadoop.hbase.snapshot.RestoreSnapshotException;
 import org.apache.hadoop.hbase.snapshot.SnapshotCreationException;
 import org.apache.hadoop.hbase.snapshot.UnknownSnapshotException;
+import org.apache.hadoop.hbase.zookeeper.ZKUtil;
+import org.apache.hadoop.hbase.zookeeper.ZKWatcher;
+import org.apache.hadoop.hbase.zookeeper.ZNodePaths;
 import org.apache.yetus.audience.InterfaceAudience;
+import org.apache.zookeeper.KeeperException;
 
-import org.apache.hbase.thirdparty.com.google.common.annotations.VisibleForTesting;
+import org.apache.hbase.thirdparty.com.google.common.collect.Maps;
 import org.apache.hbase.thirdparty.com.google.common.collect.Sets;
 
-/**
- * Client used for managing region server group information.
- *
- * @deprecated Keep it here only for tests, using {@link Admin} instead.
- */
-@Deprecated
 @InterfaceAudience.Private
-public class RSGroupAdminClient implements RSGroupAdmin, Admin {
-  private RSGroupAdminService.BlockingInterface stub;
-  private Admin admin;
+public class VerifyingRSGroupAdmin implements Admin, Closeable {
 
-  public RSGroupAdminClient(Connection conn) throws IOException {
-    admin = conn.getAdmin();
-    stub = RSGroupAdminService.newBlockingStub(admin.coprocessorService());
-  }
+  private final Connection conn;
+
+  private final Admin admin;
 
-  // for writing UTs
-  @VisibleForTesting
-  protected RSGroupAdminClient() {
+  private final ZKWatcher zkw;
+
+  public VerifyingRSGroupAdmin(Configuration conf) throws IOException {
+    conn = ConnectionFactory.createConnection(conf);
+    admin = conn.getAdmin();
+    zkw = new ZKWatcher(conf, this.getClass().getSimpleName(), null);
   }
 
-  @Override
   public int getOperationTimeout() {
-    return 0;
+    return admin.getOperationTimeout();
   }
 
-  @Override
   public int getSyncWaitTimeout() {
-    return 0;
+    return admin.getSyncWaitTimeout();
   }
 
-  @Override
   public void abort(String why, Throwable e) {
-
+    admin.abort(why, e);
   }
 
-  @Override
   public boolean isAborted() {
-    return false;
+    return admin.isAborted();
   }
 
-  @Override
   public Connection getConnection() {
-    return null;
+    return admin.getConnection();
   }
 
-  @Override
   public boolean tableExists(TableName tableName) throws IOException {
-    return false;
+    return admin.tableExists(tableName);
   }
 
-  @Override
   public List<TableDescriptor> listTableDescriptors() throws IOException {
-    return null;
+    return admin.listTableDescriptors();
   }
 
-  @Override
   public List<TableDescriptor> listTableDescriptors(boolean includeSysTables) throws IOException {
-    return null;
+    return admin.listTableDescriptors(includeSysTables);
   }
 
-  @Override
-  public List<TableDescriptor> listTableDescriptors(Pattern pattern) throws IOException {
-    return null;
-  }
-
-  @Override
   public List<TableDescriptor> listTableDescriptors(Pattern pattern, boolean includeSysTables)
-      throws IOException {
-    return null;
+    throws IOException {
+    return admin.listTableDescriptors(pattern, includeSysTables);
   }
 
-  @Override
   public TableName[] listTableNames() throws IOException {
-    return new TableName[0];
+    return admin.listTableNames();
   }
 
-  @Override
   public TableName[] listTableNames(Pattern pattern, boolean includeSysTables) throws IOException {
-    return new TableName[0];
+    return admin.listTableNames(pattern, includeSysTables);
   }
 
-  @Override
   public TableDescriptor getDescriptor(TableName tableName)
-      throws TableNotFoundException, IOException {
-    return null;
+    throws TableNotFoundException, IOException {
+    return admin.getDescriptor(tableName);
   }
 
-  @Override
   public void createTable(TableDescriptor desc, byte[] startKey, byte[] endKey, int numRegions)
-      throws IOException {
-
+    throws IOException {
+    admin.createTable(desc, startKey, endKey, numRegions);
   }
 
-  @Override
   public Future<Void> createTableAsync(TableDescriptor desc) throws IOException {
-    return null;
+    return admin.createTableAsync(desc);
   }
 
-  @Override
   public Future<Void> createTableAsync(TableDescriptor desc, byte[][] splitKeys)
-      throws IOException {
-    return null;
+    throws IOException {
+    return admin.createTableAsync(desc, splitKeys);
   }
 
-  @Override
   public Future<Void> deleteTableAsync(TableName tableName) throws IOException {
-    return null;
+    return admin.deleteTableAsync(tableName);
   }
 
-  @Override
   public Future<Void> truncateTableAsync(TableName tableName, boolean preserveSplits)
-      throws IOException {
-    return null;
+    throws IOException {
+    return admin.truncateTableAsync(tableName, preserveSplits);
   }
 
-  @Override
   public Future<Void> enableTableAsync(TableName tableName) throws IOException {
-    return null;
+    return admin.enableTableAsync(tableName);
   }
 
-  @Override
   public Future<Void> disableTableAsync(TableName tableName) throws IOException {
-    return null;
+    return admin.disableTableAsync(tableName);
   }
 
-  @Override
   public boolean isTableEnabled(TableName tableName) throws IOException {
-    return false;
+    return admin.isTableEnabled(tableName);
   }
 
-  @Override
   public boolean isTableDisabled(TableName tableName) throws IOException {
-    return false;
+    return admin.isTableDisabled(tableName);
   }
 
-  @Override
   public boolean isTableAvailable(TableName tableName) throws IOException {
-    return false;
+    return admin.isTableAvailable(tableName);
   }
 
-  @Override
   public Future<Void> addColumnFamilyAsync(TableName tableName, ColumnFamilyDescriptor columnFamily)
-      throws IOException {
-    return null;
+    throws IOException {
+    return admin.addColumnFamilyAsync(tableName, columnFamily);
   }
 
-  @Override
   public Future<Void> deleteColumnFamilyAsync(TableName tableName, byte[] columnFamily)
-      throws IOException {
-    return null;
+    throws IOException {
+    return admin.deleteColumnFamilyAsync(tableName, columnFamily);
   }
 
-  @Override
   public Future<Void> modifyColumnFamilyAsync(TableName tableName,
-      ColumnFamilyDescriptor columnFamily) throws IOException {
-    return null;
+    ColumnFamilyDescriptor columnFamily) throws IOException {
+    return admin.modifyColumnFamilyAsync(tableName, columnFamily);
   }
 
-  @Override
   public List<RegionInfo> getRegions(ServerName serverName) throws IOException {
-    return null;
+    return admin.getRegions(serverName);
   }
 
-  @Override
   public void flush(TableName tableName) throws IOException {
-
+    admin.flush(tableName);
   }
 
-  @Override
   public void flushRegion(byte[] regionName) throws IOException {
-
+    admin.flushRegion(regionName);
   }
 
-  @Override
   public void flushRegionServer(ServerName serverName) throws IOException {
-
+    admin.flushRegionServer(serverName);
   }
 
-  @Override
   public void compact(TableName tableName) throws IOException {
-
+    admin.compact(tableName);
   }
 
-  @Override
   public void compactRegion(byte[] regionName) throws IOException {
-
+    admin.compactRegion(regionName);
   }
 
-  @Override
   public void compact(TableName tableName, byte[] columnFamily) throws IOException {
-
+    admin.compact(tableName, columnFamily);
   }
 
-  @Override
   public void compactRegion(byte[] regionName, byte[] columnFamily) throws IOException {
-
+    admin.compactRegion(regionName, columnFamily);
   }
 
-  @Override
   public void compact(TableName tableName, CompactType compactType)
-      throws IOException, InterruptedException {
-
+    throws IOException, InterruptedException {
+    admin.compact(tableName, compactType);
   }
 
-  @Override
   public void compact(TableName tableName, byte[] columnFamily, CompactType compactType)
-      throws IOException, InterruptedException {
-
+    throws IOException, InterruptedException {
+    admin.compact(tableName, columnFamily, compactType);
   }
 
-  @Override
   public void majorCompact(TableName tableName) throws IOException {
-
+    admin.majorCompact(tableName);
   }
 
-  @Override
   public void majorCompactRegion(byte[] regionName) throws IOException {
-
+    admin.majorCompactRegion(regionName);
   }
 
-  @Override
   public void majorCompact(TableName tableName, byte[] columnFamily) throws IOException {
-
+    admin.majorCompact(tableName, columnFamily);
   }
 
-  @Override
   public void majorCompactRegion(byte[] regionName, byte[] columnFamily) throws IOException {
-
+    admin.majorCompactRegion(regionName, columnFamily);
   }
 
-  @Override
   public void majorCompact(TableName tableName, CompactType compactType)
-      throws IOException, InterruptedException {
-
+    throws IOException, InterruptedException {
+    admin.majorCompact(tableName, compactType);
   }
 
-  @Override
   public void majorCompact(TableName tableName, byte[] columnFamily, CompactType compactType)
-      throws IOException, InterruptedException {
-
+    throws IOException, InterruptedException {
+    admin.majorCompact(tableName, columnFamily, compactType);
   }
 
-  @Override
   public Map<ServerName, Boolean> compactionSwitch(boolean switchState,
-      List<String> serverNamesList) throws IOException {
-    return null;
+    List<String> serverNamesList) throws IOException {
+    return admin.compactionSwitch(switchState, serverNamesList);
   }
 
-  @Override
   public void compactRegionServer(ServerName serverName) throws IOException {
-
+    admin.compactRegionServer(serverName);
   }
 
-  @Override
   public void majorCompactRegionServer(ServerName serverName) throws IOException {
-
+    admin.majorCompactRegionServer(serverName);
   }
 
-  @Override
   public void move(byte[] encodedRegionName) throws IOException {
-
+    admin.move(encodedRegionName);
   }
 
-  @Override
   public void move(byte[] encodedRegionName, ServerName destServerName) throws IOException {
-
+    admin.move(encodedRegionName, destServerName);
   }
 
-  @Override
   public void assign(byte[] regionName) throws IOException {
-
+    admin.assign(regionName);
   }
 
-  @Override
   public void unassign(byte[] regionName, boolean force) throws IOException {
-
+    admin.unassign(regionName, force);
   }
 
-  @Override
   public void offline(byte[] regionName) throws IOException {
-
+    admin.offline(regionName);
   }
 
-  @Override
   public boolean balancerSwitch(boolean onOrOff, boolean synchronous) throws IOException {
-    return false;
+    return admin.balancerSwitch(onOrOff, synchronous);
   }
 
-  @Override
   public boolean balance() throws IOException {
-    return false;
+    return admin.balance();
   }
 
-  @Override
   public boolean balance(boolean force) throws IOException {
-    return false;
+    return admin.balance(force);
   }
 
-  @Override
   public boolean isBalancerEnabled() throws IOException {
-    return false;
+    return admin.isBalancerEnabled();
   }
 
-  @Override
   public CacheEvictionStats clearBlockCache(TableName tableName) throws IOException {
-    return null;
+    return admin.clearBlockCache(tableName);
   }
 
-  @Override
   public boolean normalize() throws IOException {
-    return false;
+    return admin.normalize();
   }
 
-  @Override
   public boolean isNormalizerEnabled() throws IOException {
-    return false;
+    return admin.isNormalizerEnabled();
   }
 
-  @Override
   public boolean normalizerSwitch(boolean on) throws IOException {
-    return false;
+    return admin.normalizerSwitch(on);
   }
 
-  @Override
   public boolean catalogJanitorSwitch(boolean onOrOff) throws IOException {
-    return false;
+    return admin.catalogJanitorSwitch(onOrOff);
   }
 
-  @Override
   public int runCatalogJanitor() throws IOException {
-    return 0;
+    return admin.runCatalogJanitor();
   }
 
-  @Override
   public boolean isCatalogJanitorEnabled() throws IOException {
-    return false;
+    return admin.isCatalogJanitorEnabled();
   }
 
-  @Override
   public boolean cleanerChoreSwitch(boolean onOrOff) throws IOException {
-    return false;
+    return admin.cleanerChoreSwitch(onOrOff);
   }
 
-  @Override
   public boolean runCleanerChore() throws IOException {
-    return false;
+    return admin.runCleanerChore();
   }
 
-  @Override
   public boolean isCleanerChoreEnabled() throws IOException {
-    return false;
+    return admin.isCleanerChoreEnabled();
   }
 
-  @Override
   public Future<Void> mergeRegionsAsync(byte[][] nameofRegionsToMerge, boolean forcible)
-      throws IOException {
-    return null;
+    throws IOException {
+    return admin.mergeRegionsAsync(nameofRegionsToMerge, forcible);
   }
 
-  @Override
   public void split(TableName tableName) throws IOException {
-
+    admin.split(tableName);
   }
 
-  @Override
   public void split(TableName tableName, byte[] splitPoint) throws IOException {
-
+    admin.split(tableName, splitPoint);
   }
 
-  @Override
   public Future<Void> splitRegionAsync(byte[] regionName) throws IOException {
-    return null;
+    return admin.splitRegionAsync(regionName);
   }
 
-  @Override
   public Future<Void> splitRegionAsync(byte[] regionName, byte[] splitPoint) throws IOException {
-    return null;
+    return admin.splitRegionAsync(regionName, splitPoint);
   }
 
-  @Override
   public Future<Void> modifyTableAsync(TableDescriptor td) throws IOException {
-    return null;
+    return admin.modifyTableAsync(td);
   }
 
-  @Override
   public void shutdown() throws IOException {
-
+    admin.shutdown();
   }
 
-  @Override
   public void stopMaster() throws IOException {
-
+    admin.stopMaster();
   }
 
-  @Override
   public boolean isMasterInMaintenanceMode() throws IOException {
-    return false;
+    return admin.isMasterInMaintenanceMode();
   }
 
-  @Override
   public void stopRegionServer(String hostnamePort) throws IOException {
-
+    admin.stopRegionServer(hostnamePort);
   }
 
-  @Override
-  public ClusterMetrics getClusterMetrics(EnumSet<ClusterMetrics.Option> options)
-      throws IOException {
-    return null;
+  public ClusterMetrics getClusterMetrics(EnumSet<Option> options) throws IOException {
+    return admin.getClusterMetrics(options);
   }
 
-  @Override
   public List<RegionMetrics> getRegionMetrics(ServerName serverName) throws IOException {
-    return null;
+    return admin.getRegionMetrics(serverName);
   }
 
-  @Override
   public List<RegionMetrics> getRegionMetrics(ServerName serverName, TableName tableName)
-      throws IOException {
-    return null;
+    throws IOException {
+    return admin.getRegionMetrics(serverName, tableName);
   }
 
-  @Override
   public Configuration getConfiguration() {
-    return null;
+    return admin.getConfiguration();
   }
 
-  @Override
   public Future<Void> createNamespaceAsync(NamespaceDescriptor descriptor) throws IOException {
-    return null;
+    return admin.createNamespaceAsync(descriptor);
   }
 
-  @Override
   public Future<Void> modifyNamespaceAsync(NamespaceDescriptor descriptor) throws IOException {
-    return null;
+    return admin.modifyNamespaceAsync(descriptor);
   }
 
-  @Override
   public Future<Void> deleteNamespaceAsync(String name) throws IOException {
-    return null;
+    return admin.deleteNamespaceAsync(name);
   }
 
-  @Override
   public NamespaceDescriptor getNamespaceDescriptor(String name)
-      throws NamespaceNotFoundException, IOException {
-    return null;
+    throws NamespaceNotFoundException, IOException {
+    return admin.getNamespaceDescriptor(name);
   }
 
-  @Override
   public String[] listNamespaces() throws IOException {
-    return new String[0];
+    return admin.listNamespaces();
   }
 
-  @Override
   public NamespaceDescriptor[] listNamespaceDescriptors() throws IOException {
-    return new NamespaceDescriptor[0];
+    return admin.listNamespaceDescriptors();
   }
 
-  @Override
   public List<TableDescriptor> listTableDescriptorsByNamespace(byte[] name) throws IOException {
-    return null;
+    return admin.listTableDescriptorsByNamespace(name);
   }
 
-  @Override
   public TableName[] listTableNamesByNamespace(String name) throws IOException {
-    return new TableName[0];
+    return admin.listTableNamesByNamespace(name);
   }
 
-  @Override
   public List<RegionInfo> getRegions(TableName tableName) throws IOException {
-    return null;
+    return admin.getRegions(tableName);
   }
 
-  @Override
   public void close() {
-
+    admin.close();
   }
 
-  @Override
   public List<TableDescriptor> listTableDescriptors(List<TableName> tableNames) throws IOException {
-    return null;
+    return admin.listTableDescriptors(tableNames);
   }
 
-  @Override
   public Future<Boolean> abortProcedureAsync(long procId, boolean mayInterruptIfRunning)
-      throws IOException {
-    return null;
+    throws IOException {
+    return admin.abortProcedureAsync(procId, mayInterruptIfRunning);
   }
 
-  @Override
   public String getProcedures() throws IOException {
-    return null;
+    return admin.getProcedures();
   }
 
-  @Override
   public String getLocks() throws IOException {
-    return null;
+    return admin.getLocks();
   }
 
-  @Override
   public void rollWALWriter(ServerName serverName) throws IOException, FailedLogCloseException {
-
+    admin.rollWALWriter(serverName);
   }
 
-  @Override
   public CompactionState getCompactionState(TableName tableName) throws IOException {
-    return null;
+    return admin.getCompactionState(tableName);
   }
 
-  @Override
   public CompactionState getCompactionState(TableName tableName, CompactType compactType)
-      throws IOException {
-    return null;
+    throws IOException {
+    return admin.getCompactionState(tableName, compactType);
   }
 
-  @Override
   public CompactionState getCompactionStateForRegion(byte[] regionName) throws IOException {
-    return null;
+    return admin.getCompactionStateForRegion(regionName);
   }
 
-  @Override
   public long getLastMajorCompactionTimestamp(TableName tableName) throws IOException {
-    return 0;
+    return admin.getLastMajorCompactionTimestamp(tableName);
   }
 
-  @Override
   public long getLastMajorCompactionTimestampForRegion(byte[] regionName) throws IOException {
-    return 0;
+    return admin.getLastMajorCompactionTimestampForRegion(regionName);
   }
 
-  @Override
   public void snapshot(SnapshotDescription snapshot)
-      throws IOException, SnapshotCreationException, IllegalArgumentException {
-
+    throws IOException, SnapshotCreationException, IllegalArgumentException {
+    admin.snapshot(snapshot);
   }
 
-  @Override
   public Future<Void> snapshotAsync(SnapshotDescription snapshot)
-      throws IOException, SnapshotCreationException {
-    return null;
+    throws IOException, SnapshotCreationException {
+    return admin.snapshotAsync(snapshot);
   }
 
-  @Override
   public boolean isSnapshotFinished(SnapshotDescription snapshot)
-      throws IOException, HBaseSnapshotException, UnknownSnapshotException {
-    return false;
+    throws IOException, HBaseSnapshotException, UnknownSnapshotException {
+    return admin.isSnapshotFinished(snapshot);
   }
 
-  @Override
   public void restoreSnapshot(String snapshotName) throws IOException, RestoreSnapshotException {
-
+    admin.restoreSnapshot(snapshotName);
   }
 
-  @Override
   public void restoreSnapshot(String snapshotName, boolean takeFailSafeSnapshot, boolean restoreAcl)
-      throws IOException, RestoreSnapshotException {
-
+    throws IOException, RestoreSnapshotException {
+    admin.restoreSnapshot(snapshotName, takeFailSafeSnapshot, restoreAcl);
   }
 
-  @Override
   public Future<Void> cloneSnapshotAsync(String snapshotName, TableName tableName,
-      boolean restoreAcl) throws IOException, TableExistsException, RestoreSnapshotException {
-    return null;
+    boolean restoreAcl) throws IOException, TableExistsException, RestoreSnapshotException {
+    return admin.cloneSnapshotAsync(snapshotName, tableName, restoreAcl);
   }
 
-  @Override
   public void execProcedure(String signature, String instance, Map<String, String> props)
-      throws IOException {
-
+    throws IOException {
+    admin.execProcedure(signature, instance, props);
   }
 
-  @Override
   public byte[] execProcedureWithReturn(String signature, String instance,
-      Map<String, String> props) throws IOException {
-    return new byte[0];
+    Map<String, String> props) throws IOException {
+    return admin.execProcedureWithReturn(signature, instance, props);
   }
 
-  @Override
   public boolean isProcedureFinished(String signature, String instance, Map<String, String> props)
-      throws IOException {
-    return false;
+    throws IOException {
+    return admin.isProcedureFinished(signature, instance, props);
   }
 
-  @Override
   public List<SnapshotDescription> listSnapshots() throws IOException {
-    return null;
+    return admin.listSnapshots();
   }
 
-  @Override
   public List<SnapshotDescription> listSnapshots(Pattern pattern) throws IOException {
-    return null;
+    return admin.listSnapshots(pattern);
   }
 
-  @Override
   public List<SnapshotDescription> listTableSnapshots(Pattern tableNamePattern,
-      Pattern snapshotNamePattern) throws IOException {
-    return null;
+    Pattern snapshotNamePattern) throws IOException {
+    return admin.listTableSnapshots(tableNamePattern, snapshotNamePattern);
   }
 
-  @Override
   public void deleteSnapshot(String snapshotName) throws IOException {
-
+    admin.deleteSnapshot(snapshotName);
   }
 
-  @Override
   public void deleteSnapshots(Pattern pattern) throws IOException {
-
+    admin.deleteSnapshots(pattern);
   }
 
-  @Override
   public void deleteTableSnapshots(Pattern tableNamePattern, Pattern snapshotNamePattern)
-      throws IOException {
-
+    throws IOException {
+    admin.deleteTableSnapshots(tableNamePattern, snapshotNamePattern);
   }
 
-  @Override
   public void setQuota(QuotaSettings quota) throws IOException {
-
+    admin.setQuota(quota);
   }
 
-  @Override
   public List<QuotaSettings> getQuota(QuotaFilter filter) throws IOException {
-    return null;
+    return admin.getQuota(filter);
   }
 
-  @Override
   public CoprocessorRpcChannel coprocessorService() {
-    return null;
+    return admin.coprocessorService();
   }
 
-  @Override
   public CoprocessorRpcChannel coprocessorService(ServerName serverName) {
-    return null;
+    return admin.coprocessorService(serverName);
   }
 
-  @Override
   public void updateConfiguration(ServerName server) throws IOException {
-
+    admin.updateConfiguration(server);
   }
 
-  @Override
   public void updateConfiguration() throws IOException {
-
+    admin.updateConfiguration();
   }
 
-  @Override
   public List<SecurityCapability> getSecurityCapabilities() throws IOException {
-    return null;
+    return admin.getSecurityCapabilities();
   }
 
-  @Override
   public boolean splitSwitch(boolean enabled, boolean synchronous) throws IOException {
-    return false;
+    return admin.splitSwitch(enabled, synchronous);
   }
 
-  @Override
   public boolean mergeSwitch(boolean enabled, boolean synchronous) throws IOException {
-    return false;
+    return admin.mergeSwitch(enabled, synchronous);
   }
 
-  @Override
   public boolean isSplitEnabled() throws IOException {
-    return false;
+    return admin.isSplitEnabled();
   }
 
-  @Override
   public boolean isMergeEnabled() throws IOException {
-    return false;
+    return admin.isMergeEnabled();
   }
 
-  @Override
   public Future<Void> addReplicationPeerAsync(String peerId, ReplicationPeerConfig peerConfig,
-      boolean enabled) throws IOException {
-    return null;
+    boolean enabled) throws IOException {
+    return admin.addReplicationPeerAsync(peerId, peerConfig, enabled);
   }
 
-  @Override
   public Future<Void> removeReplicationPeerAsync(String peerId) throws IOException {
-    return null;
+    return admin.removeReplicationPeerAsync(peerId);
   }
 
-  @Override
   public Future<Void> enableReplicationPeerAsync(String peerId) throws IOException {
-    return null;
+    return admin.enableReplicationPeerAsync(peerId);
   }
 
-  @Override
   public Future<Void> disableReplicationPeerAsync(String peerId) throws IOException {
-    return null;
+    return admin.disableReplicationPeerAsync(peerId);
   }
 
-  @Override
   public ReplicationPeerConfig getReplicationPeerConfig(String peerId) throws IOException {
-    return null;
+    return admin.getReplicationPeerConfig(peerId);
   }
 
-  @Override
   public Future<Void> updateReplicationPeerConfigAsync(String peerId,
-      ReplicationPeerConfig peerConfig) throws IOException {
-    return null;
+    ReplicationPeerConfig peerConfig) throws IOException {
+    return admin.updateReplicationPeerConfigAsync(peerId, peerConfig);
   }
 
-  @Override
   public List<ReplicationPeerDescription> listReplicationPeers() throws IOException {
-    return null;
+    return admin.listReplicationPeers();
   }
 
-  @Override
   public List<ReplicationPeerDescription> listReplicationPeers(Pattern pattern) throws IOException {
-    return null;
+    return admin.listReplicationPeers(pattern);
   }
 
-  @Override
   public Future<Void> transitReplicationPeerSyncReplicationStateAsync(String peerId,
-      SyncReplicationState state) throws IOException {
-    return null;
+    SyncReplicationState state) throws IOException {
+    return admin.transitReplicationPeerSyncReplicationStateAsync(peerId, state);
   }
 
-  @Override
   public void decommissionRegionServers(List<ServerName> servers, boolean offload)
-      throws IOException {
-
+    throws IOException {
+    admin.decommissionRegionServers(servers, offload);
   }
 
-  @Override
   public List<ServerName> listDecommissionedRegionServers() throws IOException {
-    return null;
+    return admin.listDecommissionedRegionServers();
   }
 
-  @Override
   public void recommissionRegionServer(ServerName server, List<byte[]> encodedRegionNames)
-      throws IOException {
-
+    throws IOException {
+    admin.recommissionRegionServer(server, encodedRegionNames);
   }
 
-  @Override
   public List<TableCFs> listReplicatedTableCFs() throws IOException {
-    return null;
+    return admin.listReplicatedTableCFs();
   }
 
-  @Override
   public void enableTableReplication(TableName tableName) throws IOException {
-
+    admin.enableTableReplication(tableName);
   }
 
-  @Override
   public void disableTableReplication(TableName tableName) throws IOException {
-
+    admin.disableTableReplication(tableName);
   }
 
-  @Override
   public void clearCompactionQueues(ServerName serverName, Set<String> queues)
-      throws IOException, InterruptedException {
-
+    throws IOException, InterruptedException {
+    admin.clearCompactionQueues(serverName, queues);
   }
 
-  @Override
   public List<ServerName> clearDeadServers(List<ServerName> servers) throws IOException {
-    return null;
+    return admin.clearDeadServers(servers);
   }
 
-  @Override
   public void cloneTableSchema(TableName tableName, TableName newTableName, boolean preserveSplits)
-      throws IOException {
-
+    throws IOException {
+    admin.cloneTableSchema(tableName, newTableName, preserveSplits);
   }
 
-  @Override
   public boolean switchRpcThrottle(boolean enable) throws IOException {
-    return false;
+    return admin.switchRpcThrottle(enable);
   }
 
-  @Override
   public boolean isRpcThrottleEnabled() throws IOException {
-    return false;
+    return admin.isRpcThrottleEnabled();
   }
 
-  @Override
   public boolean exceedThrottleQuotaSwitch(boolean enable) throws IOException {
-    return false;
+    return admin.exceedThrottleQuotaSwitch(enable);
   }
 
-  @Override
   public Map<TableName, Long> getSpaceQuotaTableSizes() throws IOException {
-    return null;
+    return admin.getSpaceQuotaTableSizes();
   }
 
-  @Override
-  public Map<TableName, ? extends SpaceQuotaSnapshotView> getRegionServerSpaceQuotaSnapshots(
-      ServerName serverName) throws IOException {
-    return null;
+  public Map<TableName, ? extends SpaceQuotaSnapshotView>
+    getRegionServerSpaceQuotaSnapshots(ServerName serverName) throws IOException {
+    return admin.getRegionServerSpaceQuotaSnapshots(serverName);
   }
 
-  @Override
   public SpaceQuotaSnapshotView getCurrentSpaceQuotaSnapshot(String namespace) throws IOException {
-    return null;
+    return admin.getCurrentSpaceQuotaSnapshot(namespace);
   }
 
-  @Override
   public SpaceQuotaSnapshotView getCurrentSpaceQuotaSnapshot(TableName tableName)
-      throws IOException {
-    return null;
+    throws IOException {
+    return admin.getCurrentSpaceQuotaSnapshot(tableName);
   }
 
-  @Override
   public void grant(UserPermission userPermission, boolean mergeExistingPermissions)
-      throws IOException {
-
+    throws IOException {
+    admin.grant(userPermission, mergeExistingPermissions);
   }
 
-  @Override
   public void revoke(UserPermission userPermission) throws IOException {
-
+    admin.revoke(userPermission);
   }
 
-  @Override
-  public List<UserPermission> getUserPermissions(
-      GetUserPermissionsRequest getUserPermissionsRequest) throws IOException {
-    return null;
+  public List<UserPermission>
+    getUserPermissions(GetUserPermissionsRequest getUserPermissionsRequest) throws IOException {
+    return admin.getUserPermissions(getUserPermissionsRequest);
   }
 
-  @Override
   public List<Boolean> hasUserPermissions(String userName, List<Permission> permissions)
-      throws IOException {
-    return null;
+    throws IOException {
+    return admin.hasUserPermissions(userName, permissions);
   }
 
-  @Override
   public boolean snapshotCleanupSwitch(boolean on, boolean synchronous) throws IOException {
-    return false;
+    return admin.snapshotCleanupSwitch(on, synchronous);
   }
 
-  @Override
   public boolean isSnapshotCleanupEnabled() throws IOException {
-    return false;
-  }
-
-  @Override
-  public RSGroupInfo getRSGroupInfo(String groupName) throws IOException {
-    return getRSGroup(groupName);
-  }
-
-  @Override
-  public void moveServers(Set<Address> servers, String targetGroup) throws IOException {
-    moveToRSGroup(servers, targetGroup);
+    return admin.isSnapshotCleanupEnabled();
   }
 
-  @Override
   public void addRSGroup(String groupName) throws IOException {
-    AddRSGroupRequest request = AddRSGroupRequest.newBuilder().setRSGroupName(groupName).build();
-    try {
-      stub.addRSGroup(null, request);
-    } catch (ServiceException e) {
-      throw ProtobufUtil.handleRemoteException(e);
-    }
+    admin.addRSGroup(groupName);
+    verify();
   }
 
-  @Override
   public RSGroupInfo getRSGroup(String groupName) throws IOException {
-    try {
-      GetRSGroupInfoResponse resp = stub.getRSGroupInfo(null,
-          GetRSGroupInfoRequest.newBuilder().setRSGroupName(groupName).build());
-      if (resp.hasRSGroupInfo()) {
-        return ProtobufUtil.toGroupInfo(resp.getRSGroupInfo());
-      }
-      return null;
-    } catch (ServiceException e) {
-      throw ProtobufUtil.handleRemoteException(e);
-    }
+    return admin.getRSGroup(groupName);
   }
 
-  @Override
   public RSGroupInfo getRSGroup(Address hostPort) throws IOException {
-    GetRSGroupInfoOfServerRequest request = GetRSGroupInfoOfServerRequest.newBuilder().setServer(
-        HBaseProtos.ServerName.newBuilder().setHostName(hostPort.getHostname())
-            .setPort(hostPort.getPort()).build()).build();
-    try {
-      GetRSGroupInfoOfServerResponse resp = stub.getRSGroupInfoOfServer(null, request);
-      if (resp.hasRSGroupInfo()) {
-        return ProtobufUtil.toGroupInfo(resp.getRSGroupInfo());
-      }
-      return null;
-    } catch (ServiceException e) {
-      throw ProtobufUtil.handleRemoteException(e);
-    }
+    return admin.getRSGroup(hostPort);
   }
 
-  @Override
   public RSGroupInfo getRSGroup(TableName tableName) throws IOException {
-    GetRSGroupInfoOfTableRequest request = GetRSGroupInfoOfTableRequest.newBuilder()
-        .setTableName(ProtobufUtil.toProtoTableName(tableName)).build();
-    try {
-      GetRSGroupInfoOfTableResponse resp = stub.getRSGroupInfoOfTable(null, request);
-      if (resp.hasRSGroupInfo()) {
-        return ProtobufUtil.toGroupInfo(resp.getRSGroupInfo());
-      }
-      return null;
-    } catch (ServiceException e) {
-      throw ProtobufUtil.handleRemoteException(e);
-    }
+    return admin.getRSGroup(tableName);
   }
 
-  @Override
-  public void removeRSGroup(String name) throws IOException {
-    RemoveRSGroupRequest request = RemoveRSGroupRequest.newBuilder().setRSGroupName(name).build();
-    try {
-      stub.removeRSGroup(null, request);
-    } catch (ServiceException e) {
-      throw ProtobufUtil.handleRemoteException(e);
-    }
+  public List<RSGroupInfo> listRSGroups() throws IOException {
+    return admin.listRSGroups();
   }
 
-  @Override
-  public void removeRSGroup(Set<Address> servers) throws IOException {
-    Set<HBaseProtos.ServerName> hostPorts = Sets.newHashSet();
-    for (Address el : servers) {
-      hostPorts.add(
-          HBaseProtos.ServerName.newBuilder().setHostName(el.getHostname()).setPort(el.getPort())
-              .build());
-    }
-    RemoveServersRequest request =
-        RemoveServersRequest.newBuilder().addAllServers(hostPorts).build();
-    try {
-      stub.removeServers(null, request);
-    } catch (ServiceException e) {
-      throw ProtobufUtil.handleRemoteException(e);
-    }
+  public void removeRSGroup(String groupName) throws IOException {
+    admin.removeRSGroup(groupName);
+    verify();
   }
 
-  @Override
-  public void moveToRSGroup(Set<Address> servers, String targetGroup) throws IOException {
-    Set<HBaseProtos.ServerName> hostPorts = Sets.newHashSet();
-    for (Address el : servers) {
-      hostPorts.add(
-          HBaseProtos.ServerName.newBuilder().setHostName(el.getHostname()).setPort(el.getPort())
-              .build());
-    }
-    MoveServersRequest request =
-        MoveServersRequest.newBuilder().setTargetGroup(targetGroup).addAllServers(hostPorts)
-            .build();
-    try {
-      stub.moveServers(null, request);
-    } catch (ServiceException e) {
-      throw ProtobufUtil.handleRemoteException(e);
-    }
+  public void removeServersFromRSGroup(Set<Address> servers) throws IOException {
+    admin.removeServersFromRSGroup(servers);
+    verify();
+  }
+
+  public void moveServersToRSGroup(Set<Address> servers, String targetGroup) throws IOException {
+    admin.moveServersToRSGroup(servers, targetGroup);
+    verify();
   }
 
-  @Override
   public void setRSGroup(Set<TableName> tables, String groupName) throws IOException {
-    MoveTablesRequest.Builder builder = MoveTablesRequest.newBuilder().setTargetGroup(groupName);
-    for (TableName tableName : tables) {
-      builder.addTableName(ProtobufUtil.toProtoTableName(tableName));
-      if (!admin.tableExists(tableName)) {
-        throw new TableNotFoundException(tableName);
-      }
-    }
-    try {
-      stub.moveTables(null, builder.build());
-    } catch (ServiceException e) {
-      throw ProtobufUtil.handleRemoteException(e);
-    }
+    admin.setRSGroup(tables, groupName);
+    verify();
   }
 
-  @Override
   public boolean balanceRSGroup(String groupName) throws IOException {
-    BalanceRSGroupRequest request =
-        BalanceRSGroupRequest.newBuilder().setRSGroupName(groupName).build();
-    try {
-      return stub.balanceRSGroup(null, request).getBalanceRan();
-    } catch (ServiceException e) {
-      throw ProtobufUtil.handleRemoteException(e);
-    }
+    return admin.balanceRSGroup(groupName);
   }
 
-  @Override
-  public List<RSGroupInfo> listRSGroups() throws IOException {
-    try {
-      List<RSGroupProtos.RSGroupInfo> resp =
-          stub.listRSGroupInfos(null, ListRSGroupInfosRequest.getDefaultInstance())
-              .getRSGroupInfoList();
-      List<RSGroupInfo> result = new ArrayList<>(resp.size());
-      for (RSGroupProtos.RSGroupInfo entry : resp) {
-        result.add(ProtobufUtil.toGroupInfo(entry));
+  private void verify() throws IOException {
+    Map<String, RSGroupInfo> groupMap = Maps.newHashMap();
+    Set<RSGroupInfo> zList = Sets.newHashSet();
+    List<TableDescriptor> tds = new ArrayList<>();
+    try (Admin admin = conn.getAdmin()) {
+      tds.addAll(admin.listTableDescriptors());
+      tds.addAll(admin.listTableDescriptorsByNamespace(NamespaceDescriptor.SYSTEM_NAMESPACE_NAME));
+    }
+    SortedSet<Address> lives = Sets.newTreeSet();
+    for (ServerName sn : conn.getAdmin().getClusterMetrics().getLiveServerMetrics().keySet()) {
+      lives.add(sn.getAddress());
+    }
+    for (ServerName sn : conn.getAdmin().listDecommissionedRegionServers()) {
+      lives.remove(sn.getAddress());
+    }
+    try (Table table = conn.getTable(RSGroupInfoManagerImpl.RSGROUP_TABLE_NAME);
+      ResultScanner scanner = table.getScanner(new Scan())) {
+      for (;;) {
+        Result result = scanner.next();
+        if (result == null) {
+          break;
+        }
+        RSGroupProtos.RSGroupInfo proto = RSGroupProtos.RSGroupInfo.parseFrom(result.getValue(
+          RSGroupInfoManagerImpl.META_FAMILY_BYTES, RSGroupInfoManagerImpl.META_QUALIFIER_BYTES));
+        RSGroupInfo rsGroupInfo = ProtobufUtil.toGroupInfo(proto);
+        groupMap.put(proto.getName(), RSGroupUtil.fillTables(rsGroupInfo, tds));
+        for (Address address : rsGroupInfo.getServers()) {
+          lives.remove(address);
+        }
+      }
+    }
+    SortedSet<TableName> tables = Sets.newTreeSet();
+    for (TableDescriptor td : conn.getAdmin().listTableDescriptors(Pattern.compile(".*"), true)) {
+      String groupName = td.getRegionServerGroup().orElse(RSGroupInfo.DEFAULT_GROUP);
+      if (groupName.equals(RSGroupInfo.DEFAULT_GROUP)) {
+        tables.add(td.getTableName());
       }
-      return result;
-    } catch (ServiceException e) {
-      throw ProtobufUtil.handleRemoteException(e);
     }
-  }
-
-  @Override
-  public RSGroupInfo getRSGroupOfServer(Address hostPort) throws IOException {
-    return getRSGroup(hostPort);
-  }
 
-  @Override
-  public void removeServers(Set<Address> servers) throws IOException {
-    removeRSGroup(servers);
+    groupMap.put(RSGroupInfo.DEFAULT_GROUP,
+      new RSGroupInfo(RSGroupInfo.DEFAULT_GROUP, lives, tables));
+    assertEquals(Sets.newHashSet(groupMap.values()), Sets.newHashSet(admin.listRSGroups()));
+    try {
+      String groupBasePath = ZNodePaths.joinZNode(zkw.getZNodePaths().baseZNode, "rsgroup");
+      for (String znode : ZKUtil.listChildrenNoWatch(zkw, groupBasePath)) {
+        byte[] data = ZKUtil.getData(zkw, ZNodePaths.joinZNode(groupBasePath, znode));
+        if (data.length > 0) {
+          ProtobufUtil.expectPBMagicPrefix(data);
+          ByteArrayInputStream bis =
+            new ByteArrayInputStream(data, ProtobufUtil.lengthOfPBMagic(), data.length);
+          RSGroupInfo rsGroupInfo =
+            ProtobufUtil.toGroupInfo(RSGroupProtos.RSGroupInfo.parseFrom(bis));
+          zList.add(RSGroupUtil.fillTables(rsGroupInfo, tds));
+        }
+      }
+      groupMap.remove(RSGroupInfo.DEFAULT_GROUP);
+      assertEquals(zList.size(), groupMap.size());
+      for (RSGroupInfo rsGroupInfo : zList) {
+        assertTrue(groupMap.get(rsGroupInfo.getName()).equals(rsGroupInfo));
+      }
+    } catch (KeeperException e) {
+      throw new IOException("ZK verification failed", e);
+    } catch (DeserializationException e) {
+      throw new IOException("ZK verification failed", e);
+    } catch (InterruptedException e) {
+      throw new IOException("ZK verification failed", e);
+    }
   }
-
 }
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/VerifyingRSGroupAdminClient.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/VerifyingRSGroupAdminClient.java
deleted file mode 100644
index b189697..0000000
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/VerifyingRSGroupAdminClient.java
+++ /dev/null
@@ -1,193 +0,0 @@
-/**
- * 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.junit.Assert.assertTrue;
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.SortedSet;
-import java.util.regex.Pattern;
-import org.apache.hadoop.conf.Configuration;
-import org.apache.hadoop.hbase.NamespaceDescriptor;
-import org.apache.hadoop.hbase.ServerName;
-import org.apache.hadoop.hbase.TableName;
-import org.apache.hadoop.hbase.client.Admin;
-import org.apache.hadoop.hbase.client.Connection;
-import org.apache.hadoop.hbase.client.ConnectionFactory;
-import org.apache.hadoop.hbase.client.Result;
-import org.apache.hadoop.hbase.client.ResultScanner;
-import org.apache.hadoop.hbase.client.Scan;
-import org.apache.hadoop.hbase.client.Table;
-import org.apache.hadoop.hbase.client.TableDescriptor;
-import org.apache.hadoop.hbase.exceptions.DeserializationException;
-import org.apache.hadoop.hbase.net.Address;
-import org.apache.hadoop.hbase.protobuf.ProtobufUtil;
-import org.apache.hadoop.hbase.protobuf.generated.RSGroupProtos;
-import org.apache.hadoop.hbase.zookeeper.ZKUtil;
-import org.apache.hadoop.hbase.zookeeper.ZKWatcher;
-import org.apache.hadoop.hbase.zookeeper.ZNodePaths;
-import org.apache.yetus.audience.InterfaceAudience;
-import org.apache.zookeeper.KeeperException;
-
-import org.apache.hbase.thirdparty.com.google.common.collect.Maps;
-import org.apache.hbase.thirdparty.com.google.common.collect.Sets;
-
-@InterfaceAudience.Private
-public class VerifyingRSGroupAdminClient extends RSGroupAdminClient {
-  private Connection conn;
-  private ZKWatcher zkw;
-  private RSGroupAdminClient wrapped;
-
-  public VerifyingRSGroupAdminClient(RSGroupAdminClient RSGroupAdmin, Configuration conf)
-      throws IOException {
-    wrapped = RSGroupAdmin;
-    conn = ConnectionFactory.createConnection(conf);
-    zkw = new ZKWatcher(conf, this.getClass().getSimpleName(), null);
-  }
-
-  @Override
-  public void addRSGroup(String groupName) throws IOException {
-    wrapped.addRSGroup(groupName);
-    verify();
-  }
-
-  @Override
-  public RSGroupInfo getRSGroup(String groupName) throws IOException {
-    return wrapped.getRSGroupInfo(groupName);
-  }
-
-  @Override
-  public RSGroupInfo getRSGroup(TableName tableName) throws IOException {
-    return wrapped.getRSGroup(tableName);
-  }
-
-  @Override
-  public void moveToRSGroup(Set<Address> servers, String targetGroup) throws IOException {
-    wrapped.moveToRSGroup(servers, targetGroup);
-    verify();
-  }
-
-  @Override
-  public void removeRSGroup(String name) throws IOException {
-    wrapped.removeRSGroup(name);
-    verify();
-  }
-
-  @Override
-  public boolean balanceRSGroup(String groupName) throws IOException {
-    return wrapped.balanceRSGroup(groupName);
-  }
-
-  @Override
-  public List<RSGroupInfo> listRSGroups() throws IOException {
-    return wrapped.listRSGroups();
-  }
-
-  @Override
-  public RSGroupInfo getRSGroup(Address hostPort) throws IOException {
-    return wrapped.getRSGroup(hostPort);
-  }
-
-  @Override
-  public void removeRSGroup(Set<Address> servers) throws IOException {
-    wrapped.removeRSGroup(servers);
-    verify();
-  }
-
-  @Override
-  public void setRSGroup(Set<TableName> tables, String groupName) throws IOException{
-    wrapped.setRSGroup(tables, groupName);
-    verify();
-  }
-
-  public void verify() throws IOException {
-    Map<String, RSGroupInfo> groupMap = Maps.newHashMap();
-    Set<RSGroupInfo> zList = Sets.newHashSet();
-    List<TableDescriptor> tds = new ArrayList<>();
-    try (Admin admin = conn.getAdmin()) {
-      tds.addAll(admin.listTableDescriptors());
-      tds.addAll(admin.listTableDescriptorsByNamespace(NamespaceDescriptor.SYSTEM_NAMESPACE_NAME));
-    }
-    SortedSet<Address> lives = Sets.newTreeSet();
-    for (ServerName sn : conn.getAdmin().getClusterMetrics().getLiveServerMetrics().keySet()) {
-      lives.add(sn.getAddress());
-    }
-    for (ServerName sn : conn.getAdmin().listDecommissionedRegionServers()) {
-      lives.remove(sn.getAddress());
-    }
-    try (Table table = conn.getTable(RSGroupInfoManagerImpl.RSGROUP_TABLE_NAME);
-        ResultScanner scanner = table.getScanner(new Scan())) {
-      for (;;) {
-        Result result = scanner.next();
-        if (result == null) {
-          break;
-        }
-        RSGroupProtos.RSGroupInfo proto = RSGroupProtos.RSGroupInfo.parseFrom(result.getValue(
-          RSGroupInfoManagerImpl.META_FAMILY_BYTES, RSGroupInfoManagerImpl.META_QUALIFIER_BYTES));
-        RSGroupInfo rsGroupInfo = ProtobufUtil.toGroupInfo(proto);
-        groupMap.put(proto.getName(), RSGroupUtil.fillTables(rsGroupInfo, tds));
-        for(Address address : rsGroupInfo.getServers()){
-          lives.remove(address);
-        }
-      }
-    }
-    SortedSet<TableName> tables = Sets.newTreeSet();
-    for (TableDescriptor td : conn.getAdmin().listTableDescriptors(Pattern.compile(".*"),
-        true)){
-      String groupName = td.getRegionServerGroup().orElse(RSGroupInfo.DEFAULT_GROUP);
-      if (groupName.equals(RSGroupInfo.DEFAULT_GROUP)) {
-        tables.add(td.getTableName());
-      }
-    }
-
-    groupMap.put(RSGroupInfo.DEFAULT_GROUP,
-        new RSGroupInfo(RSGroupInfo.DEFAULT_GROUP, lives, tables));
-    assertEquals(Sets.newHashSet(groupMap.values()), Sets.newHashSet(wrapped.listRSGroups()));
-    try {
-      String groupBasePath = ZNodePaths.joinZNode(zkw.getZNodePaths().baseZNode, "rsgroup");
-      for (String znode : ZKUtil.listChildrenNoWatch(zkw, groupBasePath)) {
-        byte[] data = ZKUtil.getData(zkw, ZNodePaths.joinZNode(groupBasePath, znode));
-        if (data.length > 0) {
-          ProtobufUtil.expectPBMagicPrefix(data);
-          ByteArrayInputStream bis =
-              new ByteArrayInputStream(data, ProtobufUtil.lengthOfPBMagic(), data.length);
-          RSGroupInfo rsGroupInfo =
-              ProtobufUtil.toGroupInfo(RSGroupProtos.RSGroupInfo.parseFrom(bis));
-          zList.add(RSGroupUtil.fillTables(rsGroupInfo, tds));
-        }
-      }
-      groupMap.remove(RSGroupInfo.DEFAULT_GROUP);
-      assertEquals(zList.size(), groupMap.size());
-      for (RSGroupInfo rsGroupInfo : zList) {
-        assertTrue(groupMap.get(rsGroupInfo.getName()).equals(rsGroupInfo));
-      }
-    } catch (KeeperException e) {
-      throw new IOException("ZK verification failed", e);
-    } catch (DeserializationException e) {
-      throw new IOException("ZK verification failed", e);
-    } catch (InterruptedException e) {
-      throw new IOException("ZK verification failed", e);
-    }
-  }
-}
diff --git a/hbase-thrift/src/main/java/org/apache/hadoop/hbase/thrift2/client/ThriftAdmin.java b/hbase-thrift/src/main/java/org/apache/hadoop/hbase/thrift2/client/ThriftAdmin.java
index 52d032e..a222c74 100644
--- a/hbase-thrift/src/main/java/org/apache/hadoop/hbase/thrift2/client/ThriftAdmin.java
+++ b/hbase-thrift/src/main/java/org/apache/hadoop/hbase/thrift2/client/ThriftAdmin.java
@@ -1165,7 +1165,7 @@ public class ThriftAdmin implements Admin {
   }
 
   @Override
-  public void moveToRSGroup(Set<Address> servers, String targetGroup) {
+  public void moveServersToRSGroup(Set<Address> servers, String targetGroup) {
     throw new NotImplementedException("moveToRSGroup not supported in ThriftAdmin");
   }
 
@@ -1195,7 +1195,7 @@ public class ThriftAdmin implements Admin {
   }
 
   @Override
-  public void removeRSGroup(Set<Address> servers) {
+  public void removeServersFromRSGroup(Set<Address> servers) {
     throw new NotImplementedException("removeRSGroup not supported in ThriftAdmin");
   }
 
diff --git a/pom.xml b/pom.xml
index 159a7f0..ecaf910 100755
--- a/pom.xml
+++ b/pom.xml
@@ -3629,6 +3629,21 @@
         <surefire.secondPartGroups></surefire.secondPartGroups>
       </properties>
     </profile>
+    <profile>
+      <id>runRSGroupTests</id>
+      <activation>
+        <activeByDefault>false</activeByDefault>
+      </activation>
+      <properties>
+        <surefire.firstPartForkCount>1</surefire.firstPartForkCount>
+        <surefire.secondPartForkCount>1</surefire.secondPartForkCount>
+        <surefire.skipFirstPart>false</surefire.skipFirstPart>
+        <surefire.skipSecondPart>true</surefire.skipSecondPart>
+        <surefire.firstPartGroups>org.apache.hadoop.hbase.testclassification.RSGroupTests
+        </surefire.firstPartGroups>
+        <surefire.secondPartGroups></surefire.secondPartGroups>
+      </properties>
+    </profile>
 
     <profile>
       <!-- Use it to launch tests locally-->


[hbase] 08/08: HBASE-23807 Make rsgroup related shell command to use the new admin methods (#1148)

Posted by zh...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

zhangduo pushed a commit to branch HBASE-22514
in repository https://gitbox.apache.org/repos/asf/hbase.git

commit 660d105febe554fcdfa19848f17e71a52fa22878
Author: Duo Zhang <zh...@apache.org>
AuthorDate: Sun Feb 9 10:33:32 2020 +0800

    HBASE-23807 Make rsgroup related shell command to use the new admin methods (#1148)
    
    Signed-off-by: stack <st...@apache.org>
---
 hbase-shell/src/main/ruby/hbase/rsgroup_admin.rb   | 32 +++++++++++++---------
 .../src/main/ruby/shell/commands/get_rsgroup.rb    |  3 +-
 .../src/main/ruby/shell/commands/list_rsgroups.rb  |  4 +--
 .../hadoop/hbase/client/TestRSGroupShell.java      | 11 ++------
 .../src/test/ruby/shell/rsgroup_shell_test.rb      | 30 ++++++++++----------
 5 files changed, 39 insertions(+), 41 deletions(-)

diff --git a/hbase-shell/src/main/ruby/hbase/rsgroup_admin.rb b/hbase-shell/src/main/ruby/hbase/rsgroup_admin.rb
index 4e32ea4..cdfeec9 100644
--- a/hbase-shell/src/main/ruby/hbase/rsgroup_admin.rb
+++ b/hbase-shell/src/main/ruby/hbase/rsgroup_admin.rb
@@ -27,8 +27,7 @@ module Hbase
 
     def initialize(connection)
       @connection = connection
-      @admin = org.apache.hadoop.hbase.rsgroup.RSGroupAdminClient.new(connection)
-      @hb_admin = @connection.getAdmin
+      @admin = @connection.getAdmin
     end
 
     def close
@@ -44,7 +43,7 @@ module Hbase
     #--------------------------------------------------------------------------
     # get a group's information
     def get_rsgroup(group_name)
-      group = @admin.getRSGroupInfo(group_name)
+      group = @admin.getRSGroup(group_name)
       raise(ArgumentError, 'Group does not exist: ' + group_name) if group.nil?
       group
     end
@@ -74,7 +73,7 @@ module Hbase
       args[0].each do |s|
         servers.add(org.apache.hadoop.hbase.net.Address.fromString(s))
       end
-      @admin.moveServers(servers, dest)
+      @admin.moveServersToRSGroup(servers, dest)
     end
 
     #--------------------------------------------------------------------------
@@ -84,20 +83,20 @@ module Hbase
       args[0].each do |s|
         tables.add(org.apache.hadoop.hbase.TableName.valueOf(s))
       end
-      @admin.moveTables(tables, dest)
+      @admin.setRSGroup(tables, dest)
     end
 
     #--------------------------------------------------------------------------
     # move namespaces to a group
     def move_namespaces(dest, *args)
       tables = get_tables(args[0])
-      @admin.moveTables(tables, dest)
+      @admin.setRSGroup(tables, dest)
     end
 
     #--------------------------------------------------------------------------
     # get group of server
     def get_rsgroup_of_server(server)
-      res = @admin.getRSGroupOfServer(
+      res = @admin.getRSGroup(
         org.apache.hadoop.hbase.net.Address.fromString(server)
       )
       raise(ArgumentError, 'Server has no group: ' + server) if res.nil?
@@ -107,7 +106,7 @@ module Hbase
     #--------------------------------------------------------------------------
     # get group of table
     def get_rsgroup_of_table(table)
-      res = @admin.getRSGroupInfoOfTable(
+      res = @admin.getRSGroup(
         org.apache.hadoop.hbase.TableName.valueOf(table)
       )
       raise(ArgumentError, 'Table has no group: ' + table) if res.nil?
@@ -122,7 +121,8 @@ module Hbase
       args[1].each do |t|
         tables.add(org.apache.hadoop.hbase.TableName.valueOf(t))
       end
-      @admin.moveServersAndTables(servers, tables, dest)
+      @admin.moveServersToRSGroup(servers, dest)
+      @admin.setRSGroup(tables, dest)
     end
 
     #--------------------------------------------------------------------------
@@ -130,7 +130,8 @@ module Hbase
     def move_servers_namespaces(dest, *args)
       servers = get_servers(args[0])
       tables = get_tables(args[1])
-      @admin.moveServersAndTables(servers, tables, dest)
+      @admin.moveServersToRSGroup(servers, dest)
+      @admin.setRSGroup(tables, dest)
     end
 
     def get_servers(servers)
@@ -154,7 +155,7 @@ module Hbase
     # Get tables by namespace
     def get_tables_by_namespace(ns)
       tables = java.util.HashSet.new
-      tablelist = @hb_admin.listTableNamesByNamespace(ns).map(&:getNameAsString)
+      tablelist = @admin.listTableNamesByNamespace(ns).map(&:getNameAsString)
       tablelist.each do |table|
         tables.add(org.apache.hadoop.hbase.TableName.valueOf(table))
       end
@@ -163,7 +164,7 @@ module Hbase
 
     # Does Namespace exist
     def namespace_exists?(ns)
-      return !@hb_admin.getNamespaceDescriptor(ns).nil?
+      return !@admin.getNamespaceDescriptor(ns).nil?
     rescue org.apache.hadoop.hbase.NamespaceNotFoundException
       return false
     end
@@ -177,7 +178,12 @@ module Hbase
       args.each do |s|
         servers.add(org.apache.hadoop.hbase.net.Address.fromString(s))
       end
-      @admin.removeServers(servers)
+      @admin.removeServersFromRSGroup(servers)
+    end
+
+    # get tables in rs group
+    def list_tables_in_rs_group(group_name)
+      @admin.listTablesInRSGroup(group_name)
     end
   end
 end
diff --git a/hbase-shell/src/main/ruby/shell/commands/get_rsgroup.rb b/hbase-shell/src/main/ruby/shell/commands/get_rsgroup.rb
index 8f5b1c1..dc4200a 100644
--- a/hbase-shell/src/main/ruby/shell/commands/get_rsgroup.rb
+++ b/hbase-shell/src/main/ruby/shell/commands/get_rsgroup.rb
@@ -39,7 +39,8 @@ EOF
         formatter.footer
 
         formatter.header(['TABLES'])
-        group.getTables.each do |table|
+        tables = rsgroup_admin.list_tables_in_rs_group(group_name)
+        tables.each do |table|
           formatter.row([table.getNameAsString])
         end
         formatter.footer
diff --git a/hbase-shell/src/main/ruby/shell/commands/list_rsgroups.rb b/hbase-shell/src/main/ruby/shell/commands/list_rsgroups.rb
index dc33b85..a891411 100644
--- a/hbase-shell/src/main/ruby/shell/commands/list_rsgroups.rb
+++ b/hbase-shell/src/main/ruby/shell/commands/list_rsgroups.rb
@@ -54,8 +54,8 @@ EOF
 
             formatter.row([group_name, 'server ' + server.toString])
           end
-
-          group.getTables.each do |table|
+          tables = rsgroup_admin.list_tables_in_rs_group(group.getName)
+          tables.each do |table|
             if group_name_printed
               group_name = ''
             else
diff --git a/hbase-shell/src/test/java/org/apache/hadoop/hbase/client/TestRSGroupShell.java b/hbase-shell/src/test/java/org/apache/hadoop/hbase/client/TestRSGroupShell.java
index 68ec518..17e8d5d 100644
--- a/hbase-shell/src/test/java/org/apache/hadoop/hbase/client/TestRSGroupShell.java
+++ b/hbase-shell/src/test/java/org/apache/hadoop/hbase/client/TestRSGroupShell.java
@@ -19,10 +19,7 @@ package org.apache.hadoop.hbase.client;
 
 import java.io.IOException;
 import org.apache.hadoop.hbase.HBaseClassTestRule;
-import org.apache.hadoop.hbase.HConstants;
-import org.apache.hadoop.hbase.coprocessor.CoprocessorHost;
-import org.apache.hadoop.hbase.rsgroup.RSGroupAdminEndpoint;
-import org.apache.hadoop.hbase.rsgroup.RSGroupBasedLoadBalancer;
+import org.apache.hadoop.hbase.rsgroup.RSGroupInfoManager;
 import org.apache.hadoop.hbase.testclassification.ClientTests;
 import org.apache.hadoop.hbase.testclassification.LargeTests;
 import org.jruby.embed.PathType;
@@ -43,11 +40,7 @@ public class TestRSGroupShell extends AbstractTestShell {
     setUpConfig();
 
     // enable rs group
-    TEST_UTIL.getConfiguration().set(CoprocessorHost.MASTER_COPROCESSOR_CONF_KEY,
-      TEST_UTIL.getConfiguration().get(CoprocessorHost.MASTER_COPROCESSOR_CONF_KEY) + "," +
-        RSGroupAdminEndpoint.class.getName());
-    TEST_UTIL.getConfiguration().set(HConstants.HBASE_MASTER_LOADBALANCER_CLASS,
-      RSGroupBasedLoadBalancer.class.getName());
+    TEST_UTIL.getConfiguration().setBoolean(RSGroupInfoManager.RS_GROUP_ENABLED, true);
 
     TEST_UTIL.startMiniCluster(3);
 
diff --git a/hbase-shell/src/test/ruby/shell/rsgroup_shell_test.rb b/hbase-shell/src/test/ruby/shell/rsgroup_shell_test.rb
index ab7ba0d..3a44a12 100644
--- a/hbase-shell/src/test/ruby/shell/rsgroup_shell_test.rb
+++ b/hbase-shell/src/test/ruby/shell/rsgroup_shell_test.rb
@@ -26,8 +26,7 @@ module Hbase
       @hbase = ::Hbase::Hbase.new($TEST_CLUSTER.getConfiguration)
       @shell = Shell::Shell.new(@hbase)
       connection = $TEST_CLUSTER.getConnection
-      @rsgroup_admin =
-          org.apache.hadoop.hbase.rsgroup.RSGroupAdminClient.new(connection)
+      @admin = connection.getAdmin
     end
 
     define_test 'Test Basic RSGroup Commands' do
@@ -37,37 +36,36 @@ module Hbase
       @shell.command('create', table_name, 'f')
 
       @shell.command('add_rsgroup', group_name)
-      assert_not_nil(@rsgroup_admin.getRSGroupInfo(group_name))
+      assert_not_nil(@admin.getRSGroup(group_name))
 
       @shell.command('remove_rsgroup', group_name)
-      assert_nil(@rsgroup_admin.getRSGroupInfo(group_name))
+      assert_nil(@admin.getRSGroup(group_name))
 
       @shell.command('add_rsgroup', group_name)
-      group = @rsgroup_admin.getRSGroupInfo(group_name)
+      group = @admin.getRSGroup(group_name)
       assert_not_nil(group)
       assert_equal(0, group.getServers.count)
 
-      hostport = @rsgroup_admin.getRSGroupInfo('default').getServers.iterator.next
+      hostport = @admin.getRSGroup('default').getServers.iterator.next
       @shell.command('get_rsgroup', 'default')
       hostPortStr = hostport.toString
       @shell.command('get_server_rsgroup', hostPortStr)
       @shell.command('move_servers_rsgroup',
                      group_name,
                      [hostPortStr])
-      assert_equal(1, @rsgroup_admin.getRSGroupInfo(group_name).getServers.count)
-      assert_equal(group_name, @rsgroup_admin.getRSGroupOfServer(hostport).getName)
+      assert_equal(1, @admin.getRSGroup(group_name).getServers.count)
+      assert_equal(group_name, @admin.getRSGroup(hostport).getName)
 
       @shell.command('move_tables_rsgroup',
                      group_name,
                      [table_name])
-      assert_equal(1, @rsgroup_admin.getRSGroupInfo(group_name).getTables.count)
+      assert_equal(1, @admin.listTablesInRSGroup(group_name).count)
 
       group = @hbase.rsgroup_admin.get_rsgroup(group_name)
       assert_not_nil(group)
       assert_equal(1, group.getServers.count)
-      assert_equal(1, group.getTables.count)
       assert_equal(hostPortStr, group.getServers.iterator.next.toString)
-      assert_equal(table_name, group.getTables.iterator.next.toString)
+      assert_equal(table_name, @admin.listTablesInRSGroup(group_name).iterator.next.toString)
 
       assert_equal(2, @hbase.rsgroup_admin.list_rs_groups.count)
 
@@ -86,11 +84,11 @@ module Hbase
       @shell.command('move_namespaces_rsgroup',
                      group_name,
                      [namespace_name])
-      assert_equal(2, @rsgroup_admin.getRSGroupInfo(group_name).getTables.count)
+      assert_equal(2, @admin.listTablesInRSGroup(group_name).count)
 
       group = @hbase.rsgroup_admin.get_rsgroup(group_name)
       assert_not_nil(group)
-      assert_equal(ns_table_name, group.getTables.iterator.next.toString)
+      assert_true(@admin.listTablesInRSGroup(group_name).contains(org.apache.hadoop.hbase.TableName.valueOf(ns_table_name)))
     end
 
     define_test 'Test RSGroup Move Server Namespace RSGroup Commands' do
@@ -99,13 +97,13 @@ module Hbase
       ns_table_name = 'test_namespace:test_ns_table'
 
       @shell.command('add_rsgroup', ns_group_name)
-      assert_not_nil(@rsgroup_admin.getRSGroupInfo(ns_group_name))
+      assert_not_nil(@admin.getRSGroup(ns_group_name))
 
       @shell.command('move_tables_rsgroup',
                      'default',
                      [ns_table_name])
 
-      group_servers = @rsgroup_admin.getRSGroupInfo('default').getServers
+      group_servers = @admin.getRSGroup('default').getServers
       hostport_str = group_servers.iterator.next.toString
       @shell.command('move_servers_namespaces_rsgroup',
                      ns_group_name,
@@ -114,7 +112,7 @@ module Hbase
       ns_group = @hbase.rsgroup_admin.get_rsgroup(ns_group_name)
       assert_not_nil(ns_group)
       assert_equal(hostport_str, ns_group.getServers.iterator.next.toString)
-      assert_equal(ns_table_name, ns_group.getTables.iterator.next.toString)
+      assert_equal(ns_table_name, @admin.listTablesInRSGroup(ns_group_name).iterator.next.toString)
     end
 
     # we test exceptions that could be thrown by the ruby wrappers