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/03/02 07:43:52 UTC

[hbase] branch master updated (48a3ccf -> 346d087)

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

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


    from 48a3ccf  HBASE-23914 : Set hbase.client.retries.number for TestThriftHBaseServiceHandler (#1227)
     new fbdaa21  HBASE-22664 Move protobuf stuff in hbase-rsgroup to hbase-protocol-shaded (#362)
     new 662149e  HBASE-22662 Move RSGroupInfoManager to hbase-server (#368)
     new f0a13bb  HBASE-22676 Move all the code in hbase-rsgroup to hbase-server and remove hbase-rsgroup module (#399)
     new 3709f33  HBASE-22695 Store the rsgroup of a table in table configuration (#426)
     new 44ddb30  HBASE-22809 Allow creating table in group when rs group contains no live servers (#464)
     new 2e0039c  HBASE-22820 Do not need to persist default rs group now (#482)
     new b3399ef  HBASE-22819 Automatically migrate the rs group config for table after HBASE-22695 (#498)
     new 2bf2781  HBASE-22729 Start RSGroupInfoManager as default (#555)
     new 0e29d62  HBASE-22987 Calculate the region servers in default group in foreground (#599)
     new b10b39a  HBASE-22971 Deprecated RSGroupAdminEndpoint and make RSGroup feature always enabled (#595)
     new 0f4a87c  HBASE-23081 Add an option to enable/disable rs group feature (#691)
     new e12064b  HBASE-23232 Remove rsgroup profile from pom.xml of hbase-assembly (#779)
     new e8e9eec  HBASE-23050 Use RSGroupInfoManager to get rsgroups in master UI's rsgroup part (#776)
     new 72cbb12  HBASE-22932 Add rs group management methods in Admin and AsyncAdmin (#657)
     new c8d892c  HBASE-23253 Rewrite rsgroup related UTs with the new methods introduced in HBASE-22932 (#813)
     new 5de0a5e  HBASE-23235 Re-enable TestRSGroupsKillRS.testLowerMetaGroupVersion (#1117)
     new 7386369  HBASE-23276 Add admin methods to get tables within a group (#1118)
     new 37e87ae  HBASE-23807 Make rsgroup related shell command to use the new admin methods (#1148)
     new 7f2d823  HBASE-23818 Cleanup the remaining RSGroupInfo.getTables call in the code base (#1152)
     new 420e380  HBASE-23890 Update the rsgroup section in our ref guide (#1206)
     new 346d087  HBASE-23911 Attach the new rsgroup implementation design doc to our code base (#1224)

The 21 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:
 ...roup-feature-and-move-it-into-core-of-HBase.pdf |  Bin 0 -> 27888 bytes
 .../{MetricsTests.java => RSGroupTests.java}       |    7 +-
 hbase-assembly/pom.xml                             |   17 -
 hbase-assembly/src/main/assembly/components.xml    |    8 -
 .../src/main/assembly/hadoop-two-compat.xml        |    1 -
 .../org/apache/hadoop/hbase/HTableDescriptor.java  |    6 +
 .../apache/hadoop/hbase/RSGroupTableAccessor.java  |   86 --
 .../java/org/apache/hadoop/hbase/client/Admin.java |  104 +-
 .../hadoop/hbase/client/AdminOverAsyncAdmin.java   |   63 ++
 .../org/apache/hadoop/hbase/client/AsyncAdmin.java |  106 +-
 .../hadoop/hbase/client/AsyncHBaseAdmin.java       |   63 ++
 .../hadoop/hbase/client/RawAsyncHBaseAdmin.java    |  219 +++-
 .../hadoop/hbase/client/TableDescriptor.java       |    8 +
 .../hbase/client/TableDescriptorBuilder.java       |   19 +
 .../apache/hadoop/hbase/protobuf/ProtobufUtil.java |   29 +-
 .../hadoop/hbase/shaded/protobuf/ProtobufUtil.java |   27 +
 .../hbase/shaded/protobuf/RequestConverter.java    |   36 +-
 .../apache/hadoop/hbase/rsgroup/RSGroupInfo.java   |   46 +-
 hbase-it/pom.xml                                   |   10 -
 .../hbase/rsgroup/IntegrationTestRSGroup.java      |   30 +-
 .../src/main/protobuf/Master.proto                 |   34 +
 .../src/main/protobuf/RSGroup.proto                |    3 +-
 .../src/main/protobuf/RSGroupAdmin.proto           |   19 +-
 .../src/main/protobuf/RSGroupAdmin.proto           |    0
 hbase-rsgroup/README.txt                           |   13 -
 hbase-rsgroup/pom.xml                              |  284 -----
 .../apache/hadoop/hbase/rsgroup/RSGroupAdmin.java  |  101 --
 .../hadoop/hbase/rsgroup/RSGroupAdminEndpoint.java |  561 ----------
 .../hadoop/hbase/rsgroup/RSGroupAdminServer.java   |  647 -----------
 .../hbase/rsgroup/RSGroupInfoManagerImpl.java      |  801 -------------
 .../hadoop/hbase/rsgroup/RSGroupProtobufUtil.java  |   63 --
 .../hadoop/hbase/rsgroup/RSGroupableBalancer.java  |   32 -
 .../hbase/rsgroup/VerifyingRSGroupAdminClient.java |  159 ---
 hbase-rsgroup/src/test/resources/hbase-site.xml    |   32 -
 hbase-rsgroup/src/test/resources/log4j.properties  |   68 --
 .../hbase/tmpl/master/MasterStatusTmpl.jamon       |    4 +-
 .../hadoop/hbase/tmpl/master/RSGroupListTmpl.jamon |    6 +-
 .../hadoop/hbase/coprocessor/MasterObserver.java   |   34 +
 .../hbase/favored/FavoredNodeLoadBalancer.java     |    1 +
 .../hadoop/hbase/favored/FavoredNodesPromoter.java |    2 +
 .../org/apache/hadoop/hbase/master/HMaster.java    |   71 +-
 .../apache/hadoop/hbase/master/LoadBalancer.java   |   58 +-
 .../hadoop/hbase/master/MasterCoprocessorHost.java |   48 +-
 .../hadoop/hbase/master/MasterRpcServices.java     |  341 +++++-
 .../apache/hadoop/hbase/master/MasterServices.java |   17 +-
 .../hbase/master/assignment/AssignmentManager.java |   21 +-
 .../master/balancer/FavoredStochasticBalancer.java |    1 +
 .../hbase/master/balancer/LoadBalancerFactory.java |   18 +-
 .../AbstractStateMachineNamespaceProcedure.java    |   11 +
 .../master/procedure/CreateNamespaceProcedure.java |    1 +
 .../master/procedure/CreateTableProcedure.java     |   27 +-
 .../master/procedure/MasterProcedureUtil.java      |   50 +-
 .../master/procedure/ModifyNamespaceProcedure.java |   19 +-
 .../master/procedure/ModifyTableProcedure.java     |   13 +-
 .../hbase/rsgroup/DisabledRSGroupInfoManager.java  |  118 ++
 .../hadoop/hbase/rsgroup/RSGroupAdminClient.java   |  145 ++-
 .../hadoop/hbase/rsgroup/RSGroupAdminEndpoint.java |   61 +
 .../hbase/rsgroup/RSGroupAdminServiceImpl.java     |  398 +++++++
 .../hbase/rsgroup/RSGroupBasedLoadBalancer.java    |  140 +--
 .../hadoop/hbase/rsgroup/RSGroupInfoManager.java   |   76 +-
 .../hbase/rsgroup/RSGroupInfoManagerImpl.java      | 1184 ++++++++++++++++++++
 .../hbase/rsgroup/RSGroupMajorCompactionTTL.java   |   53 +-
 .../apache/hadoop/hbase/rsgroup/RSGroupUtil.java   |  127 +++
 .../hbase/security/access/AccessController.java    |   95 ++
 .../resources/hbase-webapps/master/rsgroup.jsp     |   10 +-
 .../org/apache/hadoop/hbase/TestNamespace.java     |    3 +-
 .../hbase/master/MockNoopMasterServices.java       |   14 +-
 .../hadoop/hbase/master/TestClusterRestart.java    |    6 +-
 .../hadoop/hbase/master/TestRegionPlacement2.java  |    6 +-
 .../balancer/RSGroupableBalancerTestBase.java      |   84 +-
 .../TestFavoredStochasticBalancerPickers.java      |    2 +-
 .../balancer/TestRSGroupBasedLoadBalancer.java     |   42 +-
 ...lancerWithStochasticLoadBalancerAsInternal.java |    4 +-
 .../procedure/TestSCPWithReplicasWithRSGroup.java  |    0
 .../hadoop/hbase/regionserver/TestRegionOpen.java  |    1 -
 .../hbase/rsgroup/EnableRSGroupsTestBase.java      |   28 +-
 .../TestEnableRSGroups.java}                       |   17 +-
 .../TestEnableRSGroupsCompatibility.java}          |   21 +-
 .../hbase/rsgroup/TestMigrateRSGroupInfo.java      |  190 ++++
 .../rsgroup/TestRSGroupMajorCompactionTTL.java     |   16 +-
 .../hadoop/hbase/rsgroup/TestRSGroupsAdmin1.java   |  259 ++---
 .../hadoop/hbase/rsgroup/TestRSGroupsAdmin2.java   |  383 +++----
 .../hadoop/hbase/rsgroup/TestRSGroupsBalance.java  |   57 +-
 .../hadoop/hbase/rsgroup/TestRSGroupsBase.java     |  296 ++---
 .../hadoop/hbase/rsgroup/TestRSGroupsBasics.java   |  132 +--
 .../hbase/rsgroup/TestRSGroupsCPHookCalled.java    |   91 ++
 .../hadoop/hbase/rsgroup/TestRSGroupsKillRS.java   |  104 +-
 .../hbase/rsgroup/TestRSGroupsOfflineMode.java     |   48 +-
 .../hadoop/hbase/rsgroup/TestRSGroupsWithACL.java  |  113 +-
 .../hbase/rsgroup/VerifyingRSGroupAdmin.java       |  897 +++++++++++++++
 .../security/access/TestTablePermissions.java      |    1 +
 hbase-shell/pom.xml                                |   35 -
 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 +-
 .../hadoop/hbase/thrift2/client/ThriftAdmin.java   |   64 ++
 pom.xml                                            |   39 +-
 src/main/asciidoc/_chapters/ops_mgt.adoc           |  152 ++-
 src/main/asciidoc/_chapters/upgrading.adoc         |    4 +
 101 files changed, 5704 insertions(+), 4337 deletions(-)
 create mode 100644 dev-support/design-docs/HBASE-22514-Reimplement-rsgroup-feature-and-move-it-into-core-of-HBase.pdf
 copy hbase-annotations/src/test/java/org/apache/hadoop/hbase/testclassification/{MetricsTests.java => RSGroupTests.java} (91%)
 delete mode 100644 hbase-client/src/main/java/org/apache/hadoop/hbase/RSGroupTableAccessor.java
 copy {hbase-protocol => hbase-protocol-shaded}/src/main/protobuf/RSGroup.proto (93%)
 copy {hbase-rsgroup => hbase-protocol-shaded}/src/main/protobuf/RSGroupAdmin.proto (89%)
 rename {hbase-rsgroup => hbase-protocol}/src/main/protobuf/RSGroupAdmin.proto (100%)
 delete mode 100644 hbase-rsgroup/README.txt
 delete mode 100644 hbase-rsgroup/pom.xml
 delete mode 100644 hbase-rsgroup/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupAdmin.java
 delete mode 100644 hbase-rsgroup/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupAdminEndpoint.java
 delete mode 100644 hbase-rsgroup/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupAdminServer.java
 delete mode 100644 hbase-rsgroup/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupInfoManagerImpl.java
 delete mode 100644 hbase-rsgroup/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupProtobufUtil.java
 delete mode 100644 hbase-rsgroup/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupableBalancer.java
 delete mode 100644 hbase-rsgroup/src/test/java/org/apache/hadoop/hbase/rsgroup/VerifyingRSGroupAdminClient.java
 delete mode 100644 hbase-rsgroup/src/test/resources/hbase-site.xml
 delete mode 100644 hbase-rsgroup/src/test/resources/log4j.properties
 create mode 100644 hbase-server/src/main/java/org/apache/hadoop/hbase/rsgroup/DisabledRSGroupInfoManager.java
 rename {hbase-rsgroup => hbase-server}/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupAdminClient.java (66%)
 create mode 100644 hbase-server/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupAdminEndpoint.java
 create mode 100644 hbase-server/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupAdminServiceImpl.java
 rename {hbase-rsgroup => hbase-server}/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupBasedLoadBalancer.java (80%)
 rename {hbase-rsgroup => hbase-server}/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupInfoManager.java (55%)
 create mode 100644 hbase-server/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupInfoManagerImpl.java
 rename {hbase-rsgroup => hbase-server}/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupMajorCompactionTTL.java (76%)
 create mode 100644 hbase-server/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupUtil.java
 rename {hbase-rsgroup => hbase-server}/src/test/java/org/apache/hadoop/hbase/master/balancer/RSGroupableBalancerTestBase.java (85%)
 rename {hbase-rsgroup => hbase-server}/src/test/java/org/apache/hadoop/hbase/master/balancer/TestRSGroupBasedLoadBalancer.java (86%)
 rename {hbase-rsgroup => hbase-server}/src/test/java/org/apache/hadoop/hbase/master/balancer/TestRSGroupBasedLoadBalancerWithStochasticLoadBalancerAsInternal.java (98%)
 rename {hbase-rsgroup => hbase-server}/src/test/java/org/apache/hadoop/hbase/master/procedure/TestSCPWithReplicasWithRSGroup.java (100%)
 rename hbase-rsgroup/src/test/java/org/apache/hadoop/hbase/rsgroup/TestEnableRSGroups.java => hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/EnableRSGroupsTestBase.java (73%)
 copy hbase-server/src/test/java/org/apache/hadoop/hbase/{client/TestCIDeleteRpcTimeout.java => rsgroup/TestEnableRSGroups.java} (71%)
 copy hbase-server/src/test/java/org/apache/hadoop/hbase/{master/procedure/TestSCPWithoutMetaWithoutZKCoordinated.java => rsgroup/TestEnableRSGroupsCompatibility.java} (61%)
 create mode 100644 hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestMigrateRSGroupInfo.java
 rename {hbase-rsgroup => hbase-server}/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupMajorCompactionTTL.java (91%)
 rename {hbase-rsgroup => hbase-server}/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsAdmin1.java (58%)
 rename {hbase-rsgroup => hbase-server}/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsAdmin2.java (60%)
 rename {hbase-rsgroup => hbase-server}/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsBalance.java (75%)
 rename {hbase-rsgroup => hbase-server}/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsBase.java (57%)
 rename {hbase-rsgroup => hbase-server}/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsBasics.java (64%)
 create mode 100644 hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsCPHookCalled.java
 rename {hbase-rsgroup => hbase-server}/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsKillRS.java (73%)
 rename {hbase-rsgroup => hbase-server}/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsOfflineMode.java (80%)
 rename {hbase-rsgroup => hbase-server}/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsWithACL.java (76%)
 create mode 100644 hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/VerifyingRSGroupAdmin.java


[hbase] 07/21: HBASE-22819 Automatically migrate the rs group config for table after HBASE-22695 (#498)

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

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

commit b3399ef36555f7ae28f488ecd54b13c40c7f53b7
Author: Duo Zhang <zh...@apache.org>
AuthorDate: Sun Aug 25 11:19:04 2019 +0800

    HBASE-22819 Automatically migrate the rs group config for table after HBASE-22695 (#498)
    
    Signed-off-by: Guanghao Zhang <zg...@apache.org>
---
 .../apache/hadoop/hbase/rsgroup/RSGroupInfo.java   |   4 +-
 .../hadoop/hbase/rsgroup/RSGroupAdminServer.java   |   2 +-
 .../hbase/rsgroup/RSGroupAdminServiceImpl.java     |   2 +-
 .../hadoop/hbase/rsgroup/RSGroupInfoManager.java   |  14 +-
 .../hbase/rsgroup/RSGroupInfoManagerImpl.java      | 158 +++++++++++++++---
 .../apache/hadoop/hbase/rsgroup/RSGroupUtil.java   |  39 ++---
 .../hbase/rsgroup/TestMigrateRSGroupInfo.java      | 181 +++++++++++++++++++++
 .../hadoop/hbase/rsgroup/TestRSGroupsBase.java     |   3 +-
 8 files changed, 345 insertions(+), 58 deletions(-)

diff --git a/hbase-common/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupInfo.java b/hbase-common/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupInfo.java
index ad55d1f..817e237 100644
--- a/hbase-common/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupInfo.java
+++ b/hbase-common/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupInfo.java
@@ -19,10 +19,8 @@
 package org.apache.hadoop.hbase.rsgroup;
 
 import java.util.Collection;
-import java.util.Set;
 import java.util.SortedSet;
 import java.util.TreeSet;
-
 import org.apache.hadoop.hbase.TableName;
 import org.apache.hadoop.hbase.net.Address;
 import org.apache.yetus.audience.InterfaceAudience;
@@ -104,7 +102,7 @@ public class RSGroupInfo {
   /**
    * Get list of servers.
    */
-  public Set<Address> getServers() {
+  public SortedSet<Address> getServers() {
     return servers;
   }
 
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
index 947251a..f998d94 100644
--- 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
@@ -61,7 +61,7 @@ public class RSGroupAdminServer implements RSGroupAdmin {
           "one server in 'default' RSGroup.";
 
   private MasterServices master;
-  private final RSGroupInfoManager rsGroupInfoManager;
+  final RSGroupInfoManager rsGroupInfoManager;
 
   /** Define the config key of retries threshold when movements failed */
   //made package private for testing
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 6bc4519..749d353 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
@@ -164,7 +164,7 @@ class RSGroupAdminServiceImpl extends RSGroupAdminProtos.RSGroupAdminService {
       }
       checkPermission("getRSGroupInfoOfTable");
       Optional<RSGroupInfo> optGroup =
-          RSGroupUtil.getRSGroupInfo(master, groupAdminServer, tableName);
+        RSGroupUtil.getRSGroupInfo(master, groupAdminServer.rsGroupInfoManager, tableName);
       if (optGroup.isPresent()) {
         builder.setRSGroupInfo(ProtobufUtil.toProtoGroupInfo(fillTables(optGroup.get())));
       } else {
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 28f7c1f..1b9f3ef 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
@@ -20,6 +20,7 @@ package org.apache.hadoop.hbase.rsgroup;
 import java.io.IOException;
 import java.util.List;
 import java.util.Set;
+import org.apache.hadoop.hbase.TableName;
 import org.apache.hadoop.hbase.net.Address;
 import org.apache.yetus.audience.InterfaceAudience;
 
@@ -68,11 +69,6 @@ public interface RSGroupInfoManager {
   List<RSGroupInfo> listRSGroups() throws IOException;
 
   /**
-   * Refresh/reload the group information from the persistent store
-   */
-  void refresh() throws IOException;
-
-  /**
    * Whether the manager is able to fully return group metadata
    * @return whether the manager is in online mode
    */
@@ -83,4 +79,12 @@ public interface RSGroupInfoManager {
    * @param servers set of servers to remove
    */
   void removeServers(Set<Address> servers) throws IOException;
+
+  /**
+   * 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;
 }
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 eaf23f3..6725066 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
@@ -20,6 +20,7 @@ package org.apache.hadoop.hbase.rsgroup;
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -33,6 +34,7 @@ import org.apache.hadoop.hbase.Coprocessor;
 import org.apache.hadoop.hbase.DoNotRetryIOException;
 import org.apache.hadoop.hbase.NamespaceDescriptor;
 import org.apache.hadoop.hbase.ServerName;
+import org.apache.hadoop.hbase.TableDescriptors;
 import org.apache.hadoop.hbase.TableName;
 import org.apache.hadoop.hbase.client.AsyncClusterConnection;
 import org.apache.hadoop.hbase.client.AsyncTable;
@@ -76,6 +78,7 @@ 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.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;
@@ -104,9 +107,6 @@ import org.apache.hbase.thirdparty.com.google.common.collect.Sets;
 final class RSGroupInfoManagerImpl implements RSGroupInfoManager {
   private static final Logger LOG = LoggerFactory.getLogger(RSGroupInfoManagerImpl.class);
 
-  private static final String REASSIGN_WAIT_INTERVAL_KEY = "hbase.rsgroup.reassign.wait";
-  private static final long DEFAULT_REASSIGN_WAIT_INTERVAL = 30 * 1000L;
-
   // Assigned before user tables
   @VisibleForTesting
   static final TableName RSGROUP_TABLE_NAME =
@@ -120,6 +120,9 @@ final class RSGroupInfoManagerImpl implements RSGroupInfoManager {
   @VisibleForTesting
   static final byte[] META_QUALIFIER_BYTES = Bytes.toBytes("i");
 
+  @VisibleForTesting
+  static final String MIGRATE_THREAD_NAME = "Migrate-RSGroup-Tables";
+
   private static final byte[] ROW_KEY = { 0 };
 
   /** Table descriptor for <code>hbase:rsgroup</code> catalog table */
@@ -140,7 +143,30 @@ final class RSGroupInfoManagerImpl implements RSGroupInfoManager {
 
   // There two Maps are immutable and wholesale replaced on each modification
   // so are safe to access concurrently. See class comment.
-  private volatile Map<String, RSGroupInfo> rsGroupMap = Collections.emptyMap();
+  private static final class RSGroupInfoHolder {
+    final ImmutableMap<String, RSGroupInfo> groupName2Group;
+    final ImmutableMap<TableName, RSGroupInfo> tableName2Group;
+
+    RSGroupInfoHolder() {
+      this(Collections.emptyMap());
+    }
+
+    RSGroupInfoHolder(Map<String, RSGroupInfo> rsGroupMap) {
+      ImmutableMap.Builder<String, RSGroupInfo> group2Name2GroupBuilder = ImmutableMap.builder();
+      ImmutableMap.Builder<TableName, RSGroupInfo> tableName2GroupBuilder = ImmutableMap.builder();
+      rsGroupMap.forEach((groupName, rsGroupInfo) -> {
+        group2Name2GroupBuilder.put(groupName, rsGroupInfo);
+        if (!groupName.equals(RSGroupInfo.DEFAULT_GROUP)) {
+          rsGroupInfo.getTables()
+            .forEach(tableName -> tableName2GroupBuilder.put(tableName, rsGroupInfo));
+        }
+      });
+      this.groupName2Group = group2Name2GroupBuilder.build();
+      this.tableName2Group = tableName2GroupBuilder.build();
+    }
+  }
+
+  private volatile RSGroupInfoHolder holder = new RSGroupInfoHolder();
 
   private final MasterServices masterServices;
   private final AsyncClusterConnection conn;
@@ -160,9 +186,10 @@ final class RSGroupInfoManagerImpl implements RSGroupInfoManager {
 
 
   private synchronized void init() throws IOException {
-    refresh();
+    refresh(false);
     serverEventsListenerThread.start();
     masterServices.getServerManager().registerListener(serverEventsListenerThread);
+    migrate();
   }
 
   static RSGroupInfoManager getInstance(MasterServices master) throws IOException {
@@ -179,6 +206,7 @@ final class RSGroupInfoManagerImpl implements RSGroupInfoManager {
   @Override
   public synchronized void addRSGroup(RSGroupInfo rsGroupInfo) throws IOException {
     checkGroupName(rsGroupInfo.getName());
+    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());
@@ -235,7 +263,7 @@ final class RSGroupInfoManagerImpl implements RSGroupInfoManager {
       }
       dst.addServer(el);
     }
-    Map<String, RSGroupInfo> newGroupMap = Maps.newHashMap(rsGroupMap);
+    Map<String, RSGroupInfo> newGroupMap = Maps.newHashMap(holder.groupName2Group);
     newGroupMap.put(src.getName(), src);
     newGroupMap.put(dst.getName(), dst);
     flushConfig(newGroupMap);
@@ -244,7 +272,7 @@ final class RSGroupInfoManagerImpl implements RSGroupInfoManager {
 
   @Override
   public RSGroupInfo getRSGroupOfServer(Address serverHostPort) throws IOException {
-    for (RSGroupInfo info : rsGroupMap.values()) {
+    for (RSGroupInfo info : holder.groupName2Group.values()) {
       if (info.containsServer(serverHostPort)) {
         return info;
       }
@@ -254,11 +282,12 @@ final class RSGroupInfoManagerImpl implements RSGroupInfoManager {
 
   @Override
   public RSGroupInfo getRSGroup(String groupName) {
-    return rsGroupMap.get(groupName);
+    return holder.groupName2Group.get(groupName);
   }
 
   @Override
   public synchronized void removeRSGroup(String groupName) throws IOException {
+    Map<String, RSGroupInfo> rsGroupMap = holder.groupName2Group;
     if (!rsGroupMap.containsKey(groupName) || groupName.equals(RSGroupInfo.DEFAULT_GROUP)) {
       throw new DoNotRetryIOException(
         "Group " + groupName + " does not exist or is a reserved " + "group");
@@ -270,7 +299,7 @@ final class RSGroupInfoManagerImpl implements RSGroupInfoManager {
 
   @Override
   public List<RSGroupInfo> listRSGroups() {
-    return Lists.newArrayList(rsGroupMap.values());
+    return Lists.newArrayList(holder.groupName2Group.values());
   }
 
   @Override
@@ -298,7 +327,7 @@ final class RSGroupInfoManagerImpl implements RSGroupInfoManager {
     }
 
     if (rsGroupInfos.size() > 0) {
-      Map<String, RSGroupInfo> newGroupMap = Maps.newHashMap(rsGroupMap);
+      Map<String, RSGroupInfo> newGroupMap = Maps.newHashMap(holder.groupName2Group);
       newGroupMap.putAll(rsGroupInfos);
       flushConfig(newGroupMap);
     }
@@ -349,9 +378,90 @@ final class RSGroupInfoManagerImpl implements RSGroupInfoManager {
     return RSGroupInfoList;
   }
 
-  @Override
-  public void refresh() throws IOException {
-    refresh(false);
+  private void migrate(Collection<RSGroupInfo> groupList) {
+    TableDescriptors tds = masterServices.getTableDescriptors();
+    for (RSGroupInfo groupInfo : groupList) {
+      if (groupInfo.getName().equals(RSGroupInfo.DEFAULT_GROUP)) {
+        continue;
+      }
+      SortedSet<TableName> failedTables = new TreeSet<>();
+      for (TableName tableName : groupInfo.getTables()) {
+        LOG.debug("Migrating {} in group {}", tableName, groupInfo.getName());
+        TableDescriptor oldTd;
+        try {
+          oldTd = tds.get(tableName);
+        } catch (IOException e) {
+          LOG.warn("Failed to migrate {} in group {}", tableName, groupInfo.getName(), e);
+          failedTables.add(tableName);
+          continue;
+        }
+        if (oldTd == null) {
+          continue;
+        }
+        if (oldTd.getRegionServerGroup().isPresent()) {
+          // either we have already migrated it or that user has set the rs group using the new
+          // code which will set the group directly on table descriptor, skip.
+          LOG.debug("Skip migrating {} since it is already in group {}", tableName,
+            oldTd.getRegionServerGroup().get());
+          continue;
+        }
+        TableDescriptor newTd = TableDescriptorBuilder.newBuilder(oldTd)
+          .setRegionServerGroup(groupInfo.getName()).build();
+        // This is a bit tricky. Since we know that the region server group config in
+        // TableDescriptor will only be used at master side, it is fine to just update the table
+        // descriptor on file system and also the cache, without reopening all the regions. This
+        // will be much faster than the normal modifyTable. And when upgrading, we will update
+        // master first and then region server, so after all the region servers has been reopened,
+        // the new TableDescriptor will be loaded.
+        try {
+          tds.add(newTd);
+        } catch (IOException e) {
+          LOG.warn("Failed to migrate {} in group {}", tableName, groupInfo.getName(), e);
+          failedTables.add(tableName);
+          continue;
+        }
+      }
+      LOG.debug("Done migrating {}, failed tables {}", groupInfo.getName(), failedTables);
+      synchronized (RSGroupInfoManagerImpl.this) {
+        Map<String, RSGroupInfo> rsGroupMap = holder.groupName2Group;
+        RSGroupInfo currentInfo = rsGroupMap.get(groupInfo.getName());
+        if (currentInfo != null) {
+          RSGroupInfo newInfo =
+            new RSGroupInfo(currentInfo.getName(), currentInfo.getServers(), failedTables);
+          Map<String, RSGroupInfo> newGroupMap = new HashMap<>(rsGroupMap);
+          newGroupMap.put(groupInfo.getName(), newInfo);
+          try {
+            flushConfig(newGroupMap);
+          } catch (IOException e) {
+            LOG.warn("Failed to persist rs group {}", newInfo.getName(), e);
+          }
+        }
+      }
+    }
+  }
+
+  // Migrate the table rs group info from RSGroupInfo into the table descriptor
+  // Notice that we do not want to block the initialize so this will be done in background, and
+  // during the migrating, the rs group info maybe incomplete and cause region to be misplaced.
+  private void migrate() {
+    Thread migrateThread = new Thread(MIGRATE_THREAD_NAME) {
+
+      @Override
+      public void run() {
+        LOG.info("Start migrating table rs group config");
+        while (!masterServices.isStopped()) {
+          Collection<RSGroupInfo> groups = holder.groupName2Group.values();
+          boolean hasTables = groups.stream().anyMatch(r -> !r.getTables().isEmpty());
+          if (!hasTables) {
+            break;
+          }
+          migrate(groups);
+        }
+        LOG.info("Done migrating table rs group info");
+      }
+    };
+    migrateThread.setDaemon(true);
+    migrateThread.start();
   }
 
   /**
@@ -381,7 +491,7 @@ final class RSGroupInfoManagerImpl implements RSGroupInfoManager {
       newGroupMap.put(group.getName(), group);
     }
     resetRSGroupMap(newGroupMap);
-    updateCacheOfRSGroups(rsGroupMap.keySet());
+    updateCacheOfRSGroups(newGroupMap.keySet());
   }
 
   private void flushConfigTable(Map<String, RSGroupInfo> groupMap) throws IOException {
@@ -411,20 +521,20 @@ final class RSGroupInfoManagerImpl implements RSGroupInfoManager {
   }
 
   private synchronized void flushConfig() throws IOException {
-    flushConfig(this.rsGroupMap);
+    flushConfig(holder.groupName2Group);
   }
 
   private synchronized void flushConfig(Map<String, RSGroupInfo> newGroupMap) throws IOException {
     // For offline mode persistence is still unavailable
     // We're refreshing in-memory state but only for servers in default group
     if (!isOnline()) {
-      if (newGroupMap == this.rsGroupMap) {
+      if (newGroupMap == holder.groupName2Group) {
         // When newGroupMap is this.rsGroupMap itself,
         // do not need to check default group and other groups as followed
         return;
       }
 
-      Map<String, RSGroupInfo> oldGroupMap = Maps.newHashMap(rsGroupMap);
+      Map<String, RSGroupInfo> oldGroupMap = Maps.newHashMap(holder.groupName2Group);
       RSGroupInfo oldDefaultGroup = oldGroupMap.remove(RSGroupInfo.DEFAULT_GROUP);
       RSGroupInfo newDefaultGroup = newGroupMap.remove(RSGroupInfo.DEFAULT_GROUP);
       if (!oldGroupMap.equals(newGroupMap) /* compare both tables and servers in other groups */ ||
@@ -438,7 +548,7 @@ final class RSGroupInfoManagerImpl implements RSGroupInfoManager {
 
       // Refresh rsGroupMap
       // according to the inputted newGroupMap (an updated copy of rsGroupMap)
-      rsGroupMap = newGroupMap;
+      this.holder = new RSGroupInfoHolder(newGroupMap);
 
       // Do not need to update tableMap
       // because only the update on servers in default group is allowed above,
@@ -495,8 +605,7 @@ final class RSGroupInfoManagerImpl implements RSGroupInfoManager {
    * Make changes visible. Caller must be synchronized on 'this'.
    */
   private void resetRSGroupMap(Map<String, RSGroupInfo> newRSGroupMap) {
-    // Make maps Immutable.
-    this.rsGroupMap = Collections.unmodifiableMap(newRSGroupMap);
+    this.holder = new RSGroupInfoHolder(newRSGroupMap);
   }
 
   /**
@@ -549,6 +658,7 @@ final class RSGroupInfoManagerImpl implements RSGroupInfoManager {
   // Called by ServerEventsListenerThread. Synchronize on this because redoing
   // the rsGroupMap then writing it out.
   private synchronized void updateDefaultServers(SortedSet<Address> servers) {
+    Map<String, RSGroupInfo> rsGroupMap = holder.groupName2Group;
     RSGroupInfo info = rsGroupMap.get(RSGroupInfo.DEFAULT_GROUP);
     RSGroupInfo newInfo = new RSGroupInfo(info.getName(), servers);
     HashMap<String, RSGroupInfo> newGroupMap = Maps.newHashMap(rsGroupMap);
@@ -647,6 +757,8 @@ final class RSGroupInfoManagerImpl implements RSGroupInfoManager {
           online = true;
           // flush any inconsistencies between ZK and HTable
           RSGroupInfoManagerImpl.this.flushConfig();
+          // migrate after we are online.
+          migrate();
           return true;
         } catch (Exception e) {
           LOG.warn("Failed to perform check", e);
@@ -725,4 +837,10 @@ final class RSGroupInfoManagerImpl implements RSGroupInfoManager {
       throw new ConstraintException("RSGroup name should only contain alphanumeric characters");
     }
   }
+
+
+  @Override
+  public RSGroupInfo getRSGroupForTable(TableName tableName) throws IOException {
+    return holder.tableName2Group.get(tableName);
+  }
 }
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 a08d236..af30049 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
@@ -34,12 +34,11 @@ final class RSGroupUtil {
   private RSGroupUtil() {
   }
 
-  @FunctionalInterface
-  private interface GetRSGroup {
-    RSGroupInfo get(String groupName) throws IOException;
-  }
-
-  private static Optional<RSGroupInfo> getRSGroupInfo(MasterServices master, GetRSGroup getter,
+  /**
+   * 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 {
     TableDescriptor td = master.getTableDescriptors().get(tableName);
     if (td == null) {
@@ -47,11 +46,17 @@ final class RSGroupUtil {
     }
     Optional<String> optGroupNameOfTable = td.getRegionServerGroup();
     if (optGroupNameOfTable.isPresent()) {
-      RSGroupInfo group = getter.get(optGroupNameOfTable.get());
+      RSGroupInfo group = manager.getRSGroup(optGroupNameOfTable.get());
       if (group != null) {
         return Optional.of(group);
       }
     }
+    // for backward compatible, where we may still have table configs in the RSGroupInfo after
+    // upgrading when migrating is still on-going.
+    RSGroupInfo groupFromOldRSGroupInfo = manager.getRSGroupForTable(tableName);
+    if (groupFromOldRSGroupInfo != null) {
+      return Optional.of(groupFromOldRSGroupInfo);
+    }
     ClusterSchema clusterSchema = master.getClusterSchema();
     if (clusterSchema == null) {
       if (TableName.isMetaTableName(tableName)) {
@@ -67,25 +72,7 @@ final class RSGroupUtil {
     if (groupNameOfNs == null) {
       return Optional.empty();
     }
-    return Optional.ofNullable(getter.get(groupNameOfNs));
-  }
-
-  /**
-   * 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 {
-    return getRSGroupInfo(master, manager::getRSGroup, tableName);
-  }
-
-  /**
-   * 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, RSGroupAdmin admin,
-      TableName tableName) throws IOException {
-    return getRSGroupInfo(master, admin::getRSGroupInfo, tableName);
+    return Optional.ofNullable(manager.getRSGroup(groupNameOfNs));
   }
 
   /**
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
new file mode 100644
index 0000000..8d63cf8
--- /dev/null
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestMigrateRSGroupInfo.java
@@ -0,0 +1,181 @@
+/**
+ * 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.apache.hadoop.hbase.rsgroup.RSGroupInfoManagerImpl.META_FAMILY_BYTES;
+import static org.apache.hadoop.hbase.rsgroup.RSGroupInfoManagerImpl.META_QUALIFIER_BYTES;
+import static org.apache.hadoop.hbase.rsgroup.RSGroupInfoManagerImpl.RSGROUP_TABLE_NAME;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.io.IOException;
+import java.util.concurrent.CountDownLatch;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.hbase.HBaseClassTestRule;
+import org.apache.hadoop.hbase.HConstants;
+import org.apache.hadoop.hbase.TableDescriptors;
+import org.apache.hadoop.hbase.TableName;
+import org.apache.hadoop.hbase.client.Get;
+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.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.util.Bytes;
+import org.apache.zookeeper.KeeperException;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+/**
+ * Testcase for HBASE-22819
+ */
+@Category({ MediumTests.class })
+public class TestMigrateRSGroupInfo extends TestRSGroupsBase {
+
+  @ClassRule
+  public static final HBaseClassTestRule CLASS_RULE =
+    HBaseClassTestRule.forClass(TestMigrateRSGroupInfo.class);
+
+  private static String TABLE_NAME_PREFIX = "Table_";
+
+  private static int NUM_TABLES = 10;
+
+  private static byte[] FAMILY = Bytes.toBytes("family");
+
+  @BeforeClass
+  public static void setUp() throws Exception {
+    TEST_UTIL.getConfiguration().setClass(HConstants.MASTER_IMPL, HMasterForTest.class,
+      HMaster.class);
+    setUpTestBeforeClass();
+    for (int i = 0; i < NUM_TABLES; i++) {
+      TEST_UTIL.createTable(TableName.valueOf(TABLE_NAME_PREFIX + i), FAMILY);
+    }
+  }
+
+  @AfterClass
+  public static void tearDown() throws Exception {
+    tearDownAfterClass();
+  }
+
+  private static CountDownLatch RESUME = new CountDownLatch(1);
+
+  public static final class HMasterForTest extends HMaster {
+
+    public HMasterForTest(Configuration conf) throws IOException, KeeperException {
+      super(conf);
+    }
+
+    @Override
+    public TableDescriptors getTableDescriptors() {
+      if (RESUME != null) {
+        for (StackTraceElement element : Thread.currentThread().getStackTrace()) {
+          if (element.getClassName().contains("RSGroupInfoManagerImpl")) {
+            try {
+              RESUME.await();
+            } catch (InterruptedException e) {
+            }
+            RESUME = null;
+            break;
+          }
+        }
+      }
+      return super.getTableDescriptors();
+    }
+  }
+
+  @Test
+  public void testMigrate() throws IOException, InterruptedException {
+    String groupName = name.getMethodName();
+    addGroup(groupName, TEST_UTIL.getMiniHBaseCluster().getRegionServerThreads().size() - 1);
+    RSGroupInfo rsGroupInfo = rsGroupAdmin.getRSGroupInfo(groupName);
+    assertTrue(rsGroupInfo.getTables().isEmpty());
+    for (int i = 0; i < NUM_TABLES; i++) {
+      rsGroupInfo.addTable(TableName.valueOf(TABLE_NAME_PREFIX + i));
+    }
+    try (Table table = TEST_UTIL.getConnection().getTable(RSGROUP_TABLE_NAME)) {
+      RSGroupProtos.RSGroupInfo proto = ProtobufUtil.toProtoGroupInfo(rsGroupInfo);
+      Put p = new Put(Bytes.toBytes(rsGroupInfo.getName()));
+      p.addColumn(META_FAMILY_BYTES, META_QUALIFIER_BYTES, proto.toByteArray());
+      table.put(p);
+    }
+    TEST_UTIL.getMiniHBaseCluster().stopMaster(0).join();
+    RESUME = new CountDownLatch(1);
+    TEST_UTIL.getMiniHBaseCluster().startMaster();
+    TEST_UTIL.invalidateConnection();
+    RS_GROUP_ADMIN_CLIENT = new RSGroupAdminClient(TEST_UTIL.getConnection());
+
+    // 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));
+        return true;
+      } catch (IOException e) {
+        return false;
+      }
+    });
+    // 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));
+      assertEquals(rsGroupInfo.getName(), info.getName());
+      assertEquals(NUM_TABLES, info.getTables().size());
+    }
+    RESUME.countDown();
+    TEST_UTIL.waitFor(60000, () -> {
+      for (int i = 0; i < NUM_TABLES; i++) {
+        TableDescriptor td;
+        try {
+          td = TEST_UTIL.getAdmin().getDescriptor(TableName.valueOf(TABLE_NAME_PREFIX + i));
+        } catch (IOException e) {
+          return false;
+        }
+        if (!rsGroupInfo.getName().equals(td.getRegionServerGroup().orElse(null))) {
+          return false;
+        }
+      }
+      return true;
+    });
+    // make sure that we persist the result to hbase, where we delete all the tables in the rs
+    // group.
+    TEST_UTIL.waitFor(30000, () -> {
+      try (Table table = TEST_UTIL.getConnection().getTable(RSGROUP_TABLE_NAME)) {
+        Result result = table.get(new Get(Bytes.toBytes(rsGroupInfo.getName())));
+        RSGroupProtos.RSGroupInfo proto = RSGroupProtos.RSGroupInfo
+          .parseFrom(result.getValue(META_FAMILY_BYTES, META_QUALIFIER_BYTES));
+        RSGroupInfo gi = ProtobufUtil.toGroupInfo(proto);
+        return gi.getTables().isEmpty();
+      }
+    });
+    // make sure that the migrate thread has quit.
+    TEST_UTIL.waitFor(30000, () -> Thread.getAllStackTraces().keySet().stream()
+      .noneMatch(t -> t.getName().equals(RSGroupInfoManagerImpl.MIGRATE_THREAD_NAME)));
+    // 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));
+      assertEquals(rsGroupInfo.getName(), info.getName());
+      assertEquals(NUM_TABLES, info.getTables().size());
+    }
+  }
+}
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 b91cd5e..b9885be 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
@@ -71,7 +71,7 @@ public abstract class TestRSGroupsBase {
   protected final static Random rand = new Random();
 
   //shared, cluster type specific
-  protected static HBaseTestingUtility TEST_UTIL;
+  protected static HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
   protected static Admin admin;
   protected static HBaseCluster cluster;
   protected static RSGroupAdminClient rsGroupAdmin;
@@ -90,7 +90,6 @@ public abstract class TestRSGroupsBase {
   protected TableName tableName;
 
   public static void setUpTestBeforeClass() throws Exception {
-    TEST_UTIL = new HBaseTestingUtility();
     TEST_UTIL.getConfiguration().setFloat(
             "hbase.master.balancer.stochastic.tableSkewCost", 6000);
     TEST_UTIL.getConfiguration().set(


[hbase] 01/21: HBASE-22664 Move protobuf stuff in hbase-rsgroup to hbase-protocol-shaded (#362)

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

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

commit fbdaa2113d1024b194ace88a1e4c65687f2b835d
Author: Duo Zhang <zh...@apache.org>
AuthorDate: Tue Jul 9 09:51:19 2019 +0800

    HBASE-22664 Move protobuf stuff in hbase-rsgroup to hbase-protocol-shaded (#362)
    
    Signed-off-by: Guanghao Zhang <zg...@apache.org>
---
 .../apache/hadoop/hbase/protobuf/ProtobufUtil.java | 29 +++++++---
 .../hadoop/hbase/shaded/protobuf/ProtobufUtil.java | 27 ++++++++++
 .../src/main/protobuf/RSGroup.proto                | 33 ++++++++++++
 .../src/main/protobuf/RSGroupAdmin.proto           |  2 +-
 .../src/main/protobuf/RSGroupAdmin.proto           |  0
 hbase-rsgroup/pom.xml                              | 18 -------
 .../hadoop/hbase/rsgroup/RSGroupAdminClient.java   | 10 ++--
 .../hadoop/hbase/rsgroup/RSGroupAdminEndpoint.java | 10 ++--
 .../hbase/rsgroup/RSGroupInfoManagerImpl.java      |  8 +--
 .../hadoop/hbase/rsgroup/RSGroupProtobufUtil.java  | 63 ----------------------
 .../hbase/rsgroup/VerifyingRSGroupAdminClient.java |  5 +-
 11 files changed, 96 insertions(+), 109 deletions(-)

diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/protobuf/ProtobufUtil.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/protobuf/ProtobufUtil.java
index d270f8c..8f9f4a8 100644
--- a/hbase-client/src/main/java/org/apache/hadoop/hbase/protobuf/ProtobufUtil.java
+++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/protobuf/ProtobufUtil.java
@@ -1772,23 +1772,36 @@ public final class ProtobufUtil {
     return ServerName.valueOf(hostname, port, -1L);
   }
 
+  public static HBaseProtos.TimeRange toTimeRange(TimeRange timeRange) {
+    if (timeRange == null) {
+      timeRange = TimeRange.allTime();
+    }
+    return HBaseProtos.TimeRange.newBuilder().setFrom(timeRange.getMin()).setTo(timeRange.getMax())
+        .build();
+  }
+
   public static RSGroupInfo toGroupInfo(RSGroupProtos.RSGroupInfo proto) {
     RSGroupInfo RSGroupInfo = new RSGroupInfo(proto.getName());
-    for(HBaseProtos.ServerName el: proto.getServersList()) {
+    for (HBaseProtos.ServerName el : proto.getServersList()) {
       RSGroupInfo.addServer(Address.fromParts(el.getHostName(), el.getPort()));
     }
-    for(HBaseProtos.TableName pTableName: proto.getTablesList()) {
+    for (HBaseProtos.TableName pTableName : proto.getTablesList()) {
       RSGroupInfo.addTable(ProtobufUtil.toTableName(pTableName));
     }
     return RSGroupInfo;
   }
 
-  public static HBaseProtos.TimeRange toTimeRange(TimeRange timeRange) {
-    if (timeRange == null) {
-      timeRange = TimeRange.allTime();
+  public static RSGroupProtos.RSGroupInfo toProtoGroupInfo(RSGroupInfo pojo) {
+    List<HBaseProtos.TableName> tables = new ArrayList<>(pojo.getTables().size());
+    for (TableName arg : pojo.getTables()) {
+      tables.add(ProtobufUtil.toProtoTableName(arg));
+    }
+    List<HBaseProtos.ServerName> hostports = new ArrayList<>(pojo.getServers().size());
+    for (Address el : pojo.getServers()) {
+      hostports.add(HBaseProtos.ServerName.newBuilder().setHostName(el.getHostname())
+          .setPort(el.getPort()).build());
     }
-    return HBaseProtos.TimeRange.newBuilder().setFrom(timeRange.getMin())
-      .setTo(timeRange.getMax())
-      .build();
+    return RSGroupProtos.RSGroupInfo.newBuilder().setName(pojo.getName()).addAllServers(hostports)
+        .addAllTables(tables).build();
   }
 }
diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/shaded/protobuf/ProtobufUtil.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/shaded/protobuf/ProtobufUtil.java
index 6d76d95..a0e2cda 100644
--- a/hbase-client/src/main/java/org/apache/hadoop/hbase/shaded/protobuf/ProtobufUtil.java
+++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/shaded/protobuf/ProtobufUtil.java
@@ -98,6 +98,7 @@ import org.apache.hadoop.hbase.filter.ByteArrayComparable;
 import org.apache.hadoop.hbase.filter.Filter;
 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.protobuf.ProtobufMagic;
 import org.apache.hadoop.hbase.protobuf.ProtobufMessageConverter;
 import org.apache.hadoop.hbase.quotas.QuotaScope;
@@ -106,6 +107,7 @@ import org.apache.hadoop.hbase.quotas.SpaceViolationPolicy;
 import org.apache.hadoop.hbase.quotas.ThrottleType;
 import org.apache.hadoop.hbase.replication.ReplicationLoadSink;
 import org.apache.hadoop.hbase.replication.ReplicationLoadSource;
+import org.apache.hadoop.hbase.rsgroup.RSGroupInfo;
 import org.apache.hadoop.hbase.security.visibility.Authorizations;
 import org.apache.hadoop.hbase.security.visibility.CellVisibility;
 import org.apache.hadoop.hbase.util.Addressing;
@@ -188,6 +190,7 @@ import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.ListTableD
 import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.MajorCompactionTimestampResponse;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.ProcedureProtos;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.QuotaProtos;
+import org.apache.hadoop.hbase.shaded.protobuf.generated.RSGroupProtos;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.RegionServerStatusProtos.RegionServerReportRequest;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.RegionServerStatusProtos.RegionServerStartupRequest;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.SnapshotProtos;
@@ -3523,4 +3526,28 @@ public final class ProtobufUtil {
     return clearSlowLogResponses.getIsCleaned();
   }
 
+  public static RSGroupInfo toGroupInfo(RSGroupProtos.RSGroupInfo proto) {
+    RSGroupInfo RSGroupInfo = new RSGroupInfo(proto.getName());
+    for (HBaseProtos.ServerName el : proto.getServersList()) {
+      RSGroupInfo.addServer(Address.fromParts(el.getHostName(), el.getPort()));
+    }
+    for (HBaseProtos.TableName pTableName : proto.getTablesList()) {
+      RSGroupInfo.addTable(ProtobufUtil.toTableName(pTableName));
+    }
+    return RSGroupInfo;
+  }
+
+  public static RSGroupProtos.RSGroupInfo toProtoGroupInfo(RSGroupInfo pojo) {
+    List<HBaseProtos.TableName> tables = new ArrayList<>(pojo.getTables().size());
+    for (TableName arg : pojo.getTables()) {
+      tables.add(ProtobufUtil.toProtoTableName(arg));
+    }
+    List<HBaseProtos.ServerName> hostports = new ArrayList<>(pojo.getServers().size());
+    for (Address el : pojo.getServers()) {
+      hostports.add(HBaseProtos.ServerName.newBuilder().setHostName(el.getHostname())
+          .setPort(el.getPort()).build());
+    }
+    return RSGroupProtos.RSGroupInfo.newBuilder().setName(pojo.getName()).addAllServers(hostports)
+        .addAllTables(tables).build();
+  }
 }
diff --git a/hbase-protocol-shaded/src/main/protobuf/RSGroup.proto b/hbase-protocol-shaded/src/main/protobuf/RSGroup.proto
new file mode 100644
index 0000000..ede2b13
--- /dev/null
+++ b/hbase-protocol-shaded/src/main/protobuf/RSGroup.proto
@@ -0,0 +1,33 @@
+/**
+ * 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 hbase.pb;
+
+option java_package = "org.apache.hadoop.hbase.shaded.protobuf.generated";
+option java_outer_classname = "RSGroupProtos";
+option java_generic_services = true;
+option java_generate_equals_and_hash = true;
+option optimize_for = SPEED;
+
+import "HBase.proto";
+
+message RSGroupInfo {
+  required string name = 1;
+  repeated ServerName servers = 4;
+  repeated TableName tables = 3;
+}
diff --git a/hbase-rsgroup/src/main/protobuf/RSGroupAdmin.proto b/hbase-protocol-shaded/src/main/protobuf/RSGroupAdmin.proto
similarity index 98%
copy from hbase-rsgroup/src/main/protobuf/RSGroupAdmin.proto
copy to hbase-protocol-shaded/src/main/protobuf/RSGroupAdmin.proto
index 416097b..1db7136 100644
--- a/hbase-rsgroup/src/main/protobuf/RSGroupAdmin.proto
+++ b/hbase-protocol-shaded/src/main/protobuf/RSGroupAdmin.proto
@@ -18,7 +18,7 @@
 
 package hbase.pb;
 
-option java_package = "org.apache.hadoop.hbase.protobuf.generated";
+option java_package = "org.apache.hadoop.hbase.shaded.protobuf.generated";
 option java_outer_classname = "RSGroupAdminProtos";
 option java_generic_services = true;
 option java_generate_equals_and_hash = true;
diff --git a/hbase-rsgroup/src/main/protobuf/RSGroupAdmin.proto b/hbase-protocol/src/main/protobuf/RSGroupAdmin.proto
similarity index 100%
rename from hbase-rsgroup/src/main/protobuf/RSGroupAdmin.proto
rename to hbase-protocol/src/main/protobuf/RSGroupAdmin.proto
diff --git a/hbase-rsgroup/pom.xml b/hbase-rsgroup/pom.xml
index 0bef660..09c8e73 100644
--- a/hbase-rsgroup/pom.xml
+++ b/hbase-rsgroup/pom.xml
@@ -44,24 +44,6 @@
         <artifactId>maven-source-plugin</artifactId>
       </plugin>
       <plugin>
-        <groupId>org.xolstice.maven.plugins</groupId>
-        <artifactId>protobuf-maven-plugin</artifactId>
-        <executions>
-          <execution>
-            <id>compile-protoc</id>
-            <phase>generate-sources</phase>
-            <goals>
-              <goal>compile</goal>
-            </goals>
-            <configuration>
-              <additionalProtoPathElements>
-                <additionalProtoPathElement>${basedir}/../hbase-protocol/src/main/protobuf</additionalProtoPathElement>
-              </additionalProtoPathElements>
-            </configuration>
-          </execution>
-        </executions>
-      </plugin>
-      <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-checkstyle-plugin</artifactId>
         <configuration>
diff --git a/hbase-rsgroup/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupAdminClient.java b/hbase-rsgroup/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupAdminClient.java
index e8a1410..e7ab7f2 100644
--- a/hbase-rsgroup/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupAdminClient.java
+++ b/hbase-rsgroup/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupAdminClient.java
@@ -18,12 +18,10 @@
 package org.apache.hadoop.hbase.rsgroup;
 
 import com.google.protobuf.ServiceException;
-
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Set;
-
 import org.apache.hadoop.hbase.TableName;
 import org.apache.hadoop.hbase.TableNotFoundException;
 import org.apache.hadoop.hbase.client.Admin;
@@ -70,7 +68,7 @@ public class RSGroupAdminClient implements RSGroupAdmin {
       GetRSGroupInfoResponse resp = stub.getRSGroupInfo(null,
           GetRSGroupInfoRequest.newBuilder().setRSGroupName(groupName).build());
       if(resp.hasRSGroupInfo()) {
-        return RSGroupProtobufUtil.toGroupInfo(resp.getRSGroupInfo());
+        return ProtobufUtil.toGroupInfo(resp.getRSGroupInfo());
       }
       return null;
     } catch (ServiceException e) {
@@ -85,7 +83,7 @@ public class RSGroupAdminClient implements RSGroupAdmin {
     try {
       GetRSGroupInfoOfTableResponse resp = stub.getRSGroupInfoOfTable(null, request);
       if (resp.hasRSGroupInfo()) {
-        return RSGroupProtobufUtil.toGroupInfo(resp.getRSGroupInfo());
+        return ProtobufUtil.toGroupInfo(resp.getRSGroupInfo());
       }
       return null;
     } catch (ServiceException e) {
@@ -167,7 +165,7 @@ public class RSGroupAdminClient implements RSGroupAdmin {
           ListRSGroupInfosRequest.getDefaultInstance()).getRSGroupInfoList();
       List<RSGroupInfo> result = new ArrayList<>(resp.size());
       for(RSGroupProtos.RSGroupInfo entry : resp) {
-        result.add(RSGroupProtobufUtil.toGroupInfo(entry));
+        result.add(ProtobufUtil.toGroupInfo(entry));
       }
       return result;
     } catch (ServiceException e) {
@@ -186,7 +184,7 @@ public class RSGroupAdminClient implements RSGroupAdmin {
     try {
       GetRSGroupInfoOfServerResponse resp = stub.getRSGroupInfoOfServer(null, request);
       if (resp.hasRSGroupInfo()) {
-        return RSGroupProtobufUtil.toGroupInfo(resp.getRSGroupInfo());
+        return ProtobufUtil.toGroupInfo(resp.getRSGroupInfo());
       }
       return null;
     } catch (ServiceException e) {
diff --git a/hbase-rsgroup/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupAdminEndpoint.java b/hbase-rsgroup/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupAdminEndpoint.java
index 090ac6e..8c6e010 100644
--- a/hbase-rsgroup/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupAdminEndpoint.java
+++ b/hbase-rsgroup/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupAdminEndpoint.java
@@ -21,7 +21,6 @@ package org.apache.hadoop.hbase.rsgroup;
 import com.google.protobuf.RpcCallback;
 import com.google.protobuf.RpcController;
 import com.google.protobuf.Service;
-
 import java.io.IOException;
 import java.util.Collections;
 import java.util.HashSet;
@@ -29,7 +28,6 @@ import java.util.List;
 import java.util.Optional;
 import java.util.Set;
 import java.util.stream.Collectors;
-
 import org.apache.hadoop.hbase.CoprocessorEnvironment;
 import org.apache.hadoop.hbase.HBaseIOException;
 import org.apache.hadoop.hbase.HConstants;
@@ -164,7 +162,7 @@ public class RSGroupAdminEndpoint implements MasterCoprocessor, MasterObserver {
         checkPermission("getRSGroupInfo");
         RSGroupInfo rsGroupInfo = groupAdminServer.getRSGroupInfo(groupName);
         if (rsGroupInfo != null) {
-          builder.setRSGroupInfo(RSGroupProtobufUtil.toProtoGroupInfo(rsGroupInfo));
+          builder.setRSGroupInfo(ProtobufUtil.toProtoGroupInfo(rsGroupInfo));
         }
         if (master.getMasterCoprocessorHost() != null) {
           master.getMasterCoprocessorHost().postGetRSGroupInfo(groupName);
@@ -189,7 +187,7 @@ public class RSGroupAdminEndpoint implements MasterCoprocessor, MasterObserver {
         checkPermission("getRSGroupInfoOfTable");
         RSGroupInfo RSGroupInfo = groupAdminServer.getRSGroupInfoOfTable(tableName);
         if (RSGroupInfo != null) {
-          builder.setRSGroupInfo(RSGroupProtobufUtil.toProtoGroupInfo(RSGroupInfo));
+          builder.setRSGroupInfo(ProtobufUtil.toProtoGroupInfo(RSGroupInfo));
         }
         if (master.getMasterCoprocessorHost() != null) {
           master.getMasterCoprocessorHost().postGetRSGroupInfoOfTable(tableName);
@@ -326,7 +324,7 @@ public class RSGroupAdminEndpoint implements MasterCoprocessor, MasterObserver {
         }
         checkPermission("listRSGroup");
         for (RSGroupInfo RSGroupInfo : groupAdminServer.listRSGroups()) {
-          builder.addRSGroupInfo(RSGroupProtobufUtil.toProtoGroupInfo(RSGroupInfo));
+          builder.addRSGroupInfo(ProtobufUtil.toProtoGroupInfo(RSGroupInfo));
         }
         if (master.getMasterCoprocessorHost() != null) {
           master.getMasterCoprocessorHost().postListRSGroups();
@@ -352,7 +350,7 @@ public class RSGroupAdminEndpoint implements MasterCoprocessor, MasterObserver {
         checkPermission("getRSGroupInfoOfServer");
         RSGroupInfo info = groupAdminServer.getRSGroupOfServer(hp);
         if (info != null) {
-          builder.setRSGroupInfo(RSGroupProtobufUtil.toProtoGroupInfo(info));
+          builder.setRSGroupInfo(ProtobufUtil.toProtoGroupInfo(info));
         }
         if (master.getMasterCoprocessorHost() != null) {
           master.getMasterCoprocessorHost().postGetRSGroupInfoOfServer(hp);
diff --git a/hbase-rsgroup/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupInfoManagerImpl.java b/hbase-rsgroup/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupInfoManagerImpl.java
index b2d168a..b54f088 100644
--- a/hbase-rsgroup/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupInfoManagerImpl.java
+++ b/hbase-rsgroup/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupInfoManagerImpl.java
@@ -360,7 +360,7 @@ final class RSGroupInfoManagerImpl implements RSGroupInfoManager {
         }
         RSGroupProtos.RSGroupInfo proto = RSGroupProtos.RSGroupInfo
           .parseFrom(result.getValue(META_FAMILY_BYTES, META_QUALIFIER_BYTES));
-        rsGroupInfoList.add(RSGroupProtobufUtil.toGroupInfo(proto));
+        rsGroupInfoList.add(ProtobufUtil.toGroupInfo(proto));
       }
     }
     return rsGroupInfoList;
@@ -383,7 +383,7 @@ final class RSGroupInfoManagerImpl implements RSGroupInfoManager {
             ByteArrayInputStream bis =
               new ByteArrayInputStream(data, ProtobufUtil.lengthOfPBMagic(), data.length);
             RSGroupInfoList
-              .add(RSGroupProtobufUtil.toGroupInfo(RSGroupProtos.RSGroupInfo.parseFrom(bis)));
+              .add(ProtobufUtil.toGroupInfo(RSGroupProtos.RSGroupInfo.parseFrom(bis)));
           }
         }
         LOG.debug("Read ZK GroupInfo count:" + RSGroupInfoList.size());
@@ -459,7 +459,7 @@ final class RSGroupInfoManagerImpl implements RSGroupInfoManager {
 
     // populate puts
     for (RSGroupInfo RSGroupInfo : groupMap.values()) {
-      RSGroupProtos.RSGroupInfo proto = RSGroupProtobufUtil.toProtoGroupInfo(RSGroupInfo);
+      RSGroupProtos.RSGroupInfo proto = ProtobufUtil.toProtoGroupInfo(RSGroupInfo);
       Put p = new Put(Bytes.toBytes(RSGroupInfo.getName()));
       p.addColumn(META_FAMILY_BYTES, META_QUALIFIER_BYTES, proto.toByteArray());
       mutations.add(p);
@@ -532,7 +532,7 @@ final class RSGroupInfoManagerImpl implements RSGroupInfoManager {
 
       for (RSGroupInfo RSGroupInfo : newGroupMap.values()) {
         String znode = ZNodePaths.joinZNode(groupBasePath, RSGroupInfo.getName());
-        RSGroupProtos.RSGroupInfo proto = RSGroupProtobufUtil.toProtoGroupInfo(RSGroupInfo);
+        RSGroupProtos.RSGroupInfo proto = ProtobufUtil.toProtoGroupInfo(RSGroupInfo);
         LOG.debug("Updating znode: " + znode);
         ZKUtil.createAndFailSilent(watcher, znode);
         zkOps.add(ZKUtil.ZKUtilOp.deleteNodeFailSilent(znode));
diff --git a/hbase-rsgroup/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupProtobufUtil.java b/hbase-rsgroup/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupProtobufUtil.java
deleted file mode 100644
index 56e35e7..0000000
--- a/hbase-rsgroup/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupProtobufUtil.java
+++ /dev/null
@@ -1,63 +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.util.ArrayList;
-import java.util.List;
-
-import org.apache.hadoop.hbase.TableName;
-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.RSGroupProtos;
-import org.apache.yetus.audience.InterfaceAudience;
-
-@InterfaceAudience.Private
-final class RSGroupProtobufUtil {
-  private RSGroupProtobufUtil() {
-  }
-
-  static RSGroupInfo toGroupInfo(RSGroupProtos.RSGroupInfo proto) {
-    RSGroupInfo RSGroupInfo = new RSGroupInfo(proto.getName());
-    for(HBaseProtos.ServerName el: proto.getServersList()) {
-      RSGroupInfo.addServer(Address.fromParts(el.getHostName(), el.getPort()));
-    }
-    for(HBaseProtos.TableName pTableName: proto.getTablesList()) {
-      RSGroupInfo.addTable(ProtobufUtil.toTableName(pTableName));
-    }
-    return RSGroupInfo;
-  }
-
-  static RSGroupProtos.RSGroupInfo toProtoGroupInfo(RSGroupInfo pojo) {
-    List<HBaseProtos.TableName> tables = new ArrayList<>(pojo.getTables().size());
-    for(TableName arg: pojo.getTables()) {
-      tables.add(ProtobufUtil.toProtoTableName(arg));
-    }
-    List<HBaseProtos.ServerName> hostports = new ArrayList<>(pojo.getServers().size());
-    for(Address el: pojo.getServers()) {
-      hostports.add(HBaseProtos.ServerName.newBuilder()
-          .setHostName(el.getHostname())
-          .setPort(el.getPort())
-          .build());
-    }
-    return RSGroupProtos.RSGroupInfo.newBuilder().setName(pojo.getName())
-        .addAllServers(hostports)
-        .addAllTables(tables).build();
-  }
-}
diff --git a/hbase-rsgroup/src/test/java/org/apache/hadoop/hbase/rsgroup/VerifyingRSGroupAdminClient.java b/hbase-rsgroup/src/test/java/org/apache/hadoop/hbase/rsgroup/VerifyingRSGroupAdminClient.java
index 88a4339..2ad30e4 100644
--- a/hbase-rsgroup/src/test/java/org/apache/hadoop/hbase/rsgroup/VerifyingRSGroupAdminClient.java
+++ b/hbase-rsgroup/src/test/java/org/apache/hadoop/hbase/rsgroup/VerifyingRSGroupAdminClient.java
@@ -22,7 +22,6 @@ import java.io.IOException;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
-
 import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.hbase.TableName;
 import org.apache.hadoop.hbase.client.ConnectionFactory;
@@ -129,7 +128,7 @@ public class VerifyingRSGroupAdminClient implements RSGroupAdmin {
               result.getValue(
                   RSGroupInfoManager.META_FAMILY_BYTES,
                   RSGroupInfoManager.META_QUALIFIER_BYTES));
-      groupMap.put(proto.getName(), RSGroupProtobufUtil.toGroupInfo(proto));
+      groupMap.put(proto.getName(), ProtobufUtil.toGroupInfo(proto));
     }
     Assert.assertEquals(Sets.newHashSet(groupMap.values()),
         Sets.newHashSet(wrapped.listRSGroups()));
@@ -141,7 +140,7 @@ public class VerifyingRSGroupAdminClient implements RSGroupAdmin {
           ProtobufUtil.expectPBMagicPrefix(data);
           ByteArrayInputStream bis = new ByteArrayInputStream(
               data, ProtobufUtil.lengthOfPBMagic(), data.length);
-          zList.add(RSGroupProtobufUtil.toGroupInfo(RSGroupProtos.RSGroupInfo.parseFrom(bis)));
+          zList.add(ProtobufUtil.toGroupInfo(RSGroupProtos.RSGroupInfo.parseFrom(bis)));
         }
       }
       Assert.assertEquals(zList.size(), groupMap.size());


[hbase] 08/21: HBASE-22729 Start RSGroupInfoManager as default (#555)

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

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

commit 2bf2781df999d0c94eec9875457206089c39661b
Author: Guanghao Zhang <zg...@apache.org>
AuthorDate: Tue Sep 3 11:39:19 2019 +0800

    HBASE-22729 Start RSGroupInfoManager as default (#555)
    
    Amending-Author: Duo Zhang <zh...@apache.org>
    Signed-off-by: stack <st...@apache.org>
---
 .../src/main/java/org/apache/hadoop/hbase/master/HMaster.java | 11 +++++++++++
 .../java/org/apache/hadoop/hbase/master/MasterServices.java   |  8 ++++++--
 .../org/apache/hadoop/hbase/rsgroup/RSGroupAdminEndpoint.java |  2 +-
 .../org/apache/hadoop/hbase/rsgroup/RSGroupInfoManager.java   |  5 +++++
 .../apache/hadoop/hbase/master/MockNoopMasterServices.java    |  9 ++++++++-
 5 files changed, 31 insertions(+), 4 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 77ec39b..2472e34 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
@@ -188,6 +188,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.RSGroupInfoManager;
 import org.apache.hadoop.hbase.security.AccessDeniedException;
 import org.apache.hadoop.hbase.security.SecurityConstants;
 import org.apache.hadoop.hbase.security.UserProvider;
@@ -356,12 +357,15 @@ public class HMaster extends HRegionServer implements MasterServices {
   // manager of assignment nodes in zookeeper
   private AssignmentManager assignmentManager;
 
+
   /**
    * Cache for the meta region replica's locations. Also tracks their changes to avoid stale
    * cache entries.
    */
   private final MetaRegionLocationCache metaRegionLocationCache;
 
+  private RSGroupInfoManager rsGroupInfoManager;
+
   // manager of replication
   private ReplicationPeerManager replicationPeerManager;
 
@@ -799,6 +803,8 @@ public class HMaster extends HRegionServer implements MasterServices {
     this.splitOrMergeTracker = new SplitOrMergeTracker(zooKeeper, conf, this);
     this.splitOrMergeTracker.start();
 
+    this.rsGroupInfoManager = RSGroupInfoManager.create(this);
+
     this.replicationPeerManager = ReplicationPeerManager.create(zooKeeper, conf);
 
     this.drainingServerTracker = new DrainingServerTracker(zooKeeper, this, this.serverManager);
@@ -3865,4 +3871,9 @@ public class HMaster extends HRegionServer implements MasterServices {
   public MetaRegionLocationCache getMetaRegionLocationCache() {
     return this.metaRegionLocationCache;
   }
+
+  @Override
+  public RSGroupInfoManager getRSRSGroupInfoManager() {
+    return rsGroupInfoManager;
+  }
 }
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 0163998..28e254a 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
@@ -18,10 +18,8 @@
 package org.apache.hadoop.hbase.master;
 
 import com.google.protobuf.Service;
-
 import java.io.IOException;
 import java.util.List;
-
 import org.apache.hadoop.hbase.Server;
 import org.apache.hadoop.hbase.ServerName;
 import org.apache.hadoop.hbase.TableDescriptors;
@@ -51,6 +49,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.RSGroupInfoManager;
 import org.apache.hadoop.hbase.security.access.AccessChecker;
 import org.apache.hadoop.hbase.security.access.ZKPermissionWatcher;
 import org.apache.yetus.audience.InterfaceAudience;
@@ -541,4 +540,9 @@ public interface MasterServices extends Server {
    * Run the ReplicationBarrierChore.
    */
   void runReplicationBarrierCleaner();
+
+  /**
+   * @return the {@link RSGroupInfoManager}
+   */
+  RSGroupInfoManager getRSRSGroupInfoManager();
 }
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 a2a5623..0bde67b 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
@@ -66,7 +66,7 @@ public class RSGroupAdminEndpoint implements MasterCoprocessor, MasterObserver {
     }
 
     master = ((HasMasterServices) env).getMasterServices();
-    groupInfoManager = RSGroupInfoManagerImpl.getInstance(master);
+    groupInfoManager = master.getRSRSGroupInfoManager();
     groupAdminServer = new RSGroupAdminServer(master, groupInfoManager);
     Class<?> clazz =
       master.getConfiguration().getClass(HConstants.HBASE_MASTER_LOADBALANCER_CLASS, 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 1b9f3ef..a46fa4b 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
@@ -21,6 +21,7 @@ import java.io.IOException;
 import java.util.List;
 import java.util.Set;
 import org.apache.hadoop.hbase.TableName;
+import org.apache.hadoop.hbase.master.MasterServices;
 import org.apache.hadoop.hbase.net.Address;
 import org.apache.yetus.audience.InterfaceAudience;
 
@@ -87,4 +88,8 @@ public interface RSGroupInfoManager {
    */
   @Deprecated
   RSGroupInfo getRSGroupForTable(TableName tableName) throws IOException;
+
+  static RSGroupInfoManager create(MasterServices master) throws IOException {
+    return RSGroupInfoManagerImpl.getInstance(master);
+  }
 }
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 b9fff6d..cde6ea0 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
@@ -54,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.RSGroupInfoManager;
 import org.apache.hadoop.hbase.security.access.AccessChecker;
 import org.apache.hadoop.hbase.security.access.ZKPermissionWatcher;
 import org.apache.hadoop.hbase.zookeeper.ZKWatcher;
@@ -492,5 +493,11 @@ public class MockNoopMasterServices implements MasterServices {
   }
 
   @Override
-  public void runReplicationBarrierCleaner() {}
+  public void runReplicationBarrierCleaner() {
+  }
+
+  @Override
+  public RSGroupInfoManager getRSRSGroupInfoManager() {
+    return null;
+  }
 }


[hbase] 15/21: 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 master
in repository https://gitbox.apache.org/repos/asf/hbase.git

commit c8d892cdef671d94bdf24096383b5704a87be6c5
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    |   91 +-
 .../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   | 1077 +++-----------------
 ...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   |   65 +-
 .../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}      |  834 ++++++---------
 .../hbase/rsgroup/VerifyingRSGroupAdminClient.java |  193 ----
 .../hadoop/hbase/thrift2/client/ThriftAdmin.java   |    4 +-
 pom.xml                                            |   15 +
 29 files changed, 1258 insertions(+), 2554 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 aeb5197..1e73022 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
@@ -2338,7 +2338,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
@@ -2346,7 +2346,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 293d513..c790cde 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
@@ -980,8 +980,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
@@ -1010,8 +1010,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 d53aab2..70ce606 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
@@ -1580,7 +1580,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
@@ -1588,7 +1588,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 7939b80..edbd646 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
@@ -858,8 +858,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
@@ -888,8 +888,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 b9cb700..4e0ad26 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;
@@ -3885,7 +3891,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,
@@ -3998,27 +4004,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))
@@ -4069,47 +4071,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 368328e..ff492ee 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 223acc2..52fc425 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
@@ -321,6 +321,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;
@@ -2919,6 +2925,94 @@ public class MasterRpcServices extends RSRpcServices implements
   }
 
   @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 94ac353..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,35 +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.SlowLogQueryFilter;
-import org.apache.hadoop.hbase.client.SlowLogRecord;
-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;
@@ -61,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;
 
@@ -102,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());
       }
@@ -973,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()) {
@@ -988,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) {
@@ -1060,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));
@@ -1076,24 +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);
-  }
-
-  @Override
-  public void removeServers(Set<Address> servers) throws IOException {
-    removeRSGroup(servers);
+    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 List<SlowLogRecord> getSlowLogResponses(Set<ServerName> serverNames,
-    SlowLogQueryFilter slowLogQueryFilter) throws IOException {
-    return null;
+  /**
+   * 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);
+    }
   }
 
-  @Override
-  public List<Boolean> clearSlowLogResponses(Set<ServerName> serverNames) throws IOException {
-    return null;
+  /**
+   * 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 2b43f3e..f443360 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));
@@ -131,7 +128,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;
@@ -140,7 +137,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());
     }
@@ -176,7 +173,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..0d8d119 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,30 @@ 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;
       }
     });
 
     Set<Address> ServersInDeadServerGroup =
-        rsGroupAdmin.getRSGroup(deadServerGroup.getName()).getServers();
+      ADMIN.getRSGroup(deadServerGroup.getName()).getServers();
     assertEquals(serverCountToMoveToDeadServerGroup, ServersInDeadServerGroup.size());
     assertTrue(ServersInDeadServerGroup.contains(serverToStop.getAddress()));
   }
@@ -256,7 +253,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 54%
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 94ac353..119cada 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,31 +47,22 @@ 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.SlowLogQueryFilter;
 import org.apache.hadoop.hbase.client.SlowLogRecord;
 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;
@@ -81,1009 +78,798 @@ 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;
 
-  // for writing UTs
-  @VisibleForTesting
-  protected RSGroupAdminClient() {
+  private final Admin admin;
+
+  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);
+    return admin.isSnapshotCleanupEnabled();
   }
 
-  @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);
-    }
+    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);
+    }
   }
 
   @Override
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 cb93dd4..a6f9fac 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
@@ -1178,7 +1178,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");
   }
 
@@ -1208,7 +1208,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 b68ddf6..6fc2db9 100755
--- a/pom.xml
+++ b/pom.xml
@@ -3635,6 +3635,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] 18/21: 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 master
in repository https://gitbox.apache.org/repos/asf/hbase.git

commit 37e87aeca773f64d07b1e3f2353f2bc9018de385
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


[hbase] 03/21: HBASE-22676 Move all the code in hbase-rsgroup to hbase-server and remove hbase-rsgroup module (#399)

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

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

commit f0a13bb74daff94a121885d9bab67060ef380b92
Author: Duo Zhang <zh...@apache.org>
AuthorDate: Tue Jul 23 09:40:02 2019 +0800

    HBASE-22676 Move all the code in hbase-rsgroup to hbase-server and remove hbase-rsgroup module (#399)
    
    Signed-off-by: Zheng Hu <op...@gmail.com>
---
 hbase-assembly/src/main/assembly/components.xml    |   8 -
 .../src/main/assembly/hadoop-two-compat.xml        |   1 -
 hbase-it/pom.xml                                   |  10 -
 hbase-rsgroup/README.txt                           |  13 -
 hbase-rsgroup/pom.xml                              | 262 ----------
 .../hadoop/hbase/rsgroup/RSGroupAdminEndpoint.java | 559 ---------------------
 hbase-rsgroup/src/test/resources/hbase-site.xml    |  32 --
 hbase-rsgroup/src/test/resources/log4j.properties  |  68 ---
 .../apache/hadoop/hbase/rsgroup/RSGroupAdmin.java  |   0
 .../hadoop/hbase/rsgroup/RSGroupAdminClient.java   |   0
 .../hadoop/hbase/rsgroup/RSGroupAdminEndpoint.java | 226 +++++++++
 .../hadoop/hbase/rsgroup/RSGroupAdminServer.java   |   0
 .../hbase/rsgroup/RSGroupAdminServiceImpl.java     | 378 ++++++++++++++
 .../hbase/rsgroup/RSGroupBasedLoadBalancer.java    |  24 +-
 .../hbase/rsgroup/RSGroupMajorCompactionTTL.java   |   0
 .../hadoop/hbase/rsgroup/RSGroupableBalancer.java  |   0
 .../balancer/RSGroupableBalancerTestBase.java      |   0
 .../balancer/TestRSGroupBasedLoadBalancer.java     |   0
 ...lancerWithStochasticLoadBalancerAsInternal.java |   0
 .../procedure/TestSCPWithReplicasWithRSGroup.java  |   0
 .../hadoop/hbase/rsgroup/TestEnableRSGroups.java   |   0
 .../rsgroup/TestRSGroupMajorCompactionTTL.java     |   0
 .../hadoop/hbase/rsgroup/TestRSGroupsAdmin1.java   |   0
 .../hadoop/hbase/rsgroup/TestRSGroupsAdmin2.java   |   0
 .../hadoop/hbase/rsgroup/TestRSGroupsBalance.java  |   0
 .../hadoop/hbase/rsgroup/TestRSGroupsBase.java     |   0
 .../hadoop/hbase/rsgroup/TestRSGroupsBasics.java   |   0
 .../hadoop/hbase/rsgroup/TestRSGroupsKillRS.java   |   0
 .../hbase/rsgroup/TestRSGroupsOfflineMode.java     |   0
 .../hadoop/hbase/rsgroup/TestRSGroupsWithACL.java  |  77 ++-
 .../hbase/rsgroup/VerifyingRSGroupAdminClient.java |   0
 hbase-shell/pom.xml                                |  35 --
 pom.xml                                            |  24 -
 33 files changed, 647 insertions(+), 1070 deletions(-)

diff --git a/hbase-assembly/src/main/assembly/components.xml b/hbase-assembly/src/main/assembly/components.xml
index 18dd495..791ca9add 100644
--- a/hbase-assembly/src/main/assembly/components.xml
+++ b/hbase-assembly/src/main/assembly/components.xml
@@ -145,14 +145,6 @@
       <fileMode>0644</fileMode>
     </fileSet>
     <fileSet>
-      <directory>${project.basedir}/../hbase-rsgroup/target/</directory>
-      <outputDirectory>lib</outputDirectory>
-      <includes>
-        <include>${rsgroup.test.jar}</include>
-      </includes>
-      <fileMode>0644</fileMode>
-    </fileSet>
-    <fileSet>
       <directory>${project.basedir}/../hbase-mapreduce/target/</directory>
       <outputDirectory>lib</outputDirectory>
       <includes>
diff --git a/hbase-assembly/src/main/assembly/hadoop-two-compat.xml b/hbase-assembly/src/main/assembly/hadoop-two-compat.xml
index 91d3749..6a21b92 100644
--- a/hbase-assembly/src/main/assembly/hadoop-two-compat.xml
+++ b/hbase-assembly/src/main/assembly/hadoop-two-compat.xml
@@ -52,7 +52,6 @@
         <include>org.apache.hbase:hbase-protocol-shaded</include>
         <include>org.apache.hbase:hbase-replication</include>
         <include>org.apache.hbase:hbase-rest</include>
-        <include>org.apache.hbase:hbase-rsgroup</include>
         <include>org.apache.hbase:hbase-server</include>
         <include>org.apache.hbase:hbase-shell</include>
         <include>org.apache.hbase:hbase-testing-util</include>
diff --git a/hbase-it/pom.xml b/hbase-it/pom.xml
index 0fe578a..6b173f0 100644
--- a/hbase-it/pom.xml
+++ b/hbase-it/pom.xml
@@ -183,16 +183,6 @@
     </dependency>
     <dependency>
       <groupId>org.apache.hbase</groupId>
-      <artifactId>hbase-rsgroup</artifactId>
-    </dependency>
-    <dependency>
-      <groupId>org.apache.hbase</groupId>
-      <artifactId>hbase-rsgroup</artifactId>
-      <type>test-jar</type>
-      <scope>test</scope>
-    </dependency>
-    <dependency>
-      <groupId>org.apache.hbase</groupId>
       <artifactId>hbase-server</artifactId>
     </dependency>
     <dependency>
diff --git a/hbase-rsgroup/README.txt b/hbase-rsgroup/README.txt
deleted file mode 100644
index b24aee6..0000000
--- a/hbase-rsgroup/README.txt
+++ /dev/null
@@ -1,13 +0,0 @@
-ON PROTOBUFS
-This maven module has protobuf definition files ('.protos') used by hbase
-Coprocessor Endpoints that ship with hbase core including tests. Coprocessor
-Endpoints are meant to be standalone, independent code not reliant on hbase
-internals. They define their Service using protobuf. The protobuf version
-they use can be distinct from that used by HBase internally since HBase started
-shading its protobuf references. Endpoints have no access to the shaded protobuf
-hbase uses. They do have access to the content of hbase-protocol -- the
-.protos found in here -- but avoid using as much of this as you can as it is
-liable to change.
-
-Generation of java files from protobuf .proto files included here is done as
-part of the build.
diff --git a/hbase-rsgroup/pom.xml b/hbase-rsgroup/pom.xml
deleted file mode 100644
index 1135939..0000000
--- a/hbase-rsgroup/pom.xml
+++ /dev/null
@@ -1,262 +0,0 @@
-<?xml version="1.0"?>
-<project xmlns="https://maven.apache.org/POM/4.0.0" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
-  <!--
-  /**
-   * 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.
-   */
-  -->
-  <modelVersion>4.0.0</modelVersion>
-  <parent>
-    <artifactId>hbase-build-configuration</artifactId>
-    <groupId>org.apache.hbase</groupId>
-    <version>3.0.0-SNAPSHOT</version>
-    <relativePath>../hbase-build-configuration</relativePath>
-  </parent>
-  <artifactId>hbase-rsgroup</artifactId>
-  <name>Apache HBase - RSGroup</name>
-  <description>Regionserver Groups for HBase</description>
-  <build>
-    <plugins>
-      <plugin>
-        <!--Make it so assembly:single does nothing in here-->
-        <artifactId>maven-assembly-plugin</artifactId>
-        <configuration>
-          <skipAssembly>true</skipAssembly>
-        </configuration>
-      </plugin>
-      <!-- Make a jar and put the sources in the jar -->
-      <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-source-plugin</artifactId>
-      </plugin>
-      <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-checkstyle-plugin</artifactId>
-        <configuration>
-          <failOnViolation>true</failOnViolation>
-        </configuration>
-      </plugin>
-      <plugin>
-        <groupId>net.revelc.code</groupId>
-        <artifactId>warbucks-maven-plugin</artifactId>
-      </plugin>
-    </plugins>
-  </build>
-  <dependencies>
-    <!-- Intra-project dependencies -->
-    <dependency>
-      <groupId>org.apache.hbase</groupId>
-      <artifactId>hbase-annotations</artifactId>
-      <type>test-jar</type>
-      <scope>test</scope>
-    </dependency>
-    <dependency>
-      <groupId>org.apache.hbase</groupId>
-      <artifactId>hbase-client</artifactId>
-    </dependency>
-    <dependency>
-      <groupId>org.apache.hbase</groupId>
-      <artifactId>hbase-server</artifactId>
-    </dependency>
-    <dependency>
-      <groupId>org.apache.hbase</groupId>
-      <artifactId>hbase-common</artifactId>
-    </dependency>
-    <dependency>
-      <groupId>org.apache.hbase</groupId>
-      <artifactId>hbase-procedure</artifactId>
-    </dependency>
-    <dependency>
-      <groupId>org.apache.hbase</groupId>
-      <artifactId>hbase-procedure</artifactId>
-      <type>test-jar</type>
-      <scope>test</scope>
-    </dependency>
-    <dependency>
-      <groupId>org.apache.hbase</groupId>
-      <artifactId>hbase-protocol</artifactId>
-    </dependency>
-    <dependency>
-      <groupId>org.apache.hbase</groupId>
-      <artifactId>hbase-protocol-shaded</artifactId>
-    </dependency>
-    <dependency>
-      <groupId>org.apache.hbase</groupId>
-      <artifactId>hbase-testing-util</artifactId>
-      <scope>test</scope>
-    </dependency>
-    <!-- General dependencies -->
-    <dependency>
-      <groupId>org.apache.commons</groupId>
-      <artifactId>commons-lang3</artifactId>
-    </dependency>
-    <dependency>
-      <groupId>org.slf4j</groupId>
-      <artifactId>slf4j-api</artifactId>
-    </dependency>
-    <dependency>
-      <groupId>org.apache.hbase.thirdparty</groupId>
-      <artifactId>hbase-shaded-miscellaneous</artifactId>
-    </dependency>
-    <dependency>
-      <groupId>org.apache.zookeeper</groupId>
-      <artifactId>zookeeper</artifactId>
-    </dependency>
-    <dependency>
-      <groupId>log4j</groupId>
-      <artifactId>log4j</artifactId>
-      <scope>test</scope>
-    </dependency>
-    <dependency>
-      <groupId>org.mockito</groupId>
-      <artifactId>mockito-core</artifactId>
-      <scope>test</scope>
-    </dependency>
-    <dependency>
-      <groupId>junit</groupId>
-      <artifactId>junit</artifactId>
-      <scope>test</scope>
-    </dependency>
-  </dependencies>
-  <profiles>
-    <!-- Skip the tests in this module -->
-    <profile>
-      <id>skipRSGroupTests</id>
-      <activation>
-        <property>
-          <name>skipRSGroupTests</name>
-        </property>
-      </activation>
-      <properties>
-        <surefire.skipFirstPart>true</surefire.skipFirstPart>
-        <surefire.skipSecondPart>true</surefire.skipSecondPart>
-      </properties>
-    </profile>
-    <!-- profile against Hadoop 2.x: This is the default. -->
-    <profile>
-      <id>hadoop-2.0</id>
-      <activation>
-        <property>
-          <!--Below formatting for dev-support/generate-hadoopX-poms.sh-->
-          <!--h2-->
-          <name>!hadoop.profile</name>
-        </property>
-      </activation>
-      <dependencies>
-        <dependency>
-          <groupId>com.github.stephenc.findbugs</groupId>
-          <artifactId>findbugs-annotations</artifactId>
-          <optional>true</optional>
-        </dependency>
-        <dependency>
-          <groupId>org.apache.hadoop</groupId>
-          <artifactId>hadoop-common</artifactId>
-          <exclusions>
-            <exclusion>
-              <groupId>net.java.dev.jets3t</groupId>
-              <artifactId>jets3t</artifactId>
-            </exclusion>
-            <exclusion>
-              <groupId>javax.servlet.jsp</groupId>
-              <artifactId>jsp-api</artifactId>
-            </exclusion>
-            <exclusion>
-              <groupId>org.mortbay.jetty</groupId>
-              <artifactId>jetty</artifactId>
-            </exclusion>
-            <exclusion>
-              <groupId>com.sun.jersey</groupId>
-              <artifactId>jersey-server</artifactId>
-            </exclusion>
-            <exclusion>
-              <groupId>com.sun.jersey</groupId>
-              <artifactId>jersey-core</artifactId>
-            </exclusion>
-            <exclusion>
-              <groupId>com.sun.jersey</groupId>
-              <artifactId>jersey-json</artifactId>
-            </exclusion>
-            <exclusion>
-              <groupId>javax.servlet</groupId>
-              <artifactId>servlet-api</artifactId>
-            </exclusion>
-            <exclusion>
-              <groupId>tomcat</groupId>
-              <artifactId>jasper-compiler</artifactId>
-            </exclusion>
-            <exclusion>
-              <groupId>tomcat</groupId>
-              <artifactId>jasper-runtime</artifactId>
-            </exclusion>
-            <exclusion>
-              <groupId>com.google.code.findbugs</groupId>
-              <artifactId>jsr305</artifactId>
-            </exclusion>
-          </exclusions>
-        </dependency>
-      </dependencies>
-    </profile>
-    <!--
-      profile for building against Hadoop 3.0.x. Activate using:
-       mvn -Dhadoop.profile=3.0
-    -->
-    <profile>
-      <id>hadoop-3.0</id>
-      <activation>
-        <property>
-          <name>hadoop.profile</name>
-          <value>3.0</value>
-        </property>
-      </activation>
-      <properties>
-        <hadoop.version>3.0-SNAPSHOT</hadoop.version>
-      </properties>
-      <dependencies>
-        <dependency>
-          <groupId>org.apache.hadoop</groupId>
-          <artifactId>hadoop-common</artifactId>
-        </dependency>
-      </dependencies>
-    </profile>
-    <profile>
-      <id>eclipse-specific</id>
-      <activation>
-        <property>
-          <name>m2e.version</name>
-        </property>
-      </activation>
-      <build>
-        <pluginManagement>
-          <plugins>
-            <!--This plugin's configuration is used to store Eclipse m2e settings
-                 only. It has no influence on the Maven build itself.-->
-            <plugin>
-              <groupId>org.eclipse.m2e</groupId>
-              <artifactId>lifecycle-mapping</artifactId>
-              <configuration>
-                <lifecycleMappingMetadata>
-                  <pluginExecutions>
-                  </pluginExecutions>
-                </lifecycleMappingMetadata>
-              </configuration>
-            </plugin>
-          </plugins>
-        </pluginManagement>
-      </build>
-    </profile>
-  </profiles>
-</project>
diff --git a/hbase-rsgroup/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupAdminEndpoint.java b/hbase-rsgroup/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupAdminEndpoint.java
deleted file mode 100644
index 8c6e010..0000000
--- a/hbase-rsgroup/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupAdminEndpoint.java
+++ /dev/null
@@ -1,559 +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 com.google.protobuf.RpcCallback;
-import com.google.protobuf.RpcController;
-import com.google.protobuf.Service;
-import java.io.IOException;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Optional;
-import java.util.Set;
-import java.util.stream.Collectors;
-import org.apache.hadoop.hbase.CoprocessorEnvironment;
-import org.apache.hadoop.hbase.HBaseIOException;
-import org.apache.hadoop.hbase.HConstants;
-import org.apache.hadoop.hbase.MasterNotRunningException;
-import org.apache.hadoop.hbase.NamespaceDescriptor;
-import org.apache.hadoop.hbase.PleaseHoldException;
-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.SnapshotDescription;
-import org.apache.hadoop.hbase.client.TableDescriptor;
-import org.apache.hadoop.hbase.constraint.ConstraintException;
-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.coprocessor.MasterCoprocessorEnvironment;
-import org.apache.hadoop.hbase.coprocessor.MasterObserver;
-import org.apache.hadoop.hbase.coprocessor.ObserverContext;
-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.HBaseProtos;
-import org.apache.hadoop.hbase.protobuf.generated.RSGroupAdminProtos;
-import org.apache.hadoop.hbase.protobuf.generated.RSGroupAdminProtos.AddRSGroupRequest;
-import org.apache.hadoop.hbase.protobuf.generated.RSGroupAdminProtos.AddRSGroupResponse;
-import org.apache.hadoop.hbase.protobuf.generated.RSGroupAdminProtos.BalanceRSGroupRequest;
-import org.apache.hadoop.hbase.protobuf.generated.RSGroupAdminProtos.BalanceRSGroupResponse;
-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.ListRSGroupInfosResponse;
-import org.apache.hadoop.hbase.protobuf.generated.RSGroupAdminProtos.MoveServersAndTablesRequest;
-import org.apache.hadoop.hbase.protobuf.generated.RSGroupAdminProtos.MoveServersAndTablesResponse;
-import org.apache.hadoop.hbase.protobuf.generated.RSGroupAdminProtos.MoveServersRequest;
-import org.apache.hadoop.hbase.protobuf.generated.RSGroupAdminProtos.MoveServersResponse;
-import org.apache.hadoop.hbase.protobuf.generated.RSGroupAdminProtos.MoveTablesRequest;
-import org.apache.hadoop.hbase.protobuf.generated.RSGroupAdminProtos.MoveTablesResponse;
-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.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.apache.yetus.audience.InterfaceAudience;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import org.apache.hbase.thirdparty.com.google.common.collect.Sets;
-
-// TODO: Encapsulate MasterObserver functions into separate subclass.
-@CoreCoprocessor
-@InterfaceAudience.Private
-public class RSGroupAdminEndpoint implements MasterCoprocessor, MasterObserver {
-  private static final Logger LOG = LoggerFactory.getLogger(RSGroupAdminEndpoint.class);
-
-  private MasterServices master = null;
-  // Only instance of RSGroupInfoManager. RSGroup aware load balancers ask for this instance on
-  // their setup.
-  private RSGroupInfoManager groupInfoManager;
-  private RSGroupAdminServer groupAdminServer;
-  private final RSGroupAdminService groupAdminService = new RSGroupAdminServiceImpl();
-  private AccessChecker accessChecker;
-
-  /** Provider for mapping principal names to Users */
-  private UserProvider userProvider;
-
-  @Override
-  public void start(CoprocessorEnvironment env) throws IOException {
-    if (!(env instanceof HasMasterServices)) {
-      throw new IOException("Does not implement HMasterServices");
-    }
-
-    master = ((HasMasterServices)env).getMasterServices();
-    groupInfoManager = RSGroupInfoManagerImpl.getInstance(master);
-    groupAdminServer = new RSGroupAdminServer(master, groupInfoManager);
-    Class<?> clazz =
-        master.getConfiguration().getClass(HConstants.HBASE_MASTER_LOADBALANCER_CLASS, null);
-    if (!RSGroupableBalancer.class.isAssignableFrom(clazz)) {
-      throw new IOException("Configured balancer does not support RegionServer groups.");
-    }
-    accessChecker = ((HasMasterServices) env).getMasterServices().getAccessChecker();
-
-    // set the user-provider.
-    this.userProvider = UserProvider.instantiate(env.getConfiguration());
-  }
-
-  @Override
-  public void stop(CoprocessorEnvironment env) {
-  }
-
-  @Override
-  public Iterable<Service> getServices() {
-    return Collections.singleton(groupAdminService);
-  }
-
-  @Override
-  public Optional<MasterObserver> getMasterObserver() {
-    return Optional.of(this);
-  }
-
-  RSGroupInfoManager getGroupInfoManager() {
-    return groupInfoManager;
-  }
-
-  /**
-   * 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.
-   */
-  private class RSGroupAdminServiceImpl extends RSGroupAdminProtos.RSGroupAdminService {
-    @Override
-    public void getRSGroupInfo(RpcController controller,
-        GetRSGroupInfoRequest request, RpcCallback<GetRSGroupInfoResponse> done) {
-      GetRSGroupInfoResponse.Builder builder = GetRSGroupInfoResponse.newBuilder();
-      String groupName = request.getRSGroupName();
-      LOG.info(master.getClientIdAuditPrefix() + " initiates rsgroup info retrieval, group="
-              + groupName);
-      try {
-        if (master.getMasterCoprocessorHost() != null) {
-          master.getMasterCoprocessorHost().preGetRSGroupInfo(groupName);
-        }
-        checkPermission("getRSGroupInfo");
-        RSGroupInfo rsGroupInfo = groupAdminServer.getRSGroupInfo(groupName);
-        if (rsGroupInfo != null) {
-          builder.setRSGroupInfo(ProtobufUtil.toProtoGroupInfo(rsGroupInfo));
-        }
-        if (master.getMasterCoprocessorHost() != null) {
-          master.getMasterCoprocessorHost().postGetRSGroupInfo(groupName);
-        }
-      } catch (IOException e) {
-        CoprocessorRpcUtils.setControllerException(controller, e);
-      }
-      done.run(builder.build());
-    }
-
-    @Override
-    public void getRSGroupInfoOfTable(RpcController controller,
-        GetRSGroupInfoOfTableRequest request, RpcCallback<GetRSGroupInfoOfTableResponse> done) {
-      GetRSGroupInfoOfTableResponse.Builder builder = GetRSGroupInfoOfTableResponse.newBuilder();
-      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);
-        }
-        checkPermission("getRSGroupInfoOfTable");
-        RSGroupInfo RSGroupInfo = groupAdminServer.getRSGroupInfoOfTable(tableName);
-        if (RSGroupInfo != null) {
-          builder.setRSGroupInfo(ProtobufUtil.toProtoGroupInfo(RSGroupInfo));
-        }
-        if (master.getMasterCoprocessorHost() != null) {
-          master.getMasterCoprocessorHost().postGetRSGroupInfoOfTable(tableName);
-        }
-      } catch (IOException e) {
-        CoprocessorRpcUtils.setControllerException(controller, e);
-      }
-      done.run(builder.build());
-    }
-
-    @Override
-    public void moveServers(RpcController controller, MoveServersRequest request,
-        RpcCallback<MoveServersResponse> done) {
-      MoveServersResponse.Builder builder = MoveServersResponse.newBuilder();
-      Set<Address> hostPorts = Sets.newHashSet();
-      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());
-        }
-        checkPermission("moveServers");
-        groupAdminServer.moveServers(hostPorts, request.getTargetGroup());
-        if (master.getMasterCoprocessorHost() != null) {
-          master.getMasterCoprocessorHost().postMoveServers(hostPorts, request.getTargetGroup());
-        }
-      } catch (IOException e) {
-        CoprocessorRpcUtils.setControllerException(controller, e);
-      }
-      done.run(builder.build());
-    }
-
-    @Override
-    public void moveTables(RpcController controller, MoveTablesRequest request,
-        RpcCallback<MoveTablesResponse> done) {
-      MoveTablesResponse.Builder builder = MoveTablesResponse.newBuilder();
-      Set<TableName> tables = new HashSet<>(request.getTableNameList().size());
-      for (HBaseProtos.TableName tableName : request.getTableNameList()) {
-        tables.add(ProtobufUtil.toTableName(tableName));
-      }
-      LOG.info(master.getClientIdAuditPrefix() + " move tables " + tables +" to rsgroup "
-          + request.getTargetGroup());
-      try {
-        if (master.getMasterCoprocessorHost() != null) {
-          master.getMasterCoprocessorHost().preMoveTables(tables, request.getTargetGroup());
-        }
-        checkPermission("moveTables");
-        groupAdminServer.moveTables(tables, request.getTargetGroup());
-        if (master.getMasterCoprocessorHost() != null) {
-          master.getMasterCoprocessorHost().postMoveTables(tables, request.getTargetGroup());
-        }
-      } catch (IOException e) {
-        CoprocessorRpcUtils.setControllerException(controller, e);
-      }
-      done.run(builder.build());
-    }
-
-    @Override
-    public void addRSGroup(RpcController controller, AddRSGroupRequest request,
-        RpcCallback<AddRSGroupResponse> done) {
-      AddRSGroupResponse.Builder builder = AddRSGroupResponse.newBuilder();
-      LOG.info(master.getClientIdAuditPrefix() + " add rsgroup " + request.getRSGroupName());
-      try {
-        if (master.getMasterCoprocessorHost() != null) {
-          master.getMasterCoprocessorHost().preAddRSGroup(request.getRSGroupName());
-        }
-        checkPermission("addRSGroup");
-        groupAdminServer.addRSGroup(request.getRSGroupName());
-        if (master.getMasterCoprocessorHost() != null) {
-          master.getMasterCoprocessorHost().postAddRSGroup(request.getRSGroupName());
-        }
-      } catch (IOException e) {
-        CoprocessorRpcUtils.setControllerException(controller, e);
-      }
-      done.run(builder.build());
-    }
-
-    @Override
-    public void removeRSGroup(RpcController controller,
-        RemoveRSGroupRequest request, RpcCallback<RemoveRSGroupResponse> done) {
-      RemoveRSGroupResponse.Builder builder =
-          RemoveRSGroupResponse.newBuilder();
-      LOG.info(master.getClientIdAuditPrefix() + " remove rsgroup " + request.getRSGroupName());
-      try {
-        if (master.getMasterCoprocessorHost() != null) {
-          master.getMasterCoprocessorHost().preRemoveRSGroup(request.getRSGroupName());
-        }
-        checkPermission("removeRSGroup");
-        groupAdminServer.removeRSGroup(request.getRSGroupName());
-        if (master.getMasterCoprocessorHost() != null) {
-          master.getMasterCoprocessorHost().postRemoveRSGroup(request.getRSGroupName());
-        }
-      } catch (IOException e) {
-        CoprocessorRpcUtils.setControllerException(controller, e);
-      }
-      done.run(builder.build());
-    }
-
-    @Override
-    public void balanceRSGroup(RpcController controller,
-        BalanceRSGroupRequest request, RpcCallback<BalanceRSGroupResponse> done) {
-      BalanceRSGroupResponse.Builder builder = BalanceRSGroupResponse.newBuilder();
-      LOG.info(master.getClientIdAuditPrefix() + " balance rsgroup, group=" +
-              request.getRSGroupName());
-      try {
-        if (master.getMasterCoprocessorHost() != null) {
-          master.getMasterCoprocessorHost().preBalanceRSGroup(request.getRSGroupName());
-        }
-        checkPermission("balanceRSGroup");
-        boolean balancerRan = groupAdminServer.balanceRSGroup(request.getRSGroupName());
-        builder.setBalanceRan(balancerRan);
-        if (master.getMasterCoprocessorHost() != null) {
-          master.getMasterCoprocessorHost().postBalanceRSGroup(request.getRSGroupName(),
-              balancerRan);
-        }
-      } catch (IOException e) {
-        CoprocessorRpcUtils.setControllerException(controller, e);
-        builder.setBalanceRan(false);
-      }
-      done.run(builder.build());
-    }
-
-    @Override
-    public void listRSGroupInfos(RpcController controller,
-        ListRSGroupInfosRequest request, RpcCallback<ListRSGroupInfosResponse> done) {
-      ListRSGroupInfosResponse.Builder builder = ListRSGroupInfosResponse.newBuilder();
-      LOG.info(master.getClientIdAuditPrefix() + " list rsgroup");
-      try {
-        if (master.getMasterCoprocessorHost() != null) {
-          master.getMasterCoprocessorHost().preListRSGroups();
-        }
-        checkPermission("listRSGroup");
-        for (RSGroupInfo RSGroupInfo : groupAdminServer.listRSGroups()) {
-          builder.addRSGroupInfo(ProtobufUtil.toProtoGroupInfo(RSGroupInfo));
-        }
-        if (master.getMasterCoprocessorHost() != null) {
-          master.getMasterCoprocessorHost().postListRSGroups();
-        }
-      } catch (IOException e) {
-        CoprocessorRpcUtils.setControllerException(controller, e);
-      }
-      done.run(builder.build());
-    }
-
-    @Override
-    public void getRSGroupInfoOfServer(RpcController controller,
-        GetRSGroupInfoOfServerRequest request, RpcCallback<GetRSGroupInfoOfServerResponse> done) {
-      GetRSGroupInfoOfServerResponse.Builder builder = GetRSGroupInfoOfServerResponse.newBuilder();
-      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);
-        }
-        checkPermission("getRSGroupInfoOfServer");
-        RSGroupInfo info = groupAdminServer.getRSGroupOfServer(hp);
-        if (info != null) {
-          builder.setRSGroupInfo(ProtobufUtil.toProtoGroupInfo(info));
-        }
-        if (master.getMasterCoprocessorHost() != null) {
-          master.getMasterCoprocessorHost().postGetRSGroupInfoOfServer(hp);
-        }
-      } catch (IOException e) {
-        CoprocessorRpcUtils.setControllerException(controller, e);
-      }
-      done.run(builder.build());
-    }
-
-    @Override
-    public void moveServersAndTables(RpcController controller,
-        MoveServersAndTablesRequest request, RpcCallback<MoveServersAndTablesResponse> done) {
-      MoveServersAndTablesResponse.Builder builder = MoveServersAndTablesResponse.newBuilder();
-      Set<Address> hostPorts = Sets.newHashSet();
-      for (HBaseProtos.ServerName el : request.getServersList()) {
-        hostPorts.add(Address.fromParts(el.getHostName(), el.getPort()));
-      }
-      Set<TableName> tables = new HashSet<>(request.getTableNameList().size());
-      for (HBaseProtos.TableName tableName : request.getTableNameList()) {
-        tables.add(ProtobufUtil.toTableName(tableName));
-      }
-      LOG.info(master.getClientIdAuditPrefix() + " move servers " + hostPorts
-          + " and tables " + tables + " to rsgroup" + request.getTargetGroup());
-      try {
-        if (master.getMasterCoprocessorHost() != null) {
-          master.getMasterCoprocessorHost().preMoveServersAndTables(hostPorts, tables,
-              request.getTargetGroup());
-        }
-        checkPermission("moveServersAndTables");
-        groupAdminServer.moveServersAndTables(hostPorts, tables, request.getTargetGroup());
-        if (master.getMasterCoprocessorHost() != null) {
-          master.getMasterCoprocessorHost().postMoveServersAndTables(hostPorts, tables,
-              request.getTargetGroup());
-        }
-      } catch (IOException e) {
-        CoprocessorRpcUtils.setControllerException(controller, e);
-      }
-      done.run(builder.build());
-    }
-
-    @Override
-    public void removeServers(RpcController controller,
-        RemoveServersRequest request,
-        RpcCallback<RemoveServersResponse> done) {
-      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);
-        }
-        checkPermission("removeServers");
-        groupAdminServer.removeServers(servers);
-        if (master.getMasterCoprocessorHost() != null) {
-          master.getMasterCoprocessorHost().postRemoveServers(servers);
-        }
-      } catch (IOException e) {
-        CoprocessorRpcUtils.setControllerException(controller, e);
-      }
-      done.run(builder.build());
-    }
-  }
-
-  boolean rsgroupHasServersOnline(TableDescriptor desc) throws IOException {
-    String groupName;
-    try {
-      groupName =
-        master.getClusterSchema().getNamespace(desc.getTableName().getNamespaceAsString())
-        .getConfigurationValue(RSGroupInfo.NAMESPACE_DESC_PROP_GROUP);
-      if (groupName == null) {
-        groupName = RSGroupInfo.DEFAULT_GROUP;
-      }
-    } catch (MasterNotRunningException | PleaseHoldException e) {
-      LOG.info("Master has not initialized yet; temporarily using default RSGroup '" +
-          RSGroupInfo.DEFAULT_GROUP + "' for deploy of system table");
-      groupName = RSGroupInfo.DEFAULT_GROUP;
-    }
-
-    RSGroupInfo rsGroupInfo = groupAdminServer.getRSGroupInfo(groupName);
-    if (rsGroupInfo == null) {
-      throw new ConstraintException(
-          "Default RSGroup (" + groupName + ") for this table's " + "namespace does not exist.");
-    }
-
-    for (ServerName onlineServer : master.getServerManager().createDestinationServersList()) {
-      if (rsGroupInfo.getServers().contains(onlineServer.getAddress())) {
-        return true;
-      }
-    }
-    return false;
-  }
-
-  void assignTableToGroup(TableDescriptor desc) throws IOException {
-    String groupName =
-        master.getClusterSchema().getNamespace(desc.getTableName().getNamespaceAsString())
-                .getConfigurationValue(RSGroupInfo.NAMESPACE_DESC_PROP_GROUP);
-    if (groupName == null) {
-      groupName = RSGroupInfo.DEFAULT_GROUP;
-    }
-    RSGroupInfo rsGroupInfo = groupAdminServer.getRSGroupInfo(groupName);
-    if (rsGroupInfo == null) {
-      throw new ConstraintException("Default RSGroup (" + groupName + ") for this table's "
-          + "namespace does not exist.");
-    }
-    if (!rsGroupInfo.containsTable(desc.getTableName())) {
-      LOG.debug("Pre-moving table " + desc.getTableName() + " to RSGroup " + groupName);
-      groupAdminServer.moveTables(Sets.newHashSet(desc.getTableName()), groupName);
-    }
-  }
-
-  /////////////////////////////////////////////////////////////////////////////
-  // MasterObserver overrides
-  /////////////////////////////////////////////////////////////////////////////
-
-  @Override
-  public void preCreateTableAction(
-      final ObserverContext<MasterCoprocessorEnvironment> ctx,
-      final TableDescriptor desc,
-      final RegionInfo[] regions) throws IOException {
-    if (!desc.getTableName().isSystemTable() && !rsgroupHasServersOnline(desc)) {
-      throw new HBaseIOException("No online servers in the rsgroup, which table " +
-          desc.getTableName().getNameAsString() + " belongs to");
-    }
-  }
-
-  // Assign table to default RSGroup.
-  @Override
-  public void postCreateTable(ObserverContext<MasterCoprocessorEnvironment> ctx,
-      TableDescriptor desc, RegionInfo[] regions) throws IOException {
-    assignTableToGroup(desc);
-  }
-
-  // Remove table from its RSGroup.
-  @Override
-  public void postDeleteTable(ObserverContext<MasterCoprocessorEnvironment> ctx,
-                              TableName tableName) throws IOException {
-    try {
-      RSGroupInfo group = groupAdminServer.getRSGroupInfoOfTable(tableName);
-      if (group != null) {
-        LOG.debug(String.format("Removing deleted table '%s' from rsgroup '%s'", tableName,
-            group.getName()));
-        groupAdminServer.moveTables(Sets.newHashSet(tableName), null);
-      }
-    } catch (IOException ex) {
-      LOG.debug("Failed to perform RSGroup information cleanup for table: " + tableName, ex);
-    }
-  }
-
-  @Override
-  public void preCreateNamespace(ObserverContext<MasterCoprocessorEnvironment> ctx,
-                                 NamespaceDescriptor ns) throws IOException {
-    String group = ns.getConfigurationValue(RSGroupInfo.NAMESPACE_DESC_PROP_GROUP);
-    if(group != null && groupAdminServer.getRSGroupInfo(group) == null) {
-      throw new ConstraintException("Region server group "+group+" does not exit");
-    }
-  }
-
-  @Override
-  public void preModifyNamespace(ObserverContext<MasterCoprocessorEnvironment> ctx,
-      NamespaceDescriptor currentNsDesc, NamespaceDescriptor newNsDesc) throws IOException {
-    preCreateNamespace(ctx, newNsDesc);
-  }
-
-  @Override
-  public void preCloneSnapshot(ObserverContext<MasterCoprocessorEnvironment> ctx,
-      SnapshotDescription snapshot, TableDescriptor desc) throws IOException {
-    assignTableToGroup(desc);
-  }
-
-  @Override
-  public void postClearDeadServers(ObserverContext<MasterCoprocessorEnvironment> ctx,
-      List<ServerName> servers, List<ServerName> notClearedServers)
-      throws IOException {
-    Set<Address> clearedServer = servers.stream().
-        filter(server -> !notClearedServers.contains(server)).
-        map(ServerName::getAddress).
-        collect(Collectors.toSet());
-    if(!clearedServer.isEmpty()) {
-      groupAdminServer.removeServers(clearedServer);
-    }
-  }
-
-  public 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();
-  }
-}
diff --git a/hbase-rsgroup/src/test/resources/hbase-site.xml b/hbase-rsgroup/src/test/resources/hbase-site.xml
deleted file mode 100644
index 99d2ab8..0000000
--- a/hbase-rsgroup/src/test/resources/hbase-site.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-<?xml version="1.0"?>
-<?xml-stylesheet type="text/xsl" href="configuration.xsl"?>
-<!--
-/**
- *
- * 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.
- */
--->
-<configuration>
-  <property>
-    <name>hbase.defaults.for.version.skip</name>
-    <value>true</value>
-  </property>
-  <property>
-    <name>hbase.hconnection.threads.keepalivetime</name>
-    <value>3</value>
-  </property>
-</configuration>
diff --git a/hbase-rsgroup/src/test/resources/log4j.properties b/hbase-rsgroup/src/test/resources/log4j.properties
deleted file mode 100644
index c322699..0000000
--- a/hbase-rsgroup/src/test/resources/log4j.properties
+++ /dev/null
@@ -1,68 +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.
-
-# Define some default values that can be overridden by system properties
-hbase.root.logger=INFO,console
-hbase.log.dir=.
-hbase.log.file=hbase.log
-
-# Define the root logger to the system property "hbase.root.logger".
-log4j.rootLogger=${hbase.root.logger}
-
-# Logging Threshold
-log4j.threshold=ALL
-
-#
-# Daily Rolling File Appender
-#
-log4j.appender.DRFA=org.apache.log4j.DailyRollingFileAppender
-log4j.appender.DRFA.File=${hbase.log.dir}/${hbase.log.file}
-
-# Rollver at midnight
-log4j.appender.DRFA.DatePattern=.yyyy-MM-dd
-
-# 30-day backup
-#log4j.appender.DRFA.MaxBackupIndex=30
-log4j.appender.DRFA.layout=org.apache.log4j.PatternLayout
-# Debugging Pattern format
-log4j.appender.DRFA.layout.ConversionPattern=%d{ISO8601} %-5p [%t] %C{2}(%L): %m%n
-
-
-#
-# console
-# Add "console" to rootlogger above if you want to use this
-#
-log4j.appender.console=org.apache.log4j.ConsoleAppender
-log4j.appender.console.target=System.err
-log4j.appender.console.layout=org.apache.log4j.PatternLayout
-log4j.appender.console.layout.ConversionPattern=%d{ISO8601} %-5p [%t] %C{2}(%L): %m%n
-
-# Custom Logging levels
-
-#log4j.logger.org.apache.hadoop.fs.FSNamesystem=DEBUG
-
-log4j.logger.org.apache.hadoop=WARN
-log4j.logger.org.apache.zookeeper=ERROR
-log4j.logger.org.apache.hadoop.hbase=DEBUG
-
-#These settings are workarounds against spurious logs from the minicluster.
-#See HBASE-4709
-log4j.logger.org.apache.hadoop.metrics2.impl.MetricsConfig=WARN
-log4j.logger.org.apache.hadoop.metrics2.impl.MetricsSinkAdapter=WARN
-log4j.logger.org.apache.hadoop.metrics2.impl.MetricsSystemImpl=WARN
-log4j.logger.org.apache.hadoop.metrics2.util.MBeans=WARN
-# Enable this to get detailed connection error/retry logging.
-# log4j.logger.org.apache.hadoop.hbase.client.ConnectionImplementation=TRACE
diff --git a/hbase-rsgroup/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupAdmin.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupAdmin.java
similarity index 100%
rename from hbase-rsgroup/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupAdmin.java
rename to hbase-server/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupAdmin.java
diff --git a/hbase-rsgroup/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupAdminClient.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupAdminClient.java
similarity index 100%
rename from hbase-rsgroup/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupAdminClient.java
rename to hbase-server/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupAdminClient.java
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
new file mode 100644
index 0000000..2d5af04
--- /dev/null
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupAdminEndpoint.java
@@ -0,0 +1,226 @@
+/**
+ * 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 com.google.protobuf.Service;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.apache.hadoop.hbase.CoprocessorEnvironment;
+import org.apache.hadoop.hbase.HBaseIOException;
+import org.apache.hadoop.hbase.HConstants;
+import org.apache.hadoop.hbase.MasterNotRunningException;
+import org.apache.hadoop.hbase.NamespaceDescriptor;
+import org.apache.hadoop.hbase.PleaseHoldException;
+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.SnapshotDescription;
+import org.apache.hadoop.hbase.client.TableDescriptor;
+import org.apache.hadoop.hbase.constraint.ConstraintException;
+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.coprocessor.MasterCoprocessorEnvironment;
+import org.apache.hadoop.hbase.coprocessor.MasterObserver;
+import org.apache.hadoop.hbase.coprocessor.ObserverContext;
+import org.apache.hadoop.hbase.master.MasterServices;
+import org.apache.hadoop.hbase.net.Address;
+import org.apache.hadoop.hbase.security.UserProvider;
+import org.apache.hadoop.hbase.security.access.AccessChecker;
+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.com.google.common.collect.Sets;
+
+// TODO: Encapsulate MasterObserver functions into separate subclass.
+@CoreCoprocessor
+@InterfaceAudience.Private
+public class RSGroupAdminEndpoint implements MasterCoprocessor, MasterObserver {
+  static final Logger LOG = LoggerFactory.getLogger(RSGroupAdminEndpoint.class);
+
+  private MasterServices master;
+  // Only instance of RSGroupInfoManager. RSGroup aware load balancers ask for this instance on
+  // their setup.
+  private RSGroupInfoManager groupInfoManager;
+  private RSGroupAdminServer groupAdminServer;
+  private RSGroupAdminServiceImpl groupAdminService = new RSGroupAdminServiceImpl();
+
+  @Override
+  public void start(CoprocessorEnvironment env) throws IOException {
+    if (!(env instanceof HasMasterServices)) {
+      throw new IOException("Does not implement HMasterServices");
+    }
+
+    master = ((HasMasterServices) env).getMasterServices();
+    groupInfoManager = RSGroupInfoManagerImpl.getInstance(master);
+    groupAdminServer = new RSGroupAdminServer(master, groupInfoManager);
+    Class<?> clazz =
+        master.getConfiguration().getClass(HConstants.HBASE_MASTER_LOADBALANCER_CLASS, null);
+    if (!RSGroupableBalancer.class.isAssignableFrom(clazz)) {
+      throw new IOException("Configured balancer does not support RegionServer groups.");
+    }
+    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) {
+  }
+
+  @Override
+  public Iterable<Service> getServices() {
+    return Collections.singleton(groupAdminService);
+  }
+
+  @Override
+  public Optional<MasterObserver> getMasterObserver() {
+    return Optional.of(this);
+  }
+
+  RSGroupInfoManager getGroupInfoManager() {
+    return groupInfoManager;
+  }
+
+  @VisibleForTesting
+  RSGroupAdminServiceImpl getGroupAdminService() {
+    return groupAdminService;
+  }
+
+  private void assignTableToGroup(TableDescriptor desc) throws IOException {
+    String groupName =
+        master.getClusterSchema().getNamespace(desc.getTableName().getNamespaceAsString())
+            .getConfigurationValue(RSGroupInfo.NAMESPACE_DESC_PROP_GROUP);
+    if (groupName == null) {
+      groupName = RSGroupInfo.DEFAULT_GROUP;
+    }
+    RSGroupInfo rsGroupInfo = groupAdminServer.getRSGroupInfo(groupName);
+    if (rsGroupInfo == null) {
+      throw new ConstraintException(
+          "Default RSGroup (" + groupName + ") for this table's namespace does not exist.");
+    }
+    if (!rsGroupInfo.containsTable(desc.getTableName())) {
+      LOG.debug("Pre-moving table " + desc.getTableName() + " to RSGroup " + groupName);
+      groupAdminServer.moveTables(Sets.newHashSet(desc.getTableName()), groupName);
+    }
+  }
+
+  /////////////////////////////////////////////////////////////////////////////
+  // MasterObserver overrides
+  /////////////////////////////////////////////////////////////////////////////
+
+  private boolean rsgroupHasServersOnline(TableDescriptor desc) throws IOException {
+    String groupName;
+    try {
+      groupName = master.getClusterSchema().getNamespace(desc.getTableName().getNamespaceAsString())
+          .getConfigurationValue(RSGroupInfo.NAMESPACE_DESC_PROP_GROUP);
+      if (groupName == null) {
+        groupName = RSGroupInfo.DEFAULT_GROUP;
+      }
+    } catch (MasterNotRunningException | PleaseHoldException e) {
+      LOG.info("Master has not initialized yet; temporarily using default RSGroup '" +
+          RSGroupInfo.DEFAULT_GROUP + "' for deploy of system table");
+      groupName = RSGroupInfo.DEFAULT_GROUP;
+    }
+
+    RSGroupInfo rsGroupInfo = groupAdminServer.getRSGroupInfo(groupName);
+    if (rsGroupInfo == null) {
+      throw new ConstraintException(
+          "Default RSGroup (" + groupName + ") for this table's " + "namespace does not exist.");
+    }
+
+    for (ServerName onlineServer : master.getServerManager().createDestinationServersList()) {
+      if (rsGroupInfo.getServers().contains(onlineServer.getAddress())) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  @Override
+  public void preCreateTableAction(final ObserverContext<MasterCoprocessorEnvironment> ctx,
+      final TableDescriptor desc, final RegionInfo[] regions) throws IOException {
+    if (!desc.getTableName().isSystemTable() && !rsgroupHasServersOnline(desc)) {
+      throw new HBaseIOException("No online servers in the rsgroup, which table " +
+          desc.getTableName().getNameAsString() + " belongs to");
+    }
+  }
+
+  // Assign table to default RSGroup.
+  @Override
+  public void postCreateTable(ObserverContext<MasterCoprocessorEnvironment> ctx,
+      TableDescriptor desc, RegionInfo[] regions) throws IOException {
+    assignTableToGroup(desc);
+  }
+
+  // Remove table from its RSGroup.
+  @Override
+  public void postDeleteTable(ObserverContext<MasterCoprocessorEnvironment> ctx,
+      TableName tableName) throws IOException {
+    try {
+      RSGroupInfo group = groupAdminServer.getRSGroupInfoOfTable(tableName);
+      if (group != null) {
+        LOG.debug(String.format("Removing deleted table '%s' from rsgroup '%s'", tableName,
+          group.getName()));
+        groupAdminServer.moveTables(Sets.newHashSet(tableName), null);
+      }
+    } catch (IOException ex) {
+      LOG.debug("Failed to perform RSGroup information cleanup for table: " + tableName, ex);
+    }
+  }
+
+  @Override
+  public void preCreateNamespace(ObserverContext<MasterCoprocessorEnvironment> ctx,
+      NamespaceDescriptor ns) throws IOException {
+    String group = ns.getConfigurationValue(RSGroupInfo.NAMESPACE_DESC_PROP_GROUP);
+    if (group != null && groupAdminServer.getRSGroupInfo(group) == null) {
+      throw new ConstraintException("Region server group " + group + " does not exit");
+    }
+  }
+
+  @Override
+  public void preModifyNamespace(ObserverContext<MasterCoprocessorEnvironment> ctx,
+      NamespaceDescriptor currentNsDesc, NamespaceDescriptor newNsDesc) throws IOException {
+    preCreateNamespace(ctx, newNsDesc);
+  }
+
+  @Override
+  public void preCloneSnapshot(ObserverContext<MasterCoprocessorEnvironment> ctx,
+      SnapshotDescription snapshot, TableDescriptor desc) throws IOException {
+    assignTableToGroup(desc);
+  }
+
+  @Override
+  public void postClearDeadServers(ObserverContext<MasterCoprocessorEnvironment> ctx,
+      List<ServerName> servers, List<ServerName> notClearedServers) throws IOException {
+    Set<Address> clearedServer =
+        servers.stream().filter(server -> !notClearedServers.contains(server))
+            .map(ServerName::getAddress).collect(Collectors.toSet());
+    if (!clearedServer.isEmpty()) {
+      groupAdminServer.removeServers(clearedServer);
+    }
+  }
+}
diff --git a/hbase-rsgroup/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupAdminServer.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupAdminServer.java
similarity index 100%
rename from hbase-rsgroup/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupAdminServer.java
rename to hbase-server/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupAdminServer.java
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
new file mode 100644
index 0000000..918a4fe
--- /dev/null
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupAdminServiceImpl.java
@@ -0,0 +1,378 @@
+/**
+ * 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 com.google.protobuf.RpcCallback;
+import com.google.protobuf.RpcController;
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.Optional;
+import java.util.Set;
+import org.apache.hadoop.hbase.TableName;
+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.HBaseProtos;
+import org.apache.hadoop.hbase.protobuf.generated.RSGroupAdminProtos;
+import org.apache.hadoop.hbase.protobuf.generated.RSGroupAdminProtos.AddRSGroupRequest;
+import org.apache.hadoop.hbase.protobuf.generated.RSGroupAdminProtos.AddRSGroupResponse;
+import org.apache.hadoop.hbase.protobuf.generated.RSGroupAdminProtos.BalanceRSGroupRequest;
+import org.apache.hadoop.hbase.protobuf.generated.RSGroupAdminProtos.BalanceRSGroupResponse;
+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.ListRSGroupInfosResponse;
+import org.apache.hadoop.hbase.protobuf.generated.RSGroupAdminProtos.MoveServersAndTablesRequest;
+import org.apache.hadoop.hbase.protobuf.generated.RSGroupAdminProtos.MoveServersAndTablesResponse;
+import org.apache.hadoop.hbase.protobuf.generated.RSGroupAdminProtos.MoveServersRequest;
+import org.apache.hadoop.hbase.protobuf.generated.RSGroupAdminProtos.MoveServersResponse;
+import org.apache.hadoop.hbase.protobuf.generated.RSGroupAdminProtos.MoveTablesRequest;
+import org.apache.hadoop.hbase.protobuf.generated.RSGroupAdminProtos.MoveTablesResponse;
+import org.apache.hadoop.hbase.protobuf.generated.RSGroupAdminProtos.RemoveRSGroupRequest;
+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.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.
+ */
+class RSGroupAdminServiceImpl extends RSGroupAdminProtos.RSGroupAdminService {
+
+  private MasterServices master;
+
+  private RSGroupAdminServer groupAdminServer;
+
+  private AccessChecker accessChecker;
+
+  /** Provider for mapping principal names to Users */
+  private UserProvider userProvider;
+
+  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();
+  }
+
+  @Override
+  public void getRSGroupInfo(RpcController controller, GetRSGroupInfoRequest request,
+      RpcCallback<GetRSGroupInfoResponse> done) {
+    GetRSGroupInfoResponse.Builder builder = GetRSGroupInfoResponse.newBuilder();
+    String groupName = request.getRSGroupName();
+    RSGroupAdminEndpoint.LOG.info(
+      master.getClientIdAuditPrefix() + " initiates rsgroup info retrieval, group=" + groupName);
+    try {
+      if (master.getMasterCoprocessorHost() != null) {
+        master.getMasterCoprocessorHost().preGetRSGroupInfo(groupName);
+      }
+      checkPermission("getRSGroupInfo");
+      RSGroupInfo rsGroupInfo = groupAdminServer.getRSGroupInfo(groupName);
+      if (rsGroupInfo != null) {
+        builder.setRSGroupInfo(ProtobufUtil.toProtoGroupInfo(rsGroupInfo));
+      }
+      if (master.getMasterCoprocessorHost() != null) {
+        master.getMasterCoprocessorHost().postGetRSGroupInfo(groupName);
+      }
+    } catch (IOException e) {
+      CoprocessorRpcUtils.setControllerException(controller, e);
+    }
+    done.run(builder.build());
+  }
+
+  @Override
+  public void getRSGroupInfoOfTable(RpcController controller, GetRSGroupInfoOfTableRequest request,
+      RpcCallback<GetRSGroupInfoOfTableResponse> done) {
+    GetRSGroupInfoOfTableResponse.Builder builder = GetRSGroupInfoOfTableResponse.newBuilder();
+    TableName tableName = ProtobufUtil.toTableName(request.getTableName());
+    RSGroupAdminEndpoint.LOG.info(
+      master.getClientIdAuditPrefix() + " initiates rsgroup info retrieval, table=" + tableName);
+    try {
+      if (master.getMasterCoprocessorHost() != null) {
+        master.getMasterCoprocessorHost().preGetRSGroupInfoOfTable(tableName);
+      }
+      checkPermission("getRSGroupInfoOfTable");
+      RSGroupInfo RSGroupInfo = groupAdminServer.getRSGroupInfoOfTable(tableName);
+      if (RSGroupInfo != null) {
+        builder.setRSGroupInfo(ProtobufUtil.toProtoGroupInfo(RSGroupInfo));
+      }
+      if (master.getMasterCoprocessorHost() != null) {
+        master.getMasterCoprocessorHost().postGetRSGroupInfoOfTable(tableName);
+      }
+    } catch (IOException e) {
+      CoprocessorRpcUtils.setControllerException(controller, e);
+    }
+    done.run(builder.build());
+  }
+
+  @Override
+  public void moveServers(RpcController controller, MoveServersRequest request,
+      RpcCallback<MoveServersResponse> done) {
+    MoveServersResponse.Builder builder = MoveServersResponse.newBuilder();
+    Set<Address> hostPorts = Sets.newHashSet();
+    for (HBaseProtos.ServerName el : request.getServersList()) {
+      hostPorts.add(Address.fromParts(el.getHostName(), el.getPort()));
+    }
+    RSGroupAdminEndpoint.LOG.info(master.getClientIdAuditPrefix() + " move servers " + hostPorts +
+        " to rsgroup " + request.getTargetGroup());
+    try {
+      if (master.getMasterCoprocessorHost() != null) {
+        master.getMasterCoprocessorHost().preMoveServers(hostPorts, request.getTargetGroup());
+      }
+      checkPermission("moveServers");
+      groupAdminServer.moveServers(hostPorts, request.getTargetGroup());
+      if (master.getMasterCoprocessorHost() != null) {
+        master.getMasterCoprocessorHost().postMoveServers(hostPorts, request.getTargetGroup());
+      }
+    } catch (IOException e) {
+      CoprocessorRpcUtils.setControllerException(controller, e);
+    }
+    done.run(builder.build());
+  }
+
+  @Override
+  public void moveTables(RpcController controller, MoveTablesRequest request,
+      RpcCallback<MoveTablesResponse> done) {
+    MoveTablesResponse.Builder builder = MoveTablesResponse.newBuilder();
+    Set<TableName> tables = new HashSet<>(request.getTableNameList().size());
+    for (HBaseProtos.TableName tableName : request.getTableNameList()) {
+      tables.add(ProtobufUtil.toTableName(tableName));
+    }
+    RSGroupAdminEndpoint.LOG.info(master.getClientIdAuditPrefix() + " move tables " + tables +
+        " to rsgroup " + request.getTargetGroup());
+    try {
+      if (master.getMasterCoprocessorHost() != null) {
+        master.getMasterCoprocessorHost().preMoveTables(tables, request.getTargetGroup());
+      }
+      checkPermission("moveTables");
+      groupAdminServer.moveTables(tables, request.getTargetGroup());
+      if (master.getMasterCoprocessorHost() != null) {
+        master.getMasterCoprocessorHost().postMoveTables(tables, request.getTargetGroup());
+      }
+    } catch (IOException e) {
+      CoprocessorRpcUtils.setControllerException(controller, e);
+    }
+    done.run(builder.build());
+  }
+
+  @Override
+  public void addRSGroup(RpcController controller, AddRSGroupRequest request,
+      RpcCallback<AddRSGroupResponse> done) {
+    AddRSGroupResponse.Builder builder = AddRSGroupResponse.newBuilder();
+    RSGroupAdminEndpoint.LOG
+        .info(master.getClientIdAuditPrefix() + " add rsgroup " + request.getRSGroupName());
+    try {
+      if (master.getMasterCoprocessorHost() != null) {
+        master.getMasterCoprocessorHost().preAddRSGroup(request.getRSGroupName());
+      }
+      checkPermission("addRSGroup");
+      groupAdminServer.addRSGroup(request.getRSGroupName());
+      if (master.getMasterCoprocessorHost() != null) {
+        master.getMasterCoprocessorHost().postAddRSGroup(request.getRSGroupName());
+      }
+    } catch (IOException e) {
+      CoprocessorRpcUtils.setControllerException(controller, e);
+    }
+    done.run(builder.build());
+  }
+
+  @Override
+  public void removeRSGroup(RpcController controller, RemoveRSGroupRequest request,
+      RpcCallback<RemoveRSGroupResponse> done) {
+    RemoveRSGroupResponse.Builder builder = RemoveRSGroupResponse.newBuilder();
+    RSGroupAdminEndpoint.LOG
+        .info(master.getClientIdAuditPrefix() + " remove rsgroup " + request.getRSGroupName());
+    try {
+      if (master.getMasterCoprocessorHost() != null) {
+        master.getMasterCoprocessorHost().preRemoveRSGroup(request.getRSGroupName());
+      }
+      checkPermission("removeRSGroup");
+      groupAdminServer.removeRSGroup(request.getRSGroupName());
+      if (master.getMasterCoprocessorHost() != null) {
+        master.getMasterCoprocessorHost().postRemoveRSGroup(request.getRSGroupName());
+      }
+    } catch (IOException e) {
+      CoprocessorRpcUtils.setControllerException(controller, e);
+    }
+    done.run(builder.build());
+  }
+
+  @Override
+  public void balanceRSGroup(RpcController controller, BalanceRSGroupRequest request,
+      RpcCallback<BalanceRSGroupResponse> done) {
+    BalanceRSGroupResponse.Builder builder = BalanceRSGroupResponse.newBuilder();
+    RSGroupAdminEndpoint.LOG.info(
+      master.getClientIdAuditPrefix() + " balance rsgroup, group=" + request.getRSGroupName());
+    try {
+      if (master.getMasterCoprocessorHost() != null) {
+        master.getMasterCoprocessorHost().preBalanceRSGroup(request.getRSGroupName());
+      }
+      checkPermission("balanceRSGroup");
+      boolean balancerRan = groupAdminServer.balanceRSGroup(request.getRSGroupName());
+      builder.setBalanceRan(balancerRan);
+      if (master.getMasterCoprocessorHost() != null) {
+        master.getMasterCoprocessorHost().postBalanceRSGroup(request.getRSGroupName(), balancerRan);
+      }
+    } catch (IOException e) {
+      CoprocessorRpcUtils.setControllerException(controller, e);
+      builder.setBalanceRan(false);
+    }
+    done.run(builder.build());
+  }
+
+  @Override
+  public void listRSGroupInfos(RpcController controller, ListRSGroupInfosRequest request,
+      RpcCallback<ListRSGroupInfosResponse> done) {
+    ListRSGroupInfosResponse.Builder builder = ListRSGroupInfosResponse.newBuilder();
+    RSGroupAdminEndpoint.LOG.info(master.getClientIdAuditPrefix() + " list rsgroup");
+    try {
+      if (master.getMasterCoprocessorHost() != null) {
+        master.getMasterCoprocessorHost().preListRSGroups();
+      }
+      checkPermission("listRSGroup");
+      for (RSGroupInfo RSGroupInfo : groupAdminServer.listRSGroups()) {
+        builder.addRSGroupInfo(ProtobufUtil.toProtoGroupInfo(RSGroupInfo));
+      }
+      if (master.getMasterCoprocessorHost() != null) {
+        master.getMasterCoprocessorHost().postListRSGroups();
+      }
+    } catch (IOException e) {
+      CoprocessorRpcUtils.setControllerException(controller, e);
+    }
+    done.run(builder.build());
+  }
+
+  @Override
+  public void getRSGroupInfoOfServer(RpcController controller,
+      GetRSGroupInfoOfServerRequest request, RpcCallback<GetRSGroupInfoOfServerResponse> done) {
+    GetRSGroupInfoOfServerResponse.Builder builder = GetRSGroupInfoOfServerResponse.newBuilder();
+    Address hp =
+        Address.fromParts(request.getServer().getHostName(), request.getServer().getPort());
+    RSGroupAdminEndpoint.LOG
+        .info(master.getClientIdAuditPrefix() + " initiates rsgroup info retrieval, server=" + hp);
+    try {
+      if (master.getMasterCoprocessorHost() != null) {
+        master.getMasterCoprocessorHost().preGetRSGroupInfoOfServer(hp);
+      }
+      checkPermission("getRSGroupInfoOfServer");
+      RSGroupInfo info = groupAdminServer.getRSGroupOfServer(hp);
+      if (info != null) {
+        builder.setRSGroupInfo(ProtobufUtil.toProtoGroupInfo(info));
+      }
+      if (master.getMasterCoprocessorHost() != null) {
+        master.getMasterCoprocessorHost().postGetRSGroupInfoOfServer(hp);
+      }
+    } catch (IOException e) {
+      CoprocessorRpcUtils.setControllerException(controller, e);
+    }
+    done.run(builder.build());
+  }
+
+  @Override
+  public void moveServersAndTables(RpcController controller, MoveServersAndTablesRequest request,
+      RpcCallback<MoveServersAndTablesResponse> done) {
+    MoveServersAndTablesResponse.Builder builder = MoveServersAndTablesResponse.newBuilder();
+    Set<Address> hostPorts = Sets.newHashSet();
+    for (HBaseProtos.ServerName el : request.getServersList()) {
+      hostPorts.add(Address.fromParts(el.getHostName(), el.getPort()));
+    }
+    Set<TableName> tables = new HashSet<>(request.getTableNameList().size());
+    for (HBaseProtos.TableName tableName : request.getTableNameList()) {
+      tables.add(ProtobufUtil.toTableName(tableName));
+    }
+    RSGroupAdminEndpoint.LOG.info(master.getClientIdAuditPrefix() + " move servers " + hostPorts +
+        " and tables " + tables + " to rsgroup" + request.getTargetGroup());
+    try {
+      if (master.getMasterCoprocessorHost() != null) {
+        master.getMasterCoprocessorHost().preMoveServersAndTables(hostPorts, tables,
+          request.getTargetGroup());
+      }
+      checkPermission("moveServersAndTables");
+      groupAdminServer.moveServersAndTables(hostPorts, tables, request.getTargetGroup());
+      if (master.getMasterCoprocessorHost() != null) {
+        master.getMasterCoprocessorHost().postMoveServersAndTables(hostPorts, tables,
+          request.getTargetGroup());
+      }
+    } catch (IOException e) {
+      CoprocessorRpcUtils.setControllerException(controller, e);
+    }
+    done.run(builder.build());
+  }
+
+  @Override
+  public void removeServers(RpcController controller, RemoveServersRequest request,
+      RpcCallback<RemoveServersResponse> done) {
+    RemoveServersResponse.Builder builder = RemoveServersResponse.newBuilder();
+    Set<Address> servers = Sets.newHashSet();
+    for (HBaseProtos.ServerName el : request.getServersList()) {
+      servers.add(Address.fromParts(el.getHostName(), el.getPort()));
+    }
+    RSGroupAdminEndpoint.LOG.info(
+      master.getClientIdAuditPrefix() + " remove decommissioned servers from rsgroup: " + servers);
+    try {
+      if (master.getMasterCoprocessorHost() != null) {
+        master.getMasterCoprocessorHost().preRemoveServers(servers);
+      }
+      checkPermission("removeServers");
+      groupAdminServer.removeServers(servers);
+      if (master.getMasterCoprocessorHost() != null) {
+        master.getMasterCoprocessorHost().postRemoveServers(servers);
+      }
+    } catch (IOException e) {
+      CoprocessorRpcUtils.setControllerException(controller, e);
+    }
+    done.run(builder.build());
+  }
+}
\ No newline at end of file
diff --git a/hbase-rsgroup/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupBasedLoadBalancer.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupBasedLoadBalancer.java
similarity index 97%
rename from hbase-rsgroup/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupBasedLoadBalancer.java
rename to hbase-server/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupBasedLoadBalancer.java
index 7394f55..be76805 100644
--- a/hbase-rsgroup/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupBasedLoadBalancer.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupBasedLoadBalancer.java
@@ -15,7 +15,6 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 package org.apache.hadoop.hbase.rsgroup;
 
 import java.io.IOException;
@@ -56,18 +55,17 @@ import org.apache.hbase.thirdparty.com.google.common.collect.Lists;
 import org.apache.hbase.thirdparty.com.google.common.collect.Maps;
 
 /**
- * GroupBasedLoadBalancer, used when Region Server Grouping is configured (HBase-6721)
- * It does region balance based on a table's group membership.
- *
- * Most assignment methods contain two exclusive code paths: Online - when the group
- * table is online and Offline - when it is unavailable.
- *
- * During Offline, assignments are assigned based on cached information in zookeeper.
- * If unavailable (ie bootstrap) then regions are assigned randomly.
- *
- * Once the GROUP table has been assigned, the balancer switches to Online and will then
- * start providing appropriate assignments for user tables.
- *
+ * GroupBasedLoadBalancer, used when Region Server Grouping is configured (HBase-6721) It does
+ * region balance based on a table's group membership.
+ * <p/>
+ * Most assignment methods contain two exclusive code paths: Online - when the group table is online
+ * and Offline - when it is unavailable.
+ * <p/>
+ * During Offline, assignments are assigned based on cached information in zookeeper. If unavailable
+ * (ie bootstrap) then regions are assigned randomly.
+ * <p/>
+ * Once the GROUP table has been assigned, the balancer switches to Online and will then start
+ * providing appropriate assignments for user tables.
  */
 @InterfaceAudience.Private
 public class RSGroupBasedLoadBalancer implements RSGroupableBalancer {
diff --git a/hbase-rsgroup/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupMajorCompactionTTL.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupMajorCompactionTTL.java
similarity index 100%
rename from hbase-rsgroup/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupMajorCompactionTTL.java
rename to hbase-server/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupMajorCompactionTTL.java
diff --git a/hbase-rsgroup/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupableBalancer.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupableBalancer.java
similarity index 100%
rename from hbase-rsgroup/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupableBalancer.java
rename to hbase-server/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupableBalancer.java
diff --git a/hbase-rsgroup/src/test/java/org/apache/hadoop/hbase/master/balancer/RSGroupableBalancerTestBase.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/balancer/RSGroupableBalancerTestBase.java
similarity index 100%
rename from hbase-rsgroup/src/test/java/org/apache/hadoop/hbase/master/balancer/RSGroupableBalancerTestBase.java
rename to hbase-server/src/test/java/org/apache/hadoop/hbase/master/balancer/RSGroupableBalancerTestBase.java
diff --git a/hbase-rsgroup/src/test/java/org/apache/hadoop/hbase/master/balancer/TestRSGroupBasedLoadBalancer.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/balancer/TestRSGroupBasedLoadBalancer.java
similarity index 100%
rename from hbase-rsgroup/src/test/java/org/apache/hadoop/hbase/master/balancer/TestRSGroupBasedLoadBalancer.java
rename to hbase-server/src/test/java/org/apache/hadoop/hbase/master/balancer/TestRSGroupBasedLoadBalancer.java
diff --git a/hbase-rsgroup/src/test/java/org/apache/hadoop/hbase/master/balancer/TestRSGroupBasedLoadBalancerWithStochasticLoadBalancerAsInternal.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/balancer/TestRSGroupBasedLoadBalancerWithStochasticLoadBalancerAsInternal.java
similarity index 100%
rename from hbase-rsgroup/src/test/java/org/apache/hadoop/hbase/master/balancer/TestRSGroupBasedLoadBalancerWithStochasticLoadBalancerAsInternal.java
rename to hbase-server/src/test/java/org/apache/hadoop/hbase/master/balancer/TestRSGroupBasedLoadBalancerWithStochasticLoadBalancerAsInternal.java
diff --git a/hbase-rsgroup/src/test/java/org/apache/hadoop/hbase/master/procedure/TestSCPWithReplicasWithRSGroup.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/procedure/TestSCPWithReplicasWithRSGroup.java
similarity index 100%
rename from hbase-rsgroup/src/test/java/org/apache/hadoop/hbase/master/procedure/TestSCPWithReplicasWithRSGroup.java
rename to hbase-server/src/test/java/org/apache/hadoop/hbase/master/procedure/TestSCPWithReplicasWithRSGroup.java
diff --git a/hbase-rsgroup/src/test/java/org/apache/hadoop/hbase/rsgroup/TestEnableRSGroups.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestEnableRSGroups.java
similarity index 100%
rename from hbase-rsgroup/src/test/java/org/apache/hadoop/hbase/rsgroup/TestEnableRSGroups.java
rename to hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestEnableRSGroups.java
diff --git a/hbase-rsgroup/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupMajorCompactionTTL.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupMajorCompactionTTL.java
similarity index 100%
rename from hbase-rsgroup/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupMajorCompactionTTL.java
rename to hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupMajorCompactionTTL.java
diff --git a/hbase-rsgroup/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsAdmin1.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsAdmin1.java
similarity index 100%
rename from hbase-rsgroup/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsAdmin1.java
rename to hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsAdmin1.java
diff --git a/hbase-rsgroup/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsAdmin2.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsAdmin2.java
similarity index 100%
rename from hbase-rsgroup/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsAdmin2.java
rename to hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsAdmin2.java
diff --git a/hbase-rsgroup/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsBalance.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsBalance.java
similarity index 100%
rename from hbase-rsgroup/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsBalance.java
rename to hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsBalance.java
diff --git a/hbase-rsgroup/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsBase.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsBase.java
similarity index 100%
rename from hbase-rsgroup/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsBase.java
rename to hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsBase.java
diff --git a/hbase-rsgroup/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsBasics.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsBasics.java
similarity index 100%
rename from hbase-rsgroup/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsBasics.java
rename to hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsBasics.java
diff --git a/hbase-rsgroup/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsKillRS.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsKillRS.java
similarity index 100%
rename from hbase-rsgroup/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsKillRS.java
rename to hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsKillRS.java
diff --git a/hbase-rsgroup/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsOfflineMode.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsOfflineMode.java
similarity index 100%
rename from hbase-rsgroup/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsOfflineMode.java
rename to hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsOfflineMode.java
diff --git a/hbase-rsgroup/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsWithACL.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsWithACL.java
similarity index 81%
rename from hbase-rsgroup/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsWithACL.java
rename to hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsWithACL.java
index 0278e3c..c1c157a 100644
--- a/hbase-rsgroup/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsWithACL.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsWithACL.java
@@ -39,7 +39,6 @@ import org.apache.hadoop.hbase.security.access.SecureTestUtil;
 import org.apache.hadoop.hbase.testclassification.MediumTests;
 import org.apache.hadoop.hbase.testclassification.SecurityTests;
 import org.apache.hadoop.hbase.util.Bytes;
-
 import org.junit.AfterClass;
 import org.junit.BeforeClass;
 import org.junit.ClassRule;
@@ -49,11 +48,11 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 /**
- * Performs authorization checks for rsgroup operations, according to different
- * levels of authorized users.
+ * Performs authorization checks for rsgroup operations, according to different levels of authorized
+ * users.
  */
-@Category({SecurityTests.class, MediumTests.class})
-public class TestRSGroupsWithACL extends SecureTestUtil{
+@Category({ SecurityTests.class, MediumTests.class })
+public class TestRSGroupsWithACL extends SecureTestUtil {
 
   @ClassRule
   public static final HBaseClassTestRule CLASS_RULE =
@@ -98,8 +97,7 @@ 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());
+    conf.set(HConstants.HBASE_MASTER_LOADBALANCER_CLASS, RSGroupBasedLoadBalancer.class.getName());
     // Enable security
     enableSecurity(conf);
     // Verify enableSecurity sets up what we require
@@ -108,8 +106,8 @@ public class TestRSGroupsWithACL extends SecureTestUtil{
     configureRSGroupAdminEndpoint(conf);
 
     TEST_UTIL.startMiniCluster();
-    rsGroupAdminEndpoint = (RSGroupAdminEndpoint) TEST_UTIL.getMiniHBaseCluster().getMaster().
-        getMasterCoprocessorHost().findCoprocessor(RSGroupAdminEndpoint.class.getName());
+    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);
 
@@ -141,31 +139,21 @@ public class TestRSGroupsWithACL extends SecureTestUtil{
     cfd.setMaxVersions(100);
     tableBuilder.setColumnFamily(cfd.build());
     tableBuilder.setValue(TableDescriptorBuilder.OWNER, USER_OWNER.getShortName());
-    createTable(TEST_UTIL, tableBuilder.build(),
-        new byte[][] { Bytes.toBytes("s") });
+    createTable(TEST_UTIL, tableBuilder.build(), new byte[][] { Bytes.toBytes("s") });
 
     // Set up initial grants
-    grantGlobal(TEST_UTIL, USER_ADMIN.getShortName(),
-        Permission.Action.ADMIN,
-        Permission.Action.CREATE,
-        Permission.Action.READ,
-        Permission.Action.WRITE);
+    grantGlobal(TEST_UTIL, USER_ADMIN.getShortName(), Permission.Action.ADMIN,
+      Permission.Action.CREATE, Permission.Action.READ, Permission.Action.WRITE);
 
-    grantOnTable(TEST_UTIL, USER_RW.getShortName(),
-        TEST_TABLE, TEST_FAMILY, null,
-        Permission.Action.READ,
-        Permission.Action.WRITE);
+    grantOnTable(TEST_UTIL, USER_RW.getShortName(), TEST_TABLE, TEST_FAMILY, null,
+      Permission.Action.READ, Permission.Action.WRITE);
 
     // USER_CREATE is USER_RW plus CREATE permissions
-    grantOnTable(TEST_UTIL, USER_CREATE.getShortName(),
-        TEST_TABLE, null, null,
-        Permission.Action.CREATE,
-        Permission.Action.READ,
-        Permission.Action.WRITE);
+    grantOnTable(TEST_UTIL, USER_CREATE.getShortName(), TEST_TABLE, null, null,
+      Permission.Action.CREATE, Permission.Action.READ, Permission.Action.WRITE);
 
-    grantOnTable(TEST_UTIL, USER_RO.getShortName(),
-        TEST_TABLE, TEST_FAMILY, null,
-        Permission.Action.READ);
+    grantOnTable(TEST_UTIL, USER_RO.getShortName(), TEST_TABLE, TEST_FAMILY, null,
+      Permission.Action.READ);
 
     grantGlobal(TEST_UTIL, toGroupEntry(GROUP_ADMIN), Permission.Action.ADMIN);
     grantGlobal(TEST_UTIL, toGroupEntry(GROUP_CREATE), Permission.Action.CREATE);
@@ -174,8 +162,8 @@ public class TestRSGroupsWithACL extends SecureTestUtil{
 
     assertEquals(4, PermissionStorage.getTablePermissions(conf, TEST_TABLE).size());
     try {
-      assertEquals(4, AccessControlClient.getUserPermissions(systemUserConnection,
-          TEST_TABLE.toString()).size());
+      assertEquals(4,
+        AccessControlClient.getUserPermissions(systemUserConnection, TEST_TABLE.toString()).size());
     } catch (AssertionError e) {
       fail(e.getMessage());
     } catch (Throwable e) {
@@ -210,14 +198,13 @@ public class TestRSGroupsWithACL extends SecureTestUtil{
       coprocessors += "," + currentCoprocessors;
     }
     conf.set(CoprocessorHost.MASTER_COPROCESSOR_CONF_KEY, coprocessors);
-    conf.set(HConstants.HBASE_MASTER_LOADBALANCER_CLASS,
-        RSGroupBasedLoadBalancer.class.getName());
+    conf.set(HConstants.HBASE_MASTER_LOADBALANCER_CLASS, RSGroupBasedLoadBalancer.class.getName());
   }
 
   @Test
   public void testGetRSGroupInfo() throws Exception {
     AccessTestAction action = () -> {
-      rsGroupAdminEndpoint.checkPermission("getRSGroupInfo");
+      rsGroupAdminEndpoint.getGroupAdminService().checkPermission("getRSGroupInfo");
       return null;
     };
 
@@ -227,7 +214,7 @@ public class TestRSGroupsWithACL extends SecureTestUtil{
   @Test
   public void testGetRSGroupInfoOfTable() throws Exception {
     AccessTestAction action = () -> {
-      rsGroupAdminEndpoint.checkPermission("getRSGroupInfoOfTable");
+      rsGroupAdminEndpoint.getGroupAdminService().checkPermission("getRSGroupInfoOfTable");
       return null;
     };
 
@@ -237,7 +224,7 @@ public class TestRSGroupsWithACL extends SecureTestUtil{
   @Test
   public void testMoveServers() throws Exception {
     AccessTestAction action = () -> {
-      rsGroupAdminEndpoint.checkPermission("moveServers");
+      rsGroupAdminEndpoint.getGroupAdminService().checkPermission("moveServers");
       return null;
     };
 
@@ -247,7 +234,7 @@ public class TestRSGroupsWithACL extends SecureTestUtil{
   @Test
   public void testMoveTables() throws Exception {
     AccessTestAction action = () -> {
-      rsGroupAdminEndpoint.checkPermission("moveTables");
+      rsGroupAdminEndpoint.getGroupAdminService().checkPermission("moveTables");
       return null;
     };
 
@@ -257,7 +244,7 @@ public class TestRSGroupsWithACL extends SecureTestUtil{
   @Test
   public void testAddRSGroup() throws Exception {
     AccessTestAction action = () -> {
-      rsGroupAdminEndpoint.checkPermission("addRSGroup");
+      rsGroupAdminEndpoint.getGroupAdminService().checkPermission("addRSGroup");
       return null;
     };
 
@@ -267,7 +254,7 @@ public class TestRSGroupsWithACL extends SecureTestUtil{
   @Test
   public void testRemoveRSGroup() throws Exception {
     AccessTestAction action = () -> {
-      rsGroupAdminEndpoint.checkPermission("removeRSGroup");
+      rsGroupAdminEndpoint.getGroupAdminService().checkPermission("removeRSGroup");
       return null;
     };
 
@@ -277,7 +264,7 @@ public class TestRSGroupsWithACL extends SecureTestUtil{
   @Test
   public void testBalanceRSGroup() throws Exception {
     AccessTestAction action = () -> {
-      rsGroupAdminEndpoint.checkPermission("balanceRSGroup");
+      rsGroupAdminEndpoint.getGroupAdminService().checkPermission("balanceRSGroup");
       return null;
     };
 
@@ -287,7 +274,7 @@ public class TestRSGroupsWithACL extends SecureTestUtil{
   @Test
   public void testListRSGroup() throws Exception {
     AccessTestAction action = () -> {
-      rsGroupAdminEndpoint.checkPermission("listRSGroup");
+      rsGroupAdminEndpoint.getGroupAdminService().checkPermission("listRSGroup");
       return null;
     };
 
@@ -297,7 +284,7 @@ public class TestRSGroupsWithACL extends SecureTestUtil{
   @Test
   public void testGetRSGroupInfoOfServer() throws Exception {
     AccessTestAction action = () -> {
-      rsGroupAdminEndpoint.checkPermission("getRSGroupInfoOfServer");
+      rsGroupAdminEndpoint.getGroupAdminService().checkPermission("getRSGroupInfoOfServer");
       return null;
     };
 
@@ -307,7 +294,7 @@ public class TestRSGroupsWithACL extends SecureTestUtil{
   @Test
   public void testMoveServersAndTables() throws Exception {
     AccessTestAction action = () -> {
-      rsGroupAdminEndpoint.checkPermission("moveServersAndTables");
+      rsGroupAdminEndpoint.getGroupAdminService().checkPermission("moveServersAndTables");
       return null;
     };
 
@@ -317,7 +304,7 @@ public class TestRSGroupsWithACL extends SecureTestUtil{
   @Test
   public void testRemoveServers() throws Exception {
     AccessTestAction action = () -> {
-      rsGroupAdminEndpoint.checkPermission("removeServers");
+      rsGroupAdminEndpoint.getGroupAdminService().checkPermission("removeServers");
       return null;
     };
 
@@ -326,7 +313,7 @@ public class TestRSGroupsWithACL extends SecureTestUtil{
 
   private void validateAdminPermissions(AccessTestAction action) throws Exception {
     verifyAllowed(action, SUPERUSER, USER_ADMIN, USER_GROUP_ADMIN);
-    verifyDenied(action, USER_CREATE, USER_OWNER, USER_RW, USER_RO,
-        USER_NONE, USER_GROUP_READ, USER_GROUP_WRITE, USER_GROUP_CREATE);
+    verifyDenied(action, USER_CREATE, USER_OWNER, USER_RW, USER_RO, USER_NONE, USER_GROUP_READ,
+      USER_GROUP_WRITE, USER_GROUP_CREATE);
   }
 }
diff --git a/hbase-rsgroup/src/test/java/org/apache/hadoop/hbase/rsgroup/VerifyingRSGroupAdminClient.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/VerifyingRSGroupAdminClient.java
similarity index 100%
rename from hbase-rsgroup/src/test/java/org/apache/hadoop/hbase/rsgroup/VerifyingRSGroupAdminClient.java
rename to hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/VerifyingRSGroupAdminClient.java
diff --git a/hbase-shell/pom.xml b/hbase-shell/pom.xml
index 7483da5..a6b62ad 100644
--- a/hbase-shell/pom.xml
+++ b/hbase-shell/pom.xml
@@ -187,41 +187,6 @@
     </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>
-        </dependency>
-      </dependencies>
-      <build>
-        <plugins>
-          <plugin>
-            <groupId>org.codehaus.mojo</groupId>
-            <artifactId>build-helper-maven-plugin</artifactId>
-            <executions>
-              <execution>
-                <id>add-test-source</id>
-                <goals>
-                  <goal>add-test-source</goal>
-                </goals>
-                <configuration>
-                  <sources>
-                    <source>src/test/rsgroup</source>
-                  </sources>
-                </configuration>
-              </execution>
-            </executions>
-          </plugin>
-        </plugins>
-      </build>
-    </profile>
     <!-- Skip the tests in this module -->
     <profile>
       <id>skipShellTests</id>
diff --git a/pom.xml b/pom.xml
index 8e86b9c..b68ddf6 100755
--- a/pom.xml
+++ b/pom.xml
@@ -1572,7 +1572,6 @@
     <procedure.test.jar>hbase-procedure-${project.version}-tests.jar</procedure.test.jar>
     <it.test.jar>hbase-it-${project.version}-tests.jar</it.test.jar>
     <annotations.test.jar>hbase-annotations-${project.version}-tests.jar</annotations.test.jar>
-    <rsgroup.test.jar>hbase-rsgroup-${project.version}-tests.jar</rsgroup.test.jar>
     <mapreduce.test.jar>hbase-mapreduce-${project.version}-tests.jar</mapreduce.test.jar>
     <zookeeper.test.jar>hbase-zookeeper-${project.version}-tests.jar</zookeeper.test.jar>
     <shell-executable>bash</shell-executable>
@@ -1709,18 +1708,6 @@
         <scope>test</scope>
       </dependency>
       <dependency>
-        <artifactId>hbase-rsgroup</artifactId>
-        <groupId>org.apache.hbase</groupId>
-        <version>${project.version}</version>
-      </dependency>
-      <dependency>
-        <artifactId>hbase-rsgroup</artifactId>
-        <groupId>org.apache.hbase</groupId>
-        <version>${project.version}</version>
-        <type>test-jar</type>
-        <scope>test</scope>
-      </dependency>
-      <dependency>
         <artifactId>hbase-replication</artifactId>
         <groupId>org.apache.hbase</groupId>
         <version>${project.version}</version>
@@ -2383,17 +2370,6 @@
   -->
   <profiles>
     <profile>
-      <id>rsgroup</id>
-      <activation>
-        <property>
-            <name>!skip-rsgroup</name>
-        </property>
-      </activation>
-      <modules>
-        <module>hbase-rsgroup</module>
-      </modules>
-    </profile>
-    <profile>
       <id>build-with-jdk8</id>
       <activation>
         <jdk>[1.8,)</jdk>


[hbase] 10/21: HBASE-22971 Deprecated RSGroupAdminEndpoint and make RSGroup feature always enabled (#595)

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

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

commit b10b39ad0365b378bbf7a493c76501c77f739428
Author: Duo Zhang <zh...@apache.org>
AuthorDate: Tue Sep 24 12:09:12 2019 +0800

    HBASE-22971 Deprecated RSGroupAdminEndpoint and make RSGroup feature always enabled (#595)
    
    Signed-off-by: Guanghao Zhang <zg...@apache.org>
---
 .../hbase/favored/FavoredNodeLoadBalancer.java     |   1 +
 .../hadoop/hbase/favored/FavoredNodesPromoter.java |   2 +
 .../org/apache/hadoop/hbase/master/HMaster.java    |  43 +++----
 .../apache/hadoop/hbase/master/LoadBalancer.java   |   9 ++
 .../hadoop/hbase/master/MasterRpcServices.java     |  23 ++--
 .../apache/hadoop/hbase/master/MasterServices.java |   2 +-
 .../hbase/master/assignment/AssignmentManager.java |  15 ++-
 .../master/balancer/FavoredStochasticBalancer.java |   1 +
 .../hbase/master/balancer/LoadBalancerFactory.java |  18 ++-
 .../AbstractStateMachineNamespaceProcedure.java    |  11 ++
 .../master/procedure/CreateNamespaceProcedure.java |   1 +
 .../master/procedure/CreateTableProcedure.java     |  27 +++-
 .../master/procedure/MasterProcedureUtil.java      |  50 +++++++-
 .../master/procedure/ModifyNamespaceProcedure.java |  19 ++-
 .../master/procedure/ModifyTableProcedure.java     |  13 +-
 .../hadoop/hbase/rsgroup/RSGroupAdminEndpoint.java | 141 +--------------------
 .../hbase/rsgroup/RSGroupBasedLoadBalancer.java    |  65 ++++++----
 .../hbase/rsgroup/RSGroupInfoManagerImpl.java      |   2 +-
 .../hadoop/hbase/rsgroup/RSGroupableBalancer.java  |  32 -----
 .../org/apache/hadoop/hbase/TestNamespace.java     |   2 +-
 .../hadoop/hbase/master/AbstractTestDLS.java       |   4 +-
 .../hbase/master/MockNoopMasterServices.java       |   2 +-
 .../hadoop/hbase/master/TestClusterRestart.java    |   6 +-
 .../hadoop/hbase/master/TestMasterMetrics.java     |   4 +-
 .../TestMasterRestartAfterDisablingTable.java      |   4 +-
 .../hadoop/hbase/master/TestRollingRestart.java    |   2 +-
 .../TestFavoredStochasticBalancerPickers.java      |   2 +-
 .../hadoop/hbase/regionserver/TestRegionOpen.java  |   2 +-
 .../TestRegionReplicasWithRestartScenarios.java    |   2 +-
 .../regionserver/TestRegionServerMetrics.java      |   4 +-
 .../hbase/rsgroup/TestRSGroupsOfflineMode.java     |   3 +-
 .../security/access/TestTablePermissions.java      |   3 +-
 .../hbase/util/TestHBaseFsckReplication.java       |   2 +
 33 files changed, 240 insertions(+), 277 deletions(-)

diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/favored/FavoredNodeLoadBalancer.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/favored/FavoredNodeLoadBalancer.java
index b8d4b09..52a37a2 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/favored/FavoredNodeLoadBalancer.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/favored/FavoredNodeLoadBalancer.java
@@ -321,6 +321,7 @@ public class FavoredNodeLoadBalancer extends BaseLoadBalancer implements Favored
     regionsOnServer.add(region);
   }
 
+  @Override
   public synchronized List<ServerName> getFavoredNodes(RegionInfo regionInfo) {
     return this.fnm.getFavoredNodes(regionInfo);
   }
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/favored/FavoredNodesPromoter.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/favored/FavoredNodesPromoter.java
index 322eb1df..a24fce0 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/favored/FavoredNodesPromoter.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/favored/FavoredNodesPromoter.java
@@ -35,4 +35,6 @@ public interface FavoredNodesPromoter {
 
   void generateFavoredNodesForMergedRegion(RegionInfo merged, RegionInfo [] mergeParents)
       throws IOException;
+
+  List<ServerName> getFavoredNodes(RegionInfo regionInfo);
 }
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 2472e34..d3a4c95 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
@@ -93,7 +93,6 @@ import org.apache.hadoop.hbase.coprocessor.CoprocessorHost;
 import org.apache.hadoop.hbase.exceptions.DeserializationException;
 import org.apache.hadoop.hbase.executor.ExecutorType;
 import org.apache.hadoop.hbase.favored.FavoredNodesManager;
-import org.apache.hadoop.hbase.favored.FavoredNodesPromoter;
 import org.apache.hadoop.hbase.http.InfoServer;
 import org.apache.hadoop.hbase.ipc.CoprocessorRpcUtils;
 import org.apache.hadoop.hbase.ipc.RpcServer;
@@ -106,7 +105,6 @@ import org.apache.hadoop.hbase.master.assignment.RegionStateNode;
 import org.apache.hadoop.hbase.master.assignment.RegionStates;
 import org.apache.hadoop.hbase.master.assignment.TransitRegionStateProcedure;
 import org.apache.hadoop.hbase.master.balancer.BalancerChore;
-import org.apache.hadoop.hbase.master.balancer.BaseLoadBalancer;
 import org.apache.hadoop.hbase.master.balancer.ClusterStatusChore;
 import org.apache.hadoop.hbase.master.balancer.LoadBalancerFactory;
 import org.apache.hadoop.hbase.master.cleaner.DirScanPool;
@@ -188,6 +186,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.RSGroupBasedLoadBalancer;
 import org.apache.hadoop.hbase.rsgroup.RSGroupInfoManager;
 import org.apache.hadoop.hbase.security.AccessDeniedException;
 import org.apache.hadoop.hbase.security.SecurityConstants;
@@ -393,7 +392,7 @@ public class HMaster extends HRegionServer implements MasterServices {
 
   private final LockManager lockManager = new LockManager(this);
 
-  private LoadBalancer balancer;
+  private RSGroupBasedLoadBalancer balancer;
   private RegionNormalizer normalizer;
   private BalancerChore balancerChore;
   private RegionNormalizerChore normalizerChore;
@@ -450,9 +449,6 @@ public class HMaster extends HRegionServer implements MasterServices {
   private long splitPlanCount;
   private long mergePlanCount;
 
-  /* Handle favored nodes information */
-  private FavoredNodesManager favoredNodesManager;
-
   /** jetty server for master to redirect requests to regionserver infoServer */
   private Server masterJettyServer;
 
@@ -790,7 +786,8 @@ public class HMaster extends HRegionServer implements MasterServices {
   @VisibleForTesting
   protected void initializeZKBasedSystemTrackers()
       throws IOException, InterruptedException, KeeperException, ReplicationException {
-    this.balancer = LoadBalancerFactory.getLoadBalancer(conf);
+    this.balancer = new RSGroupBasedLoadBalancer();
+    this.balancer.setConf(conf);
     this.normalizer = RegionNormalizerFactory.getRegionNormalizer(conf);
     this.normalizer.setMasterServices(this);
     this.normalizer.setMasterRpcServices((MasterRpcServices)rpcServices);
@@ -1037,9 +1034,6 @@ public class HMaster extends HRegionServer implements MasterServices {
         return temp;
       });
     }
-    if (this.balancer instanceof FavoredNodesPromoter) {
-      favoredNodesManager = new FavoredNodesManager(this);
-    }
 
     // initialize load balancer
     this.balancer.setMasterServices(this);
@@ -1089,11 +1083,11 @@ public class HMaster extends HRegionServer implements MasterServices {
     // table states messing up master launch (namespace table, etc., are not assigned).
     this.assignmentManager.processOfflineRegions();
     // Initialize after meta is up as below scans meta
-    if (favoredNodesManager != null && !maintenanceMode) {
+    if (getFavoredNodesManager() != null && !maintenanceMode) {
       SnapshotOfRegionAssignmentFromMeta snapshotOfRegionAssignment =
           new SnapshotOfRegionAssignmentFromMeta(getConnection());
       snapshotOfRegionAssignment.initialize();
-      favoredNodesManager.initialize(snapshotOfRegionAssignment);
+      getFavoredNodesManager().initialize(snapshotOfRegionAssignment);
     }
 
     // set cluster status again after user regions are assigned
@@ -2055,14 +2049,13 @@ public class HMaster extends HRegionServer implements MasterServices {
         LOG.debug("Unable to determine a plan to assign " + hri);
         return;
       }
-      // TODO: What is this? I don't get it.
-      if (dest.equals(serverName) && balancer instanceof BaseLoadBalancer
-          && !((BaseLoadBalancer)balancer).shouldBeOnMaster(hri)) {
+      // TODO: deal with table on master for rs group.
+      if (dest.equals(serverName)) {
         // To avoid unnecessary region moving later by balancer. Don't put user
         // regions on master.
-        LOG.debug("Skipping move of region " + hri.getRegionNameAsString()
-          + " to avoid unnecessary region moving later by load balancer,"
-          + " because it should not be on master");
+        LOG.debug("Skipping move of region " + hri.getRegionNameAsString() +
+          " to avoid unnecessary region moving later by load balancer," +
+          " because it should not be on master");
         return;
       }
     }
@@ -3505,12 +3498,14 @@ public class HMaster extends HRegionServer implements MasterServices {
 
   /**
    * Fetch the configured {@link LoadBalancer} class name. If none is set, a default is returned.
-   *
+   * <p/>
+   * Notice that, the base load balancer will always be {@link RSGroupBasedLoadBalancer} now, so
+   * this method will return the balancer used inside each rs group.
    * @return The name of the {@link LoadBalancer} in use.
    */
   public String getLoadBalancerClassName() {
-    return conf.get(HConstants.HBASE_MASTER_LOADBALANCER_CLASS, LoadBalancerFactory
-        .getDefaultLoadBalancerClass().getName());
+    return conf.get(HConstants.HBASE_MASTER_LOADBALANCER_CLASS,
+      LoadBalancerFactory.getDefaultLoadBalancerClass().getName());
   }
 
   /**
@@ -3525,13 +3520,13 @@ public class HMaster extends HRegionServer implements MasterServices {
   }
 
   @Override
-  public LoadBalancer getLoadBalancer() {
+  public RSGroupBasedLoadBalancer getLoadBalancer() {
     return balancer;
   }
 
   @Override
   public FavoredNodesManager getFavoredNodesManager() {
-    return favoredNodesManager;
+    return balancer.getFavoredNodesManager();
   }
 
   private long executePeerProcedure(AbstractPeerProcedure<?> procedure) throws IOException {
@@ -3873,7 +3868,7 @@ public class HMaster extends HRegionServer implements MasterServices {
   }
 
   @Override
-  public RSGroupInfoManager getRSRSGroupInfoManager() {
+  public RSGroupInfoManager getRSGroupInfoManager() {
     return rsGroupInfoManager;
   }
 }
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/LoadBalancer.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/LoadBalancer.java
index 0fc544a..d5ca1f7 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/LoadBalancer.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/LoadBalancer.java
@@ -65,6 +65,15 @@ public interface LoadBalancer extends Configurable, Stoppable, ConfigurationObse
   ServerName BOGUS_SERVER_NAME = ServerName.valueOf("localhost,1,1");
 
   /**
+   * Config for pluggable load balancers.
+   * @deprecated since 3.0.0, will be removed in 4.0.0. In the new implementation, as the base load
+   *             balancer will always be the rs group based one, you should just use
+   *             {@link org.apache.hadoop.hbase.HConstants#HBASE_MASTER_LOADBALANCER_CLASS} to
+   *             config the per group load balancer.
+   */
+  @Deprecated
+  String HBASE_RSGROUP_LOADBALANCER_CLASS = "hbase.rsgroup.grouploadbalancer.class";
+  /**
    * Set the current cluster status. This allows a LoadBalancer to map host name to a server
    */
   void setClusterMetrics(ClusterMetrics st);
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 ab07bf0..6d5a7a0 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
@@ -76,6 +76,7 @@ import org.apache.hadoop.hbase.master.procedure.MasterProcedureUtil;
 import org.apache.hadoop.hbase.master.procedure.MasterProcedureUtil.NonceProcedureRunnable;
 import org.apache.hadoop.hbase.master.procedure.ServerCrashProcedure;
 import org.apache.hadoop.hbase.mob.MobUtils;
+import org.apache.hadoop.hbase.net.Address;
 import org.apache.hadoop.hbase.procedure.MasterProcedureManager;
 import org.apache.hadoop.hbase.procedure2.LockType;
 import org.apache.hadoop.hbase.procedure2.LockedResource;
@@ -230,10 +231,8 @@ import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.IsNormaliz
 import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.IsNormalizerEnabledResponse;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.IsProcedureDoneRequest;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.IsProcedureDoneResponse;
-import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos
-    .IsSnapshotCleanupEnabledRequest;
-import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos
-    .IsSnapshotCleanupEnabledResponse;
+import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.IsSnapshotCleanupEnabledRequest;
+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;
@@ -287,10 +286,8 @@ import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.SetNormali
 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.SetRegionStateInMetaRequest;
-import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos
-    .SetSnapshotCleanupRequest;
-import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos
-    .SetSnapshotCleanupResponse;
+import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.SetSnapshotCleanupRequest;
+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.SetTableStateInMetaRequest;
@@ -2379,12 +2376,18 @@ public class MasterRpcServices extends RSRpcServices implements
         LOG.debug("Some dead server is still under processing, won't clear the dead server list");
         response.addAllServerName(request.getServerNameList());
       } else {
+        DeadServer deadServer = master.getServerManager().getDeadServers();
+        Set<Address> clearedServers = new HashSet<>();
         for (HBaseProtos.ServerName pbServer : request.getServerNameList()) {
-          if (!master.getServerManager().getDeadServers()
-                  .removeDeadServer(ProtobufUtil.toServerName(pbServer))) {
+          ServerName server = ProtobufUtil.toServerName(pbServer);
+          if (!deadServer.removeDeadServer(server)) {
             response.addServerName(pbServer);
+          } else {
+            clearedServers.add(server.getAddress());
           }
         }
+        master.getRSGroupInfoManager().removeServers(clearedServers);
+        LOG.info("Remove decommissioned servers {} from RSGroup done", clearedServers);
       }
 
       if (master.cpHost != null) {
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 28e254a..16a3e4c 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
@@ -544,5 +544,5 @@ public interface MasterServices extends Server {
   /**
    * @return the {@link RSGroupInfoManager}
    */
-  RSGroupInfoManager getRSRSGroupInfoManager();
+  RSGroupInfoManager getRSGroupInfoManager();
 }
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/assignment/AssignmentManager.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/assignment/AssignmentManager.java
index 0f7f396..ab473b7 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/assignment/AssignmentManager.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/assignment/AssignmentManager.java
@@ -72,6 +72,7 @@ import org.apache.hadoop.hbase.procedure2.ProcedureExecutor;
 import org.apache.hadoop.hbase.procedure2.ProcedureInMemoryChore;
 import org.apache.hadoop.hbase.procedure2.util.StringUtils;
 import org.apache.hadoop.hbase.regionserver.SequenceId;
+import org.apache.hadoop.hbase.rsgroup.RSGroupBasedLoadBalancer;
 import org.apache.hadoop.hbase.util.Bytes;
 import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
 import org.apache.hadoop.hbase.util.HasThread;
@@ -329,6 +330,11 @@ public class AssignmentManager {
     return master.getLoadBalancer();
   }
 
+  private FavoredNodesPromoter getFavoredNodePromoter() {
+    return (FavoredNodesPromoter) ((RSGroupBasedLoadBalancer) master.getLoadBalancer())
+      .getInternalBalancer();
+  }
+
   private MasterProcedureEnv getProcedureEnvironment() {
     return master.getMasterProcedureExecutor().getEnvironment();
   }
@@ -372,7 +378,7 @@ public class AssignmentManager {
 
   public List<ServerName> getFavoredNodes(final RegionInfo regionInfo) {
     return this.shouldAssignRegionsWithFavoredNodes
-      ? ((FavoredStochasticBalancer) getBalancer()).getFavoredNodes(regionInfo)
+      ? getFavoredNodePromoter().getFavoredNodes(regionInfo)
       : ServerName.EMPTY_SERVER_LIST;
   }
 
@@ -1833,8 +1839,8 @@ public class AssignmentManager {
     regionStateStore.splitRegion(parent, daughterA, daughterB, serverName);
     if (shouldAssignFavoredNodes(parent)) {
       List<ServerName> onlineServers = this.master.getServerManager().getOnlineServersList();
-      ((FavoredNodesPromoter)getBalancer()).
-          generateFavoredNodesForDaughter(onlineServers, parent, daughterA, daughterB);
+      getFavoredNodePromoter().generateFavoredNodesForDaughter(onlineServers, parent, daughterA,
+        daughterB);
     }
   }
 
@@ -1859,8 +1865,7 @@ public class AssignmentManager {
     }
     regionStateStore.mergeRegions(child, mergeParents, serverName);
     if (shouldAssignFavoredNodes(child)) {
-      ((FavoredNodesPromoter)getBalancer()).
-        generateFavoredNodesForMergedRegion(child, mergeParents);
+      getFavoredNodePromoter().generateFavoredNodesForMergedRegion(child, mergeParents);
     }
   }
 
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/balancer/FavoredStochasticBalancer.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/balancer/FavoredStochasticBalancer.java
index 1daa9e7..442dc44 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/balancer/FavoredStochasticBalancer.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/balancer/FavoredStochasticBalancer.java
@@ -473,6 +473,7 @@ public class FavoredStochasticBalancer extends StochasticLoadBalancer implements
     }
   }
 
+  @Override
   public synchronized List<ServerName> getFavoredNodes(RegionInfo regionInfo) {
     return this.fnm.getFavoredNodes(regionInfo);
   }
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/balancer/LoadBalancerFactory.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/balancer/LoadBalancerFactory.java
index 9ce020b..bfda12e 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/balancer/LoadBalancerFactory.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/balancer/LoadBalancerFactory.java
@@ -17,11 +17,11 @@
  */
 package org.apache.hadoop.hbase.master.balancer;
 
-import org.apache.yetus.audience.InterfaceAudience;
 import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.hbase.HConstants;
 import org.apache.hadoop.hbase.master.LoadBalancer;
-import org.apache.hadoop.util.ReflectionUtils;
+import org.apache.hadoop.hbase.util.ReflectionUtils;
+import org.apache.yetus.audience.InterfaceAudience;
 
 /**
  * The class that creates a load balancer from a conf.
@@ -30,8 +30,7 @@ import org.apache.hadoop.util.ReflectionUtils;
 public class LoadBalancerFactory {
 
   /**
-   * The default {@link LoadBalancer} class. 
-   *
+   * The default {@link LoadBalancer} class.
    * @return The Class for the default {@link LoadBalancer}.
    */
   public static Class<? extends LoadBalancer> getDefaultLoadBalancerClass() {
@@ -40,16 +39,15 @@ public class LoadBalancerFactory {
 
   /**
    * Create a loadbalancer from the given conf.
-   * @param conf
    * @return A {@link LoadBalancer}
    */
   public static LoadBalancer getLoadBalancer(Configuration conf) {
-
     // Create the balancer
     Class<? extends LoadBalancer> balancerKlass =
-        conf.getClass(HConstants.HBASE_MASTER_LOADBALANCER_CLASS, getDefaultLoadBalancerClass(),
-          LoadBalancer.class);
-    return ReflectionUtils.newInstance(balancerKlass, conf);
-
+      conf.getClass(HConstants.HBASE_MASTER_LOADBALANCER_CLASS, getDefaultLoadBalancerClass(),
+        LoadBalancer.class);
+    LoadBalancer balancer = ReflectionUtils.newInstance(balancerKlass);
+    balancer.setConf(conf);
+    return balancer;
   }
 }
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/procedure/AbstractStateMachineNamespaceProcedure.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/procedure/AbstractStateMachineNamespaceProcedure.java
index e751034..5c35544 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/procedure/AbstractStateMachineNamespaceProcedure.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/procedure/AbstractStateMachineNamespaceProcedure.java
@@ -19,6 +19,7 @@
 package org.apache.hadoop.hbase.master.procedure;
 
 import java.io.IOException;
+import java.util.function.Supplier;
 import org.apache.hadoop.hbase.NamespaceDescriptor;
 import org.apache.hadoop.hbase.TableName;
 import org.apache.hadoop.hbase.master.MasterFileSystem;
@@ -28,6 +29,7 @@ import org.apache.yetus.audience.InterfaceAudience;
 import org.apache.hbase.thirdparty.com.google.common.annotations.VisibleForTesting;
 
 import org.apache.hadoop.hbase.procedure2.StateMachineProcedure;
+import org.apache.hadoop.hbase.rsgroup.RSGroupInfo;
 import org.apache.hadoop.hbase.util.FSUtils;
 
 /**
@@ -122,4 +124,13 @@ public abstract class AbstractStateMachineNamespaceProcedure<TState>
   protected void releaseSyncLatch() {
     ProcedurePrepareLatch.releaseLatch(syncLatch, this);
   }
+
+  protected final void checkNamespaceRSGroup(MasterProcedureEnv env, NamespaceDescriptor nd)
+    throws IOException {
+    Supplier<String> forWhom = () -> "namespace " + nd.getName();
+    RSGroupInfo rsGroupInfo = MasterProcedureUtil.checkGroupExists(
+      env.getMasterServices().getRSGroupInfoManager()::getRSGroup,
+      MasterProcedureUtil.getNamespaceGroup(nd), forWhom);
+    MasterProcedureUtil.checkGroupNotEmpty(rsGroupInfo, forWhom);
+  }
 }
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/procedure/CreateNamespaceProcedure.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/procedure/CreateNamespaceProcedure.java
index 28f7585..bc60360 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/procedure/CreateNamespaceProcedure.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/procedure/CreateNamespaceProcedure.java
@@ -188,6 +188,7 @@ public class CreateNamespaceProcedure
       return false;
     }
     getTableNamespaceManager(env).validateTableAndRegionCount(nsDescriptor);
+    checkNamespaceRSGroup(env, nsDescriptor);
     return true;
   }
 
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/procedure/CreateTableProcedure.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/procedure/CreateTableProcedure.java
index a6c96a3..2fb303f 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/procedure/CreateTableProcedure.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/procedure/CreateTableProcedure.java
@@ -21,11 +21,12 @@ package org.apache.hadoop.hbase.master.procedure;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
-
+import java.util.function.Supplier;
 import org.apache.hadoop.fs.FileSystem;
 import org.apache.hadoop.fs.Path;
 import org.apache.hadoop.hbase.DoNotRetryIOException;
 import org.apache.hadoop.hbase.MetaTableAccessor;
+import org.apache.hadoop.hbase.NamespaceDescriptor;
 import org.apache.hadoop.hbase.TableExistsException;
 import org.apache.hadoop.hbase.TableName;
 import org.apache.hadoop.hbase.client.RegionInfo;
@@ -35,6 +36,7 @@ import org.apache.hadoop.hbase.client.TableState;
 import org.apache.hadoop.hbase.master.MasterCoprocessorHost;
 import org.apache.hadoop.hbase.master.MasterFileSystem;
 import org.apache.hadoop.hbase.procedure2.ProcedureStateSerializer;
+import org.apache.hadoop.hbase.rsgroup.RSGroupInfo;
 import org.apache.hadoop.hbase.util.FSTableDescriptors;
 import org.apache.hadoop.hbase.util.FSUtils;
 import org.apache.hadoop.hbase.util.ModifyRegionUtils;
@@ -42,8 +44,10 @@ import org.apache.hadoop.hbase.util.ServerRegionReplicaUtil;
 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.com.google.common.collect.Lists;
+
 import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.HBaseProtos;
 import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProcedureProtos;
@@ -252,10 +256,27 @@ public class CreateTableProcedure
 
     // check that we have at least 1 CF
     if (tableDescriptor.getColumnFamilyCount() == 0) {
-      setFailure("master-create-table", new DoNotRetryIOException("Table " +
-          getTableName().toString() + " should have at least one column family."));
+      setFailure("master-create-table", new DoNotRetryIOException(
+        "Table " + getTableName().toString() + " should have at least one column family."));
       return false;
     }
+    if (!tableName.isSystemTable()) {
+      // do not check rs group for system tables as we may block the bootstrap.
+      Supplier<String> forWhom = () -> "table " + tableName;
+      RSGroupInfo rsGroupInfo = MasterProcedureUtil.checkGroupExists(
+        env.getMasterServices().getRSGroupInfoManager()::getRSGroup,
+        tableDescriptor.getRegionServerGroup(), forWhom);
+      if (rsGroupInfo == null) {
+        // we do not set rs group info on table, check if we have one on namespace
+        String namespace = tableName.getNamespaceAsString();
+        NamespaceDescriptor nd = env.getMasterServices().getClusterSchema().getNamespace(namespace);
+        forWhom = () -> "table " + tableName + "(inherit from namespace)";
+        rsGroupInfo = MasterProcedureUtil.checkGroupExists(
+          env.getMasterServices().getRSGroupInfoManager()::getRSGroup,
+          MasterProcedureUtil.getNamespaceGroup(nd), forWhom);
+      }
+      MasterProcedureUtil.checkGroupNotEmpty(rsGroupInfo, forWhom);
+    }
 
     return true;
   }
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/procedure/MasterProcedureUtil.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/procedure/MasterProcedureUtil.java
index 49bf5c8..111c4fe 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/procedure/MasterProcedureUtil.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/procedure/MasterProcedureUtil.java
@@ -18,14 +18,18 @@
 package org.apache.hadoop.hbase.master.procedure;
 
 import java.io.IOException;
+import java.util.Optional;
+import java.util.function.Supplier;
 import java.util.regex.Pattern;
-
 import org.apache.hadoop.hbase.DoNotRetryIOException;
+import org.apache.hadoop.hbase.NamespaceDescriptor;
 import org.apache.hadoop.hbase.TableName;
+import org.apache.hadoop.hbase.constraint.ConstraintException;
 import org.apache.hadoop.hbase.master.MasterServices;
 import org.apache.hadoop.hbase.procedure2.Procedure;
 import org.apache.hadoop.hbase.procedure2.ProcedureException;
 import org.apache.hadoop.hbase.procedure2.ProcedureExecutor;
+import org.apache.hadoop.hbase.rsgroup.RSGroupInfo;
 import org.apache.hadoop.hbase.security.User;
 import org.apache.hadoop.hbase.util.NonceKey;
 import org.apache.hadoop.security.UserGroupInformation;
@@ -192,10 +196,52 @@ public final class MasterProcedureUtil {
    * keep trying. The default proc.getException().unwrapRemoteException
    * doesn't have access to DNRIOE from the procedure2 module.
    */
-  public static IOException unwrapRemoteIOException(Procedure proc) {
+  public static IOException unwrapRemoteIOException(Procedure<?> proc) {
     Exception e = proc.getException().unwrapRemoteException();
     // Do not retry ProcedureExceptions!
     return (e instanceof ProcedureException)? new DoNotRetryIOException(e):
         proc.getException().unwrapRemoteIOException();
   }
+
+  /**
+   * Do not allow creating new tables/namespaces which has an empty rs group, expect the default rs
+   * group. Notice that we do not check for online servers, as this is not stable because region
+   * servers can die at any time.
+   */
+  public static void checkGroupNotEmpty(RSGroupInfo rsGroupInfo, Supplier<String> forWhom)
+    throws ConstraintException {
+    if (rsGroupInfo == null || rsGroupInfo.getName().equals(RSGroupInfo.DEFAULT_GROUP)) {
+      // we do not have a rs group config or we explicitly set the rs group to default, then no need
+      // to check.
+      return;
+    }
+    if (rsGroupInfo.getServers().isEmpty()) {
+      throw new ConstraintException(
+        "No servers in the rsgroup " + rsGroupInfo.getName() + " for " + forWhom.get());
+    }
+  }
+
+  @FunctionalInterface
+  public interface RSGroupGetter {
+    RSGroupInfo get(String groupName) throws IOException;
+  }
+
+  public static RSGroupInfo checkGroupExists(RSGroupGetter getter, Optional<String> optGroupName,
+    Supplier<String> forWhom) throws IOException {
+    if (optGroupName.isPresent()) {
+      String groupName = optGroupName.get();
+      RSGroupInfo group = getter.get(groupName);
+      if (group == null) {
+        throw new ConstraintException(
+          "Region server group " + groupName + " for " + forWhom.get() + " does not exit");
+      }
+      return group;
+    }
+    return null;
+  }
+
+  public static Optional<String> getNamespaceGroup(NamespaceDescriptor namespaceDesc) {
+    return Optional
+      .ofNullable(namespaceDesc.getConfigurationValue(RSGroupInfo.NAMESPACE_DESC_PROP_GROUP));
+  }
 }
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/procedure/ModifyNamespaceProcedure.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/procedure/ModifyNamespaceProcedure.java
index e3327e2..c52b93d 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/procedure/ModifyNamespaceProcedure.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/procedure/ModifyNamespaceProcedure.java
@@ -18,10 +18,11 @@
 package org.apache.hadoop.hbase.master.procedure;
 
 import java.io.IOException;
+import java.util.Objects;
 import org.apache.hadoop.hbase.NamespaceDescriptor;
 import org.apache.hadoop.hbase.NamespaceNotFoundException;
-import org.apache.hadoop.hbase.constraint.ConstraintException;
 import org.apache.hadoop.hbase.procedure2.ProcedureStateSerializer;
+import org.apache.hadoop.hbase.rsgroup.RSGroupInfo;
 import org.apache.yetus.audience.InterfaceAudience;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -171,24 +172,22 @@ public class ModifyNamespaceProcedure
 
   /**
    * Action before any real action of adding namespace.
-   * @param env MasterProcedureEnv
-   * @throws IOException
    */
   private boolean prepareModify(final MasterProcedureEnv env) throws IOException {
-    if (getTableNamespaceManager(env).doesNamespaceExist(newNsDescriptor.getName()) == false) {
+    if (!getTableNamespaceManager(env).doesNamespaceExist(newNsDescriptor.getName())) {
       setFailure("master-modify-namespace",
         new NamespaceNotFoundException(newNsDescriptor.getName()));
       return false;
     }
-    try {
-      getTableNamespaceManager(env).validateTableAndRegionCount(newNsDescriptor);
-    } catch (ConstraintException e) {
-      setFailure("master-modify-namespace", e);
-      return false;
-    }
+    getTableNamespaceManager(env).validateTableAndRegionCount(newNsDescriptor);
 
     // This is used for rollback
     oldNsDescriptor = getTableNamespaceManager(env).get(newNsDescriptor.getName());
+    if (!Objects.equals(
+      oldNsDescriptor.getConfigurationValue(RSGroupInfo.NAMESPACE_DESC_PROP_GROUP),
+      newNsDescriptor.getConfigurationValue(RSGroupInfo.NAMESPACE_DESC_PROP_GROUP))) {
+      checkNamespaceRSGroup(env, newNsDescriptor);
+    }
     return true;
   }
 }
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/procedure/ModifyTableProcedure.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/procedure/ModifyTableProcedure.java
index f90e06b..49b04e4 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/procedure/ModifyTableProcedure.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/procedure/ModifyTableProcedure.java
@@ -24,7 +24,7 @@ import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
-
+import java.util.function.Supplier;
 import org.apache.hadoop.hbase.ConcurrentTableModificationException;
 import org.apache.hadoop.hbase.DoNotRetryIOException;
 import org.apache.hadoop.hbase.HBaseIOException;
@@ -43,6 +43,7 @@ import org.apache.hadoop.hbase.client.TableDescriptor;
 import org.apache.hadoop.hbase.client.TableState;
 import org.apache.hadoop.hbase.master.MasterCoprocessorHost;
 import org.apache.hadoop.hbase.procedure2.ProcedureStateSerializer;
+import org.apache.hadoop.hbase.rsgroup.RSGroupInfo;
 import org.apache.hadoop.hbase.util.Bytes;
 import org.apache.hadoop.hbase.util.ServerRegionReplicaUtil;
 import org.apache.yetus.audience.InterfaceAudience;
@@ -270,8 +271,6 @@ public class ModifyTableProcedure
 
   /**
    * Check conditions before any real action of modifying a table.
-   * @param env MasterProcedureEnv
-   * @throws IOException
    */
   private void prepareModify(final MasterProcedureEnv env) throws IOException {
     // Checks whether the table exists
@@ -314,6 +313,14 @@ public class ModifyTableProcedure
     }
     this.deleteColumnFamilyInModify = isDeleteColumnFamily(unmodifiedTableDescriptor,
       modifiedTableDescriptor);
+    if (!unmodifiedTableDescriptor.getRegionServerGroup()
+      .equals(modifiedTableDescriptor.getRegionServerGroup())) {
+      Supplier<String> forWhom = () -> "table " + getTableName();
+      RSGroupInfo rsGroupInfo = MasterProcedureUtil.checkGroupExists(
+        env.getMasterServices().getRSGroupInfoManager()::getRSGroup,
+        modifiedTableDescriptor.getRegionServerGroup(), forWhom);
+      MasterProcedureUtil.checkGroupNotEmpty(rsGroupInfo, forWhom);
+    }
   }
 
   /**
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 0bde67b..353b4d2 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
@@ -20,38 +20,25 @@ package org.apache.hadoop.hbase.rsgroup;
 import com.google.protobuf.Service;
 import java.io.IOException;
 import java.util.Collections;
-import java.util.List;
-import java.util.Objects;
-import java.util.Optional;
-import java.util.Set;
-import java.util.function.Supplier;
-import java.util.stream.Collectors;
 import org.apache.hadoop.hbase.CoprocessorEnvironment;
-import org.apache.hadoop.hbase.HConstants;
-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.coprocessor.CoreCoprocessor;
 import org.apache.hadoop.hbase.coprocessor.HasMasterServices;
 import org.apache.hadoop.hbase.coprocessor.MasterCoprocessor;
-import org.apache.hadoop.hbase.coprocessor.MasterCoprocessorEnvironment;
-import org.apache.hadoop.hbase.coprocessor.MasterObserver;
-import org.apache.hadoop.hbase.coprocessor.ObserverContext;
 import org.apache.hadoop.hbase.master.MasterServices;
-import org.apache.hadoop.hbase.net.Address;
 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;
 
-// TODO: Encapsulate MasterObserver functions into separate subclass.
+/**
+ * @deprecated Keep it here only for compatibility with old client, all the logics have been moved
+ *             into core of HBase.
+ */
+@Deprecated
 @CoreCoprocessor
 @InterfaceAudience.Private
-public class RSGroupAdminEndpoint implements MasterCoprocessor, MasterObserver {
+public class RSGroupAdminEndpoint implements MasterCoprocessor {
   // Only instance of RSGroupInfoManager. RSGroup aware load balancers ask for this instance on
   // their setup.
   private MasterServices master;
@@ -66,13 +53,8 @@ public class RSGroupAdminEndpoint implements MasterCoprocessor, MasterObserver {
     }
 
     master = ((HasMasterServices) env).getMasterServices();
-    groupInfoManager = master.getRSRSGroupInfoManager();
+    groupInfoManager = master.getRSGroupInfoManager();
     groupAdminServer = new RSGroupAdminServer(master, groupInfoManager);
-    Class<?> clazz =
-      master.getConfiguration().getClass(HConstants.HBASE_MASTER_LOADBALANCER_CLASS, null);
-    if (!RSGroupableBalancer.class.isAssignableFrom(clazz)) {
-      throw new IOException("Configured balancer does not support RegionServer groups.");
-    }
     AccessChecker accessChecker = ((HasMasterServices) env).getMasterServices().getAccessChecker();
 
     // set the user-provider.
@@ -89,11 +71,6 @@ public class RSGroupAdminEndpoint implements MasterCoprocessor, MasterObserver {
     return Collections.singleton(groupAdminService);
   }
 
-  @Override
-  public Optional<MasterObserver> getMasterObserver() {
-    return Optional.of(this);
-  }
-
   RSGroupInfoManager getGroupInfoManager() {
     return groupInfoManager;
   }
@@ -102,108 +79,4 @@ public class RSGroupAdminEndpoint implements MasterCoprocessor, MasterObserver {
   RSGroupAdminServiceImpl getGroupAdminService() {
     return groupAdminService;
   }
-
-  /////////////////////////////////////////////////////////////////////////////
-  // MasterObserver overrides
-  /////////////////////////////////////////////////////////////////////////////
-
-  @Override
-  public void postClearDeadServers(ObserverContext<MasterCoprocessorEnvironment> ctx,
-    List<ServerName> servers, List<ServerName> notClearedServers) throws IOException {
-    Set<Address> clearedServer =
-      servers.stream().filter(server -> !notClearedServers.contains(server))
-        .map(ServerName::getAddress).collect(Collectors.toSet());
-    if (!clearedServer.isEmpty()) {
-      groupAdminServer.removeServers(clearedServer);
-    }
-  }
-
-  private RSGroupInfo checkGroupExists(Optional<String> optGroupName, Supplier<String> forWhom)
-    throws IOException {
-    if (optGroupName.isPresent()) {
-      String groupName = optGroupName.get();
-      RSGroupInfo group = groupAdminServer.getRSGroupInfo(groupName);
-      if (group == null) {
-        throw new ConstraintException(
-          "Region server group " + groupName + " for " + forWhom.get() + " does not exit");
-      }
-      return group;
-    }
-    return null;
-  }
-
-  private Optional<String> getNamespaceGroup(NamespaceDescriptor namespaceDesc) {
-    return Optional
-      .ofNullable(namespaceDesc.getConfigurationValue(RSGroupInfo.NAMESPACE_DESC_PROP_GROUP));
-  }
-
-  // Do not allow creating new tables/namespaces which has an empty rs group, expect the default rs
-  // group. Notice that we do not check for online servers, as this is not stable because region
-  // servers can die at any time.
-  private void checkGroupNotEmpty(RSGroupInfo rsGroupInfo, Supplier<String> forWhom)
-    throws ConstraintException {
-    if (rsGroupInfo == null || rsGroupInfo.getName().equals(RSGroupInfo.DEFAULT_GROUP)) {
-      // we do not have a rs group config or we explicitly set the rs group to default, then no need
-      // to check.
-      return;
-    }
-    if (rsGroupInfo.getServers().isEmpty()) {
-      throw new ConstraintException(
-        "No servers in the rsgroup " + rsGroupInfo.getName() + " for " + forWhom.get());
-    }
-  }
-
-  @Override
-  public void preCreateTableAction(ObserverContext<MasterCoprocessorEnvironment> ctx,
-    TableDescriptor desc, RegionInfo[] regions) throws IOException {
-    if (desc.getTableName().isSystemTable()) {
-      // do not check for system tables as we may block the bootstrap.
-      return;
-    }
-    Supplier<String> forWhom = () -> "table " + desc.getTableName();
-    RSGroupInfo rsGroupInfo = checkGroupExists(desc.getRegionServerGroup(), forWhom);
-    if (rsGroupInfo == null) {
-      // we do not set rs group info on table, check if we have one on namespace
-      String namespace = desc.getTableName().getNamespaceAsString();
-      NamespaceDescriptor nd = master.getClusterSchema().getNamespace(namespace);
-      forWhom = () -> "table " + desc.getTableName() + "(inherit from namespace)";
-      rsGroupInfo = checkGroupExists(getNamespaceGroup(nd), forWhom);
-    }
-    checkGroupNotEmpty(rsGroupInfo, forWhom);
-  }
-
-  @Override
-  public TableDescriptor preModifyTable(ObserverContext<MasterCoprocessorEnvironment> ctx,
-    TableName tableName, TableDescriptor currentDescriptor, TableDescriptor newDescriptor)
-    throws IOException {
-    if (!currentDescriptor.getRegionServerGroup().equals(newDescriptor.getRegionServerGroup())) {
-      Supplier<String> forWhom = () -> "table " + newDescriptor.getTableName();
-      RSGroupInfo rsGroupInfo = checkGroupExists(newDescriptor.getRegionServerGroup(), forWhom);
-      checkGroupNotEmpty(rsGroupInfo, forWhom);
-    }
-    return MasterObserver.super.preModifyTable(ctx, tableName, currentDescriptor, newDescriptor);
-  }
-
-  private void checkNamespaceGroup(NamespaceDescriptor nd) throws IOException {
-    Supplier<String> forWhom = () -> "namespace " + nd.getName();
-    RSGroupInfo rsGroupInfo = checkGroupExists(getNamespaceGroup(nd), forWhom);
-    checkGroupNotEmpty(rsGroupInfo, forWhom);
-  }
-
-  @Override
-  public void preCreateNamespace(ObserverContext<MasterCoprocessorEnvironment> ctx,
-    NamespaceDescriptor ns) throws IOException {
-    checkNamespaceGroup(ns);
-  }
-
-  @Override
-  public void preModifyNamespace(ObserverContext<MasterCoprocessorEnvironment> ctx,
-    NamespaceDescriptor currentNsDescriptor, NamespaceDescriptor newNsDescriptor)
-    throws IOException {
-    if (!Objects.equals(
-      currentNsDescriptor.getConfigurationValue(RSGroupInfo.NAMESPACE_DESC_PROP_GROUP),
-      newNsDescriptor.getConfigurationValue(RSGroupInfo.NAMESPACE_DESC_PROP_GROUP))) {
-      checkNamespaceGroup(newNsDescriptor);
-    }
-  }
 }
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupBasedLoadBalancer.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupBasedLoadBalancer.java
index 0f943d0..fb3db84 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupBasedLoadBalancer.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupBasedLoadBalancer.java
@@ -35,13 +35,15 @@ import org.apache.hadoop.hbase.ServerName;
 import org.apache.hadoop.hbase.TableName;
 import org.apache.hadoop.hbase.client.RegionInfo;
 import org.apache.hadoop.hbase.constraint.ConstraintException;
+import org.apache.hadoop.hbase.favored.FavoredNodesManager;
+import org.apache.hadoop.hbase.favored.FavoredNodesPromoter;
 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.balancer.StochasticLoadBalancer;
+import org.apache.hadoop.hbase.master.balancer.LoadBalancerFactory;
 import org.apache.hadoop.hbase.net.Address;
 import org.apache.hadoop.hbase.util.Pair;
-import org.apache.hadoop.util.ReflectionUtils;
+import org.apache.hadoop.hbase.util.ReflectionUtils;
 import org.apache.yetus.audience.InterfaceAudience;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -67,12 +69,13 @@ import org.apache.hbase.thirdparty.com.google.common.collect.Maps;
  * providing appropriate assignments for user tables.
  */
 @InterfaceAudience.Private
-public class RSGroupBasedLoadBalancer implements RSGroupableBalancer {
+public class RSGroupBasedLoadBalancer implements LoadBalancer {
   private static final Logger LOG = LoggerFactory.getLogger(RSGroupBasedLoadBalancer.class);
 
   private Configuration config;
   private ClusterMetrics clusterStatus;
   private MasterServices masterServices;
+  private FavoredNodesManager favoredNodesManager;
   private volatile RSGroupInfoManager rsGroupInfoManager;
   private LoadBalancer internalBalancer;
 
@@ -330,36 +333,42 @@ public class RSGroupBasedLoadBalancer implements RSGroupableBalancer {
 
   @Override
   public void initialize() throws IOException {
-    try {
+    if (rsGroupInfoManager == null) {
+      rsGroupInfoManager = masterServices.getRSGroupInfoManager();
       if (rsGroupInfoManager == null) {
-        List<RSGroupAdminEndpoint> cps =
-          masterServices.getMasterCoprocessorHost().findCoprocessors(RSGroupAdminEndpoint.class);
-        if (cps.size() != 1) {
-          String msg = "Expected one implementation of GroupAdminEndpoint but found " + cps.size();
-          LOG.error(msg);
-          throw new HBaseIOException(msg);
-        }
-        rsGroupInfoManager = cps.get(0).getGroupInfoManager();
-        if(rsGroupInfoManager == null){
-          String msg = "RSGroupInfoManager hasn't been initialized";
-          LOG.error(msg);
-          throw new HBaseIOException(msg);
-        }
-        rsGroupInfoManager.start();
+        String msg = "RSGroupInfoManager hasn't been initialized";
+        LOG.error(msg);
+        throw new HBaseIOException(msg);
       }
-    } catch (IOException e) {
-      throw new HBaseIOException("Failed to initialize GroupInfoManagerImpl", e);
+      rsGroupInfoManager.start();
     }
 
     // Create the balancer
-    Class<? extends LoadBalancer> balancerKlass = config.getClass(HBASE_RSGROUP_LOADBALANCER_CLASS,
-        StochasticLoadBalancer.class, LoadBalancer.class);
-    internalBalancer = ReflectionUtils.newInstance(balancerKlass, config);
+    Class<? extends LoadBalancer> balancerClass;
+    String balancerClassName = config.get(HBASE_RSGROUP_LOADBALANCER_CLASS);
+    if (balancerClassName == null) {
+      balancerClass = config.getClass(HConstants.HBASE_MASTER_LOADBALANCER_CLASS,
+        LoadBalancerFactory.getDefaultLoadBalancerClass(), LoadBalancer.class);
+    } else {
+      try {
+        balancerClass = Class.forName(balancerClassName).asSubclass(LoadBalancer.class);
+      } catch (ClassNotFoundException e) {
+        throw new IOException(e);
+      }
+    }
+    // avoid infinite nesting
+    if (getClass().isAssignableFrom(balancerClass)) {
+      balancerClass = LoadBalancerFactory.getDefaultLoadBalancerClass();
+    }
+    internalBalancer = ReflectionUtils.newInstance(balancerClass);
+    if (internalBalancer instanceof FavoredNodesPromoter) {
+      favoredNodesManager = new FavoredNodesManager(masterServices);
+    }
+    internalBalancer.setConf(config);
     internalBalancer.setMasterServices(masterServices);
     if(clusterStatus != null) {
       internalBalancer.setClusterMetrics(clusterStatus);
     }
-    internalBalancer.setConf(config);
     internalBalancer.initialize();
   }
 
@@ -402,6 +411,14 @@ public class RSGroupBasedLoadBalancer implements RSGroupableBalancer {
     this.rsGroupInfoManager = rsGroupInfoManager;
   }
 
+  public LoadBalancer getInternalBalancer() {
+    return internalBalancer;
+  }
+
+  public FavoredNodesManager getFavoredNodesManager() {
+    return favoredNodesManager;
+  }
+
   @Override
   public void postMasterStartupInitialize() {
     this.internalBalancer.postMasterStartupInitialize();
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 7224869..ce0bd8c 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
@@ -427,7 +427,7 @@ final class RSGroupInfoManagerImpl implements RSGroupInfoManager {
         // master first and then region server, so after all the region servers has been reopened,
         // the new TableDescriptor will be loaded.
         try {
-          tds.add(newTd);
+          tds.update(newTd);
         } catch (IOException e) {
           LOG.warn("Failed to migrate {} in group {}", tableName, groupInfo.getName(), e);
           failedTables.add(tableName);
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupableBalancer.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupableBalancer.java
deleted file mode 100644
index d091b3c..0000000
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupableBalancer.java
+++ /dev/null
@@ -1,32 +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 org.apache.hadoop.hbase.master.LoadBalancer;
-import org.apache.yetus.audience.InterfaceAudience;
-
-/**
- * Marker Interface. RSGroups feature will check for a LoadBalancer
- * marked with this Interface before it runs.
- */
-@InterfaceAudience.Private
-public interface RSGroupableBalancer extends LoadBalancer {
-  /** Config for pluggable load balancers */
-  String HBASE_RSGROUP_LOADBALANCER_CLASS = "hbase.rsgroup.grouploadbalancer.class";
-}
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 9f74aa4..df161c9 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
@@ -118,7 +118,7 @@ public class TestNamespace {
 
     //verify existence of system tables
     Set<TableName> systemTables = Sets.newHashSet(
-        TableName.META_TABLE_NAME);
+        TableName.META_TABLE_NAME, TableName.valueOf("hbase:rsgroup"));
     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 a576adc..fc2e9f7 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(1 + existingRegions, regions.size());
+    assertEquals(2 + 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 + 1 + existingRegions, regions.size());
+    assertEquals(numRegions + 2 + existingRegions, regions.size());
     return table;
   }
 
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 cde6ea0..5554e51 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
@@ -497,7 +497,7 @@ public class MockNoopMasterServices implements MasterServices {
   }
 
   @Override
-  public RSGroupInfoManager getRSRSGroupInfoManager() {
+  public RSGroupInfoManager getRSGroupInfoManager() {
     return null;
   }
 }
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 5a38234..a8d80b6 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,6 +43,8 @@ public class TestClusterRestart extends AbstractTestRestartCluster {
 
   private static final Logger LOG = LoggerFactory.getLogger(TestClusterRestart.class);
 
+  private static final int NUM_REGIONS = 4;
+
   @Override
   protected boolean splitWALCoordinatedByZk() {
     return true;
@@ -61,7 +63,7 @@ public class TestClusterRestart extends AbstractTestRestartCluster {
     }
 
     List<RegionInfo> allRegions = MetaTableAccessor.getAllRegions(UTIL.getConnection(), false);
-    assertEquals(3, allRegions.size());
+    assertEquals(NUM_REGIONS, allRegions.size());
 
     LOG.info("\n\nShutting down cluster");
     UTIL.shutdownMiniHBaseCluster();
@@ -76,7 +78,7 @@ public class TestClusterRestart extends AbstractTestRestartCluster {
     // Otherwise we're reusing an Connection that has gone stale because
     // the shutdown of the cluster also called shut of the connection.
     allRegions = MetaTableAccessor.getAllRegions(UTIL.getConnection(), false);
-    assertEquals(3, allRegions.size());
+    assertEquals(NUM_REGIONS, allRegions.size());
     LOG.info("\n\nWaiting for tables to be available");
     for (TableName TABLE : TABLES) {
       try {
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 75d9ee1..61720df 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,6 +25,7 @@ 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;
@@ -90,6 +91,7 @@ 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();
   }
 
@@ -131,7 +133,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", 1, masterSource);
+    metricsHelper.assertGauge("averageLoad", 2, 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 fca2866..2d0af3e 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.", 1, regions.size());
+      "the catalog table should be present.", 2, 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.", 5, regions.size());
+      " switch except for the catalog table.", 6, 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 0aba487..2dc1b6c 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(1, regions.size());
+    assertEquals(2, 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/master/balancer/TestFavoredStochasticBalancerPickers.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/balancer/TestFavoredStochasticBalancerPickers.java
index 0aa2322..7677eb6 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/balancer/TestFavoredStochasticBalancerPickers.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/balancer/TestFavoredStochasticBalancerPickers.java
@@ -186,7 +186,7 @@ public class TestFavoredStochasticBalancerPickers extends BalancerTestBase {
     regionFinder.setServices(TEST_UTIL.getMiniHBaseCluster().getMaster());
     Cluster cluster = new Cluster(serverAssignments, null, regionFinder, new RackManager(conf));
     LoadOnlyFavoredStochasticBalancer balancer = (LoadOnlyFavoredStochasticBalancer) TEST_UTIL
-        .getMiniHBaseCluster().getMaster().getLoadBalancer();
+        .getMiniHBaseCluster().getMaster().getLoadBalancer().getInternalBalancer();
 
     cluster.sortServersByRegionCount();
     Integer[] servers = cluster.serverIndicesSortedByRegionCount;
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 7ab5dcc..2503825 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
@@ -59,7 +59,6 @@ public class TestRegionOpen {
   public static final HBaseClassTestRule CLASS_RULE =
       HBaseClassTestRule.forClass(TestRegionOpen.class);
 
-  @SuppressWarnings("unused")
   private static final Logger LOG = LoggerFactory.getLogger(TestRegionOpen.class);
   private static final int NB_SERVERS = 1;
 
@@ -87,6 +86,7 @@ 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 d4e2507..6211db6 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(61, totalRegions);
+    assertEquals(62, 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 833e78b..8361d58 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
@@ -231,7 +231,7 @@ public class TestRegionServerMetrics {
 
   @Test
   public void testRegionCount() throws Exception {
-    metricsHelper.assertGauge("regionCount", TABLES_ON_MASTER ? 1 : 2, serverSource);
+    metricsHelper.assertGauge("regionCount", TABLES_ON_MASTER ? 1 : 3, serverSource);
   }
 
   @Test
@@ -341,7 +341,7 @@ public class TestRegionServerMetrics {
     TEST_UTIL.getAdmin().flush(tableName);
 
     metricsRegionServer.getRegionServerWrapper().forceRecompute();
-    assertGauge("storeCount", TABLES_ON_MASTER ? 1 : 5);
+    assertGauge("storeCount", TABLES_ON_MASTER ? 1 : 6);
     assertGauge("storeFileCount", 1);
   }
 
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 d3577f2..7ac1a49 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
@@ -163,8 +163,7 @@ public class TestRSGroupsOfflineMode {
     });
 
     // Get groupInfoManager from the new active master.
-    RSGroupInfoManager groupMgr = ((MiniHBaseCluster) cluster).getMaster()
-      .getMasterCoprocessorHost().findCoprocessor(RSGroupAdminEndpoint.class).getGroupInfoManager();
+    RSGroupInfoManager groupMgr = ((MiniHBaseCluster) cluster).getMaster().getRSGroupInfoManager();
     // Make sure balancer is in offline mode, since this is what we're testing.
     assertFalse(groupMgr.isOnline());
     // Kill final regionserver to see the failover happens for all tables except GROUP table since
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestTablePermissions.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestTablePermissions.java
index 078e065..3de38a7 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestTablePermissions.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestTablePermissions.java
@@ -102,6 +102,7 @@ public class TestTablePermissions {
 
     // Wait for the ACL table to become available
     UTIL.waitTableEnabled(PermissionStorage.ACL_TABLE_NAME);
+    UTIL.waitTableAvailable(TableName.valueOf("hbase:rsgroup"));
 
     ZKW = new ZKWatcher(UTIL.getConfiguration(),
       "TestTablePermissions", ABORTABLE);
@@ -222,7 +223,7 @@ public class TestTablePermissions {
     // check full load
     Map<byte[], ListMultimap<String, UserPermission>> allPerms = PermissionStorage.loadAll(conf);
     assertEquals("Full permission map should have entries for both test tables",
-        2, allPerms.size());
+        3, allPerms.size());
 
     userPerms = allPerms.get(TEST_TABLE.getName()).get("hubert");
     assertNotNull(userPerms);
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 316a321..0ac07d8 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,6 +24,7 @@ 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;
@@ -52,6 +53,7 @@ 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] 11/21: 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 master
in repository https://gitbox.apache.org/repos/asf/hbase.git

commit 0f4a87c24dc8d5c29e31df27ec741fbca9981c9b
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 +-
 .../security/access/TestTablePermissions.java      |   4 +-
 .../hbase/util/TestHBaseFsckReplication.java       |   2 -
 14 files changed, 141 insertions(+), 20 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 d3a4c95..7a4ba3d 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
@@ -186,6 +186,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;
@@ -800,6 +801,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 8361d58..833e78b 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
@@ -231,7 +231,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
@@ -341,7 +341,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/security/access/TestTablePermissions.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestTablePermissions.java
index 3de38a7..79436d3 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestTablePermissions.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestTablePermissions.java
@@ -102,7 +102,7 @@ public class TestTablePermissions {
 
     // Wait for the ACL table to become available
     UTIL.waitTableEnabled(PermissionStorage.ACL_TABLE_NAME);
-    UTIL.waitTableAvailable(TableName.valueOf("hbase:rsgroup"));
+    UTIL.waitTableAvailable(TableName.valueOf("hbase:acl"));
 
     ZKW = new ZKWatcher(UTIL.getConfiguration(),
       "TestTablePermissions", ABORTABLE);
@@ -223,7 +223,7 @@ public class TestTablePermissions {
     // check full load
     Map<byte[], ListMultimap<String, UserPermission>> allPerms = PermissionStorage.loadAll(conf);
     assertEquals("Full permission map should have entries for both test tables",
-        3, allPerms.size());
+        2, allPerms.size());
 
     userPerms = allPerms.get(TEST_TABLE.getName()).get("hubert");
     assertNotNull(userPerms);
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] 14/21: 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 master
in repository https://gitbox.apache.org/repos/asf/hbase.git

commit 72cbb129a4bbea245decd31432d27398c30db652
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 |   79 +-
 .../hadoop/hbase/client/AdminOverAsyncAdmin.java   |   52 +
 .../org/apache/hadoop/hbase/client/AsyncAdmin.java |   81 +-
 .../hadoop/hbase/client/AsyncHBaseAdmin.java       |   51 +
 .../hadoop/hbase/client/RawAsyncHBaseAdmin.java    |  207 +++-
 .../hbase/shaded/protobuf/RequestConverter.java    |   36 +-
 .../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     |  166 ++++
 .../apache/hadoop/hbase/master/MasterServices.java |    9 +
 .../hbase/rsgroup/DisabledRSGroupInfoManager.java  |   13 +-
 .../apache/hadoop/hbase/rsgroup/RSGroupAdmin.java  |    4 +
 .../hadoop/hbase/rsgroup/RSGroupAdminClient.java   | 1051 ++++++++++++++++++--
 .../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      |   29 +-
 .../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, 2587 insertions(+), 973 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 ae10f78..aeb5197 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;
@@ -2261,7 +2263,6 @@ public interface Admin extends Abortable, Closeable {
    */
   boolean isSnapshotCleanupEnabled() throws IOException;
 
-
   /**
    * Retrieves online slow RPC logs from the provided list of
    * RegionServers
@@ -2286,4 +2287,80 @@ public interface Admin extends Abortable, Closeable {
   List<Boolean> clearSlowLogResponses(final Set<ServerName> serverNames)
       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 149334c..293d513 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;
@@ -972,4 +974,54 @@ class AdminOverAsyncAdmin implements Admin {
     return get(admin.clearSlowLogResponses(serverNames));
   }
 
+  @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 baf330d..d53aab2 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;
@@ -1380,7 +1382,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);
 
   /**
@@ -1526,4 +1528,81 @@ public interface AsyncAdmin {
    *   from each RegionServer. The return value wrapped by a {@link CompletableFuture}
    */
   CompletableFuture<List<Boolean>> clearSlowLogResponses(final Set<ServerName> serverNames);
+
+  /**
+   * 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 e84adbf..7939b80 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
@@ -37,12 +37,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;
@@ -850,4 +852,53 @@ class AsyncHBaseAdmin implements AsyncAdmin {
     return wrap(rawAdmin.clearSlowLogResponses(serverNames));
   }
 
+  @Override
+  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 4bf4c3d..b9cb700 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;
@@ -3858,23 +3870,70 @@ 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();
   }
 
@@ -3937,4 +3996,120 @@ 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;
+  }
+
+  @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 8f29383..2b02cf6 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.commons.lang3.StringUtils;
 import org.apache.hadoop.hbase.CellScannable;
 import org.apache.hadoop.hbase.ClusterMetrics.Option;
@@ -60,6 +59,7 @@ import org.apache.hadoop.hbase.filter.BinaryComparator;
 import org.apache.hadoop.hbase.filter.Filter;
 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;
@@ -68,7 +68,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.ClearSlowLogResponseRequest;
@@ -123,8 +125,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;
@@ -140,8 +141,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;
@@ -150,6 +150,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;
@@ -1789,4 +1791,28 @@ public final class RequestConverter {
     return ClearSlowLogResponseRequest.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 e88ddc4..368328e 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 7a4ba3d..a5e6bf6 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
@@ -3822,7 +3822,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 6d5a7a0..223acc2 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.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;
@@ -121,6 +122,8 @@ import org.apache.yetus.audience.InterfaceAudience;
 import org.apache.zookeeper.KeeperException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+
+import org.apache.hbase.thirdparty.com.google.common.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;
@@ -314,6 +317,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;
@@ -363,6 +378,7 @@ public class MasterRpcServices extends RSRpcServices implements
     MasterService.BlockingInterface, RegionServerStatusService.BlockingInterface,
     LockService.BlockingInterface, HbckService.BlockingInterface,
     ClientMetaService.BlockingInterface {
+
   private static final Logger LOG = LoggerFactory.getLogger(MasterRpcServices.class.getName());
   private static final Logger AUDITLOG =
       LoggerFactory.getLogger("SecurityLogger."+MasterRpcServices.class.getName());
@@ -2901,4 +2917,154 @@ public class MasterRpcServices extends RSRpcServices implements
       location -> response.addMetaLocations(ProtobufUtil.toRegionLocation(location))));
     return response.build();
   }
+
+  @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..94ac353 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,35 @@ 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.SlowLogQueryFilter;
+import org.apache.hadoop.hbase.client.SlowLogRecord;
+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 +61,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 +88,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 +957,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 +974,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 +1051,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 +1063,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 +1078,22 @@ 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);
-    }
+    return getRSGroup(hostPort);
   }
 
-  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);
-    }
+  @Override
+  public void removeServers(Set<Address> servers) throws IOException {
+    removeRSGroup(servers);
   }
 
   @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);
-    }
+  public List<SlowLogRecord> getSlowLogResponses(Set<ServerName> serverNames,
+    SlowLogQueryFilter slowLogQueryFilter) throws IOException {
+    return null;
+  }
+
+  @Override
+  public List<Boolean> clearSlowLogResponses(Set<ServerName> serverNames) throws IOException {
+    return null;
   }
 }
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 8d63cf8..2b43f3e 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));
@@ -123,12 +127,11 @@ public class TestMigrateRSGroupInfo extends TestRSGroupsBase {
     RESUME = new CountDownLatch(1);
     TEST_UTIL.getMiniHBaseCluster().startMaster();
     TEST_UTIL.invalidateConnection();
-    RS_GROUP_ADMIN_CLIENT = new RSGroupAdminClient(TEST_UTIL.getConnection());
 
     // 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;
@@ -137,7 +140,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());
     }
@@ -173,7 +176,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 48c71e1..cb93dd4 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
@@ -52,12 +52,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;
@@ -1171,6 +1173,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] 21/21: HBASE-23911 Attach the new rsgroup implementation design doc to our code base (#1224)

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

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

commit 346d087f409f9b44754d1d4426492c1ecd02ea89
Author: Duo Zhang <zh...@apache.org>
AuthorDate: Sat Feb 29 10:38:33 2020 +0800

    HBASE-23911 Attach the new rsgroup implementation design doc to our code base (#1224)
    
    Signed-off-by: Guanghao Zhang <zg...@apache.org>
---
 ...t-rsgroup-feature-and-move-it-into-core-of-HBase.pdf | Bin 0 -> 27888 bytes
 1 file changed, 0 insertions(+), 0 deletions(-)

diff --git a/dev-support/design-docs/HBASE-22514-Reimplement-rsgroup-feature-and-move-it-into-core-of-HBase.pdf b/dev-support/design-docs/HBASE-22514-Reimplement-rsgroup-feature-and-move-it-into-core-of-HBase.pdf
new file mode 100644
index 0000000..2c827eb
Binary files /dev/null and b/dev-support/design-docs/HBASE-22514-Reimplement-rsgroup-feature-and-move-it-into-core-of-HBase.pdf differ


[hbase] 02/21: HBASE-22662 Move RSGroupInfoManager to hbase-server (#368)

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

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

commit 662149e1932d84ad5857cf76a2d1cf79435308e4
Author: Duo Zhang <zh...@apache.org>
AuthorDate: Thu Jul 11 10:34:05 2019 +0800

    HBASE-22662 Move RSGroupInfoManager to hbase-server (#368)
    
    Signed-off-by: Guanghao Zhang <zg...@apache.org>
---
 hbase-rsgroup/pom.xml                              |   4 -
 .../hbase/rsgroup/RSGroupBasedLoadBalancer.java    |   4 +-
 .../hbase/rsgroup/TestRSGroupsOfflineMode.java     |   6 +-
 .../hbase/rsgroup/VerifyingRSGroupAdminClient.java |   6 +-
 .../hadoop/hbase/rsgroup/RSGroupInfoManager.java   |  26 +----
 .../hbase/rsgroup/RSGroupInfoManagerImpl.java      | 108 +++++++++++----------
 6 files changed, 71 insertions(+), 83 deletions(-)

diff --git a/hbase-rsgroup/pom.xml b/hbase-rsgroup/pom.xml
index 09c8e73..1135939 100644
--- a/hbase-rsgroup/pom.xml
+++ b/hbase-rsgroup/pom.xml
@@ -113,10 +113,6 @@
       <artifactId>hbase-shaded-miscellaneous</artifactId>
     </dependency>
     <dependency>
-      <groupId>com.google.protobuf</groupId>
-      <artifactId>protobuf-java</artifactId>
-    </dependency>
-    <dependency>
       <groupId>org.apache.zookeeper</groupId>
       <artifactId>zookeeper</artifactId>
     </dependency>
diff --git a/hbase-rsgroup/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupBasedLoadBalancer.java b/hbase-rsgroup/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupBasedLoadBalancer.java
index 6767ac9..7394f55 100644
--- a/hbase-rsgroup/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupBasedLoadBalancer.java
+++ b/hbase-rsgroup/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupBasedLoadBalancer.java
@@ -121,8 +121,8 @@ public class RSGroupBasedLoadBalancer implements RSGroupableBalancer {
   public List<RegionPlan> balanceCluster(Map<ServerName, List<RegionInfo>> clusterState)
       throws HBaseIOException {
     if (!isOnline()) {
-      throw new ConstraintException(RSGroupInfoManager.RSGROUP_TABLE_NAME +
-          " is not online, unable to perform balance");
+      throw new ConstraintException(
+          RSGroupInfoManager.class.getSimpleName() + " is not online, unable to perform balance");
     }
 
     // Calculate correct assignments and a list of RegionPlan for mis-placed regions
diff --git a/hbase-rsgroup/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsOfflineMode.java b/hbase-rsgroup/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsOfflineMode.java
index 39cf164..60887e4 100644
--- a/hbase-rsgroup/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsOfflineMode.java
+++ b/hbase-rsgroup/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsOfflineMode.java
@@ -140,7 +140,7 @@ public class TestRSGroupsOfflineMode {
       }
     });
     // Move table to group and wait.
-    groupAdmin.moveTables(Sets.newHashSet(RSGroupInfoManager.RSGROUP_TABLE_NAME), newGroup);
+    groupAdmin.moveTables(Sets.newHashSet(RSGroupInfoManagerImpl.RSGROUP_TABLE_NAME), newGroup);
     LOG.info("Waiting for move table...");
     TEST_UTIL.waitFor(WAIT_TIMEOUT, new Waiter.Predicate<Exception>() {
       @Override
@@ -169,7 +169,7 @@ public class TestRSGroupsOfflineMode {
     // Make sure balancer is in offline mode, since this is what we're testing.
     assertFalse(groupMgr.isOnline());
     // Verify the group affiliation that's loaded from ZK instead of tables.
-    assertEquals(newGroup, groupMgr.getRSGroupOfTable(RSGroupInfoManager.RSGROUP_TABLE_NAME));
+    assertEquals(newGroup, groupMgr.getRSGroupOfTable(RSGroupInfoManagerImpl.RSGROUP_TABLE_NAME));
     assertEquals(RSGroupInfo.DEFAULT_GROUP, groupMgr.getRSGroupOfTable(failoverTable));
     // Kill final regionserver to see the failover happens for all tables except GROUP table since
     // it's group does not have any online RS.
@@ -182,7 +182,7 @@ public class TestRSGroupsOfflineMode {
         return failoverRS.getRegions(failoverTable).size() >= 1;
       }
     });
-    Assert.assertEquals(0, failoverRS.getRegions(RSGroupInfoManager.RSGROUP_TABLE_NAME).size());
+    Assert.assertEquals(0, failoverRS.getRegions(RSGroupInfoManagerImpl.RSGROUP_TABLE_NAME).size());
 
     // Need this for minicluster to shutdown cleanly.
     master.stopMaster();
diff --git a/hbase-rsgroup/src/test/java/org/apache/hadoop/hbase/rsgroup/VerifyingRSGroupAdminClient.java b/hbase-rsgroup/src/test/java/org/apache/hadoop/hbase/rsgroup/VerifyingRSGroupAdminClient.java
index 2ad30e4..fcaf1a7 100644
--- a/hbase-rsgroup/src/test/java/org/apache/hadoop/hbase/rsgroup/VerifyingRSGroupAdminClient.java
+++ b/hbase-rsgroup/src/test/java/org/apache/hadoop/hbase/rsgroup/VerifyingRSGroupAdminClient.java
@@ -52,7 +52,7 @@ public class VerifyingRSGroupAdminClient implements RSGroupAdmin {
       throws IOException {
     wrapped = RSGroupAdmin;
     table = ConnectionFactory.createConnection(conf)
-            .getTable(RSGroupInfoManager.RSGROUP_TABLE_NAME);
+        .getTable(RSGroupInfoManagerImpl.RSGROUP_TABLE_NAME);
     zkw = new ZKWatcher(conf, this.getClass().getSimpleName(), null);
   }
 
@@ -126,8 +126,8 @@ public class VerifyingRSGroupAdminClient implements RSGroupAdmin {
       RSGroupProtos.RSGroupInfo proto =
           RSGroupProtos.RSGroupInfo.parseFrom(
               result.getValue(
-                  RSGroupInfoManager.META_FAMILY_BYTES,
-                  RSGroupInfoManager.META_QUALIFIER_BYTES));
+                  RSGroupInfoManagerImpl.META_FAMILY_BYTES,
+                  RSGroupInfoManagerImpl.META_QUALIFIER_BYTES));
       groupMap.put(proto.getName(), ProtobufUtil.toGroupInfo(proto));
     }
     Assert.assertEquals(Sets.newHashSet(groupMap.values()),
diff --git a/hbase-rsgroup/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupInfoManager.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupInfoManager.java
similarity index 82%
rename from hbase-rsgroup/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupInfoManager.java
rename to hbase-server/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupInfoManager.java
index 398e8a4..70aeabf 100644
--- a/hbase-rsgroup/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupInfoManager.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupInfoManager.java
@@ -15,38 +15,22 @@
  * 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.NamespaceDescriptor;
 import org.apache.hadoop.hbase.TableName;
 import org.apache.hadoop.hbase.net.Address;
-import org.apache.hadoop.hbase.util.Bytes;
 import org.apache.yetus.audience.InterfaceAudience;
 
 /**
- * Interface used to manage RSGroupInfo storage. An implementation
- * has the option to support offline mode.
- * See {@link RSGroupBasedLoadBalancer}
+ * Interface used to manage RSGroupInfo storage. An implementation has the option to support offline
+ * mode. See {@code RSGroupBasedLoadBalancer}.
  */
 @InterfaceAudience.Private
 public interface RSGroupInfoManager {
 
-  String REASSIGN_WAIT_INTERVAL_KEY = "hbase.rsgroup.reassign.wait";
-  long DEFAULT_REASSIGN_WAIT_INTERVAL = 30 * 1000L;
-
-  //Assigned before user tables
-  TableName RSGROUP_TABLE_NAME =
-      TableName.valueOf(NamespaceDescriptor.SYSTEM_NAMESPACE_NAME_STR, "rsgroup");
-  String rsGroupZNode = "rsgroup";
-  byte[] META_FAMILY_BYTES = Bytes.toBytes("m");
-  byte[] META_QUALIFIER_BYTES = Bytes.toBytes("i");
-  byte[] ROW_KEY = {0};
-
   void start();
 
   /**
@@ -86,7 +70,6 @@ public interface RSGroupInfoManager {
 
   /**
    * Set the group membership of a set of tables
-   *
    * @param tableNames set of tables to move
    * @param groupName name of group of tables to move to
    */
@@ -104,7 +87,6 @@ public interface RSGroupInfoManager {
 
   /**
    * Whether the manager is able to fully return group metadata
-   *
    * @return whether the manager is in online mode
    */
   boolean isOnline();
@@ -116,8 +98,8 @@ public interface RSGroupInfoManager {
    * @param srcGroup groupName being moved from
    * @param dstGroup groupName being moved to
    */
-  void moveServersAndTables(Set<Address> servers, Set<TableName> tables,
-      String srcGroup, String dstGroup) throws IOException;
+  void moveServersAndTables(Set<Address> servers, Set<TableName> tables, String srcGroup,
+      String dstGroup) throws IOException;
 
   /**
    * Remove decommissioned servers from rsgroup
diff --git a/hbase-rsgroup/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupInfoManagerImpl.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupInfoManagerImpl.java
similarity index 90%
rename from hbase-rsgroup/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupInfoManagerImpl.java
rename to hbase-server/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupInfoManagerImpl.java
index b54f088..8aa7520 100644
--- a/hbase-rsgroup/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupInfoManagerImpl.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupInfoManagerImpl.java
@@ -17,7 +17,6 @@
  */
 package org.apache.hadoop.hbase.rsgroup;
 
-import com.google.protobuf.ServiceException;
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.util.ArrayList;
@@ -34,10 +33,12 @@ import java.util.SortedSet;
 import java.util.TreeSet;
 import org.apache.hadoop.hbase.Coprocessor;
 import org.apache.hadoop.hbase.DoNotRetryIOException;
+import org.apache.hadoop.hbase.NamespaceDescriptor;
 import org.apache.hadoop.hbase.ServerName;
 import org.apache.hadoop.hbase.TableName;
+import org.apache.hadoop.hbase.client.AsyncClusterConnection;
+import org.apache.hadoop.hbase.client.AsyncTable;
 import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder;
-import org.apache.hadoop.hbase.client.Connection;
 import org.apache.hadoop.hbase.client.CoprocessorDescriptorBuilder;
 import org.apache.hadoop.hbase.client.Delete;
 import org.apache.hadoop.hbase.client.Get;
@@ -45,14 +46,11 @@ import org.apache.hadoop.hbase.client.Mutation;
 import org.apache.hadoop.hbase.client.Put;
 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.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.ipc.CoprocessorRpcChannel;
 import org.apache.hadoop.hbase.master.MasterServices;
 import org.apache.hadoop.hbase.master.ServerListener;
 import org.apache.hadoop.hbase.master.TableStateManager;
@@ -62,10 +60,14 @@ import org.apache.hadoop.hbase.net.Address;
 import org.apache.hadoop.hbase.procedure2.Procedure;
 import org.apache.hadoop.hbase.protobuf.ProtobufMagic;
 import org.apache.hadoop.hbase.protobuf.ProtobufUtil;
-import org.apache.hadoop.hbase.protobuf.generated.MultiRowMutationProtos;
+import org.apache.hadoop.hbase.protobuf.generated.ClientProtos.MutationProto;
+import org.apache.hadoop.hbase.protobuf.generated.MultiRowMutationProtos.MultiRowMutationService;
+import org.apache.hadoop.hbase.protobuf.generated.MultiRowMutationProtos.MutateRowsRequest;
+import org.apache.hadoop.hbase.protobuf.generated.MultiRowMutationProtos.MutateRowsResponse;
 import org.apache.hadoop.hbase.protobuf.generated.RSGroupProtos;
 import org.apache.hadoop.hbase.regionserver.DisabledRegionSplitPolicy;
 import org.apache.hadoop.hbase.util.Bytes;
+import org.apache.hadoop.hbase.util.FutureUtils;
 import org.apache.hadoop.hbase.util.Threads;
 import org.apache.hadoop.hbase.zookeeper.ZKUtil;
 import org.apache.hadoop.hbase.zookeeper.ZKWatcher;
@@ -75,6 +77,7 @@ import org.apache.zookeeper.KeeperException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import org.apache.hbase.thirdparty.com.google.common.annotations.VisibleForTesting;
 import org.apache.hbase.thirdparty.com.google.common.collect.Lists;
 import org.apache.hbase.thirdparty.com.google.common.collect.Maps;
 import org.apache.hbase.thirdparty.com.google.common.collect.Sets;
@@ -87,13 +90,13 @@ import org.apache.hbase.thirdparty.com.google.common.collect.Sets;
  * 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.
- * <p>
+ * <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
  * state are read-only, just-in-case (see flushConfig).
- * <p>
+ * <p/>
  * Reads must not block else there is a danger we'll deadlock.
- * <p>
+ * <p/>
  * Clients of this class, the {@link RSGroupAdminEndpoint} for example, want to query and then act
  * on the results of the query modifying cache in zookeeper without another thread making
  * intermediate modifications. These clients synchronize on the 'this' instance so no other has
@@ -103,6 +106,24 @@ import org.apache.hbase.thirdparty.com.google.common.collect.Sets;
 final class RSGroupInfoManagerImpl implements RSGroupInfoManager {
   private static final Logger LOG = LoggerFactory.getLogger(RSGroupInfoManagerImpl.class);
 
+  private static final String REASSIGN_WAIT_INTERVAL_KEY = "hbase.rsgroup.reassign.wait";
+  private static final long DEFAULT_REASSIGN_WAIT_INTERVAL = 30 * 1000L;
+
+  // Assigned before user tables
+  @VisibleForTesting
+  static final TableName RSGROUP_TABLE_NAME =
+      TableName.valueOf(NamespaceDescriptor.SYSTEM_NAMESPACE_NAME_STR, "rsgroup");
+
+  private static final String RS_GROUP_ZNODE = "rsgroup";
+
+  @VisibleForTesting
+  static final byte[] META_FAMILY_BYTES = Bytes.toBytes("m");
+
+  @VisibleForTesting
+  static final byte[] META_QUALIFIER_BYTES = Bytes.toBytes("i");
+
+  private static final byte[] ROW_KEY = { 0 };
+
   /** Table descriptor for <code>hbase:rsgroup</code> catalog table */
   private static final TableDescriptor RSGROUP_TABLE_DESC;
   static {
@@ -125,7 +146,7 @@ final class RSGroupInfoManagerImpl implements RSGroupInfoManager {
   private volatile Map<TableName, String> tableMap = Collections.emptyMap();
 
   private final MasterServices masterServices;
-  private final Connection conn;
+  private final AsyncClusterConnection conn;
   private final ZKWatcher watcher;
   private final RSGroupStartupWorker rsGroupStartupWorker;
   // contains list of groups that were last flushed to persistent store
@@ -136,7 +157,7 @@ final class RSGroupInfoManagerImpl implements RSGroupInfoManager {
   private RSGroupInfoManagerImpl(MasterServices masterServices) throws IOException {
     this.masterServices = masterServices;
     this.watcher = masterServices.getZooKeeper();
-    this.conn = masterServices.getConnection();
+    this.conn = masterServices.getAsyncClusterConnection();
     this.rsGroupStartupWorker = new RSGroupStartupWorker();
   }
 
@@ -349,25 +370,25 @@ final class RSGroupInfoManagerImpl implements RSGroupInfoManager {
     }
   }
 
-  List<RSGroupInfo> retrieveGroupListFromGroupTable() throws IOException {
+  private List<RSGroupInfo> retrieveGroupListFromGroupTable() throws IOException {
     List<RSGroupInfo> rsGroupInfoList = Lists.newArrayList();
-    try (Table table = conn.getTable(RSGROUP_TABLE_NAME);
-        ResultScanner scanner = table.getScanner(new Scan())) {
+    AsyncTable<?> table = conn.getTable(RSGROUP_TABLE_NAME);
+    try (ResultScanner scanner = table.getScanner(META_FAMILY_BYTES, META_QUALIFIER_BYTES)) {
       for (Result result;;) {
         result = scanner.next();
         if (result == null) {
           break;
         }
         RSGroupProtos.RSGroupInfo proto = RSGroupProtos.RSGroupInfo
-          .parseFrom(result.getValue(META_FAMILY_BYTES, META_QUALIFIER_BYTES));
+            .parseFrom(result.getValue(META_FAMILY_BYTES, META_QUALIFIER_BYTES));
         rsGroupInfoList.add(ProtobufUtil.toGroupInfo(proto));
       }
     }
     return rsGroupInfoList;
   }
 
-  List<RSGroupInfo> retrieveGroupListFromZookeeper() throws IOException {
-    String groupBasePath = ZNodePaths.joinZNode(watcher.getZNodePaths().baseZNode, rsGroupZNode);
+  private List<RSGroupInfo> retrieveGroupListFromZookeeper() throws IOException {
+    String groupBasePath = ZNodePaths.joinZNode(watcher.getZNodePaths().baseZNode, RS_GROUP_ZNODE);
     List<RSGroupInfo> RSGroupInfoList = Lists.newArrayList();
     // Overwrite any info stored by table, this takes precedence
     try {
@@ -519,7 +540,8 @@ final class RSGroupInfoManagerImpl implements RSGroupInfoManager {
     resetRSGroupAndTableMaps(newGroupMap, newTableMap);
 
     try {
-      String groupBasePath = ZNodePaths.joinZNode(watcher.getZNodePaths().baseZNode, rsGroupZNode);
+      String groupBasePath =
+          ZNodePaths.joinZNode(watcher.getZNodePaths().baseZNode, RS_GROUP_ZNODE);
       ZKUtil.createAndFailSilent(watcher, groupBasePath, ProtobufMagic.PB_MAGIC);
 
       List<ZKUtil.ZKUtilOp> zkOps = new ArrayList<>(newGroupMap.size());
@@ -702,11 +724,8 @@ final class RSGroupInfoManagerImpl implements RSGroupInfoManager {
             createRSGroupTable();
           }
           // try reading from the table
-          try (Table table = conn.getTable(RSGROUP_TABLE_NAME)) {
-            table.get(new Get(ROW_KEY));
-          }
-          LOG.info(
-            "RSGroup table=" + RSGROUP_TABLE_NAME + " is online, refreshing cached information");
+          FutureUtils.get(conn.getTable(RSGROUP_TABLE_NAME).get(new Get(ROW_KEY)));
+          LOG.info("RSGroup table={} is online, refreshing cached information", RSGROUP_TABLE_NAME);
           RSGroupInfoManagerImpl.this.refresh(true);
           online = true;
           // flush any inconsistencies between ZK and HTable
@@ -748,8 +767,8 @@ final class RSGroupInfoManagerImpl implements RSGroupInfoManager {
       } else {
         Procedure<?> result = masterServices.getMasterProcedureExecutor().getResult(procId);
         if (result != null && result.isFailed()) {
-          throw new IOException(
-            "Failed to create group table. " + MasterProcedureUtil.unwrapRemoteIOException(result));
+          throw new IOException("Failed to create group table. " +
+              MasterProcedureUtil.unwrapRemoteIOException(result));
         }
       }
     }
@@ -764,33 +783,24 @@ final class RSGroupInfoManagerImpl implements RSGroupInfoManager {
   }
 
   private void multiMutate(List<Mutation> mutations) throws IOException {
-    try (Table table = conn.getTable(RSGROUP_TABLE_NAME)) {
-      CoprocessorRpcChannel channel = table.coprocessorService(ROW_KEY);
-      MultiRowMutationProtos.MutateRowsRequest.Builder mmrBuilder =
-        MultiRowMutationProtos.MutateRowsRequest.newBuilder();
-      for (Mutation mutation : mutations) {
-        if (mutation instanceof Put) {
-          mmrBuilder.addMutationRequest(org.apache.hadoop.hbase.protobuf.ProtobufUtil.toMutation(
-            org.apache.hadoop.hbase.protobuf.generated.ClientProtos.MutationProto.MutationType.PUT,
-            mutation));
-        } else if (mutation instanceof Delete) {
-          mmrBuilder.addMutationRequest(org.apache.hadoop.hbase.protobuf.ProtobufUtil.toMutation(
-            org.apache.hadoop.hbase.protobuf.generated.ClientProtos.MutationProto.MutationType.DELETE,
-            mutation));
-        } else {
-          throw new DoNotRetryIOException(
+    MutateRowsRequest.Builder builder = MutateRowsRequest.newBuilder();
+    for (Mutation mutation : mutations) {
+      if (mutation instanceof Put) {
+        builder
+            .addMutationRequest(ProtobufUtil.toMutation(MutationProto.MutationType.PUT, mutation));
+      } else if (mutation instanceof Delete) {
+        builder.addMutationRequest(
+          ProtobufUtil.toMutation(MutationProto.MutationType.DELETE, mutation));
+      } else {
+        throw new DoNotRetryIOException(
             "multiMutate doesn't support " + mutation.getClass().getName());
-        }
-      }
-
-      MultiRowMutationProtos.MultiRowMutationService.BlockingInterface service =
-        MultiRowMutationProtos.MultiRowMutationService.newBlockingStub(channel);
-      try {
-        service.mutateRows(null, mmrBuilder.build());
-      } catch (ServiceException ex) {
-        ProtobufUtil.toIOException(ex);
       }
     }
+    MutateRowsRequest request = builder.build();
+    AsyncTable<?> table = conn.getTable(RSGROUP_TABLE_NAME);
+    FutureUtils.get(table.<MultiRowMutationService, MutateRowsResponse> coprocessorService(
+      MultiRowMutationService::newStub,
+      (stub, controller, done) -> stub.mutateRows(controller, request, done), ROW_KEY));
   }
 
   private void checkGroupName(String groupName) throws ConstraintException {


[hbase] 06/21: HBASE-22820 Do not need to persist default rs group now (#482)

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

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

commit 2e0039c2a3003e8e78b2d81eff92684460de02df
Author: linkaline <li...@gmail.com>
AuthorDate: Fri Aug 16 16:52:41 2019 +0800

    HBASE-22820 Do not need to persist default rs group now (#482)
    
    Signed-off-by: Duo Zhang <zh...@apache.org>
---
 .../hbase/rsgroup/RSGroupInfoManagerImpl.java      | 40 +++++++++++++---------
 .../hbase/rsgroup/VerifyingRSGroupAdminClient.java | 25 ++++++++++++++
 2 files changed, 49 insertions(+), 16 deletions(-)

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 37f3ce6..eaf23f3 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
@@ -396,11 +396,13 @@ final class RSGroupInfoManagerImpl implements RSGroupInfoManager {
     }
 
     // populate puts
-    for (RSGroupInfo RSGroupInfo : groupMap.values()) {
-      RSGroupProtos.RSGroupInfo proto = ProtobufUtil.toProtoGroupInfo(RSGroupInfo);
-      Put p = new Put(Bytes.toBytes(RSGroupInfo.getName()));
-      p.addColumn(META_FAMILY_BYTES, META_QUALIFIER_BYTES, proto.toByteArray());
-      mutations.add(p);
+    for (RSGroupInfo gi : groupMap.values()) {
+      if (!gi.getName().equals(RSGroupInfo.DEFAULT_GROUP)) {
+        RSGroupProtos.RSGroupInfo proto = ProtobufUtil.toProtoGroupInfo(gi);
+        Put p = new Put(Bytes.toBytes(gi.getName()));
+        p.addColumn(META_FAMILY_BYTES, META_QUALIFIER_BYTES, proto.toByteArray());
+        mutations.add(p);
+      }
     }
 
     if (mutations.size() > 0) {
@@ -449,7 +451,12 @@ final class RSGroupInfoManagerImpl implements RSGroupInfoManager {
 
     // Make changes visible after having been persisted to the source of truth
     resetRSGroupMap(newGroupMap);
+    saveRSGroupMapToZK(newGroupMap);
 
+    updateCacheOfRSGroups(newGroupMap.keySet());
+  }
+
+  private void saveRSGroupMapToZK(Map<String, RSGroupInfo> newGroupMap) throws IOException {
     try {
       String groupBasePath =
           ZNodePaths.joinZNode(watcher.getZNodePaths().baseZNode, RS_GROUP_ZNODE);
@@ -463,14 +470,16 @@ final class RSGroupInfoManagerImpl implements RSGroupInfoManager {
         }
       }
 
-      for (RSGroupInfo RSGroupInfo : newGroupMap.values()) {
-        String znode = ZNodePaths.joinZNode(groupBasePath, RSGroupInfo.getName());
-        RSGroupProtos.RSGroupInfo proto = ProtobufUtil.toProtoGroupInfo(RSGroupInfo);
-        LOG.debug("Updating znode: " + znode);
-        ZKUtil.createAndFailSilent(watcher, znode);
-        zkOps.add(ZKUtil.ZKUtilOp.deleteNodeFailSilent(znode));
-        zkOps.add(ZKUtil.ZKUtilOp.createAndFailSilent(znode,
-          ProtobufUtil.prependPBMagic(proto.toByteArray())));
+      for (RSGroupInfo gi : newGroupMap.values()) {
+        if (!gi.getName().equals(RSGroupInfo.DEFAULT_GROUP)) {
+          String znode = ZNodePaths.joinZNode(groupBasePath, gi.getName());
+          RSGroupProtos.RSGroupInfo proto = ProtobufUtil.toProtoGroupInfo(gi);
+          LOG.debug("Updating znode: " + znode);
+          ZKUtil.createAndFailSilent(watcher, znode);
+          zkOps.add(ZKUtil.ZKUtilOp.deleteNodeFailSilent(znode));
+          zkOps.add(ZKUtil.ZKUtilOp.createAndFailSilent(znode,
+              ProtobufUtil.prependPBMagic(proto.toByteArray())));
+        }
       }
       LOG.debug("Writing ZK GroupInfo count: " + zkOps.size());
 
@@ -480,7 +489,6 @@ final class RSGroupInfoManagerImpl implements RSGroupInfoManager {
       masterServices.abort("Failed to write to rsGroupZNode", e);
       throw new IOException("Failed to write to rsGroupZNode", e);
     }
-    updateCacheOfRSGroups(newGroupMap.keySet());
   }
 
   /**
@@ -540,12 +548,12 @@ final class RSGroupInfoManagerImpl implements RSGroupInfoManager {
 
   // Called by ServerEventsListenerThread. Synchronize on this because redoing
   // the rsGroupMap then writing it out.
-  private synchronized void updateDefaultServers(SortedSet<Address> servers) throws IOException {
+  private synchronized void updateDefaultServers(SortedSet<Address> servers) {
     RSGroupInfo info = rsGroupMap.get(RSGroupInfo.DEFAULT_GROUP);
     RSGroupInfo newInfo = new RSGroupInfo(info.getName(), servers);
     HashMap<String, RSGroupInfo> newGroupMap = Maps.newHashMap(rsGroupMap);
     newGroupMap.put(newInfo.getName(), newInfo);
-    flushConfig(newGroupMap);
+    resetRSGroupMap(newGroupMap);
   }
 
   /**
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 a8cd277..28131a9 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
@@ -26,8 +26,11 @@ 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;
@@ -133,6 +136,13 @@ public class VerifyingRSGroupAdminClient extends RSGroupAdminClient {
       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 (;;) {
@@ -144,8 +154,22 @@ public class VerifyingRSGroupAdminClient extends RSGroupAdminClient {
           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");
@@ -160,6 +184,7 @@ public class VerifyingRSGroupAdminClient extends RSGroupAdminClient {
           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));


[hbase] 12/21: 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 master
in repository https://gitbox.apache.org/repos/asf/hbase.git

commit e12064b7250b246bfef84e83a8ac92272c09ab95
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] 04/21: HBASE-22695 Store the rsgroup of a table in table configuration (#426)

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

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

commit 3709f335a8f5ed14a3bdc1d1f6bff79c13cf94f4
Author: Duo Zhang <zh...@apache.org>
AuthorDate: Sat Aug 3 07:53:27 2019 +0800

    HBASE-22695 Store the rsgroup of a table in table configuration (#426)
    
    Signed-off-by: Guanghao Zhang <zg...@apache.org>
---
 .../org/apache/hadoop/hbase/HTableDescriptor.java  |   6 +
 .../hadoop/hbase/client/TableDescriptor.java       |   8 +
 .../hbase/client/TableDescriptorBuilder.java       |  19 ++
 .../apache/hadoop/hbase/rsgroup/RSGroupInfo.java   |  42 ++-
 .../org/apache/hadoop/hbase/master/HMaster.java    |   4 +-
 .../apache/hadoop/hbase/master/LoadBalancer.java   |  49 +---
 .../hbase/master/assignment/AssignmentManager.java |   6 +-
 .../apache/hadoop/hbase/rsgroup/RSGroupAdmin.java  |  23 --
 .../hadoop/hbase/rsgroup/RSGroupAdminClient.java   |  13 +-
 .../hadoop/hbase/rsgroup/RSGroupAdminEndpoint.java | 152 ++++------
 .../hadoop/hbase/rsgroup/RSGroupAdminServer.java   | 315 ++++++---------------
 .../hbase/rsgroup/RSGroupAdminServiceImpl.java     | 111 ++++++--
 .../hbase/rsgroup/RSGroupBasedLoadBalancer.java    |  47 ++-
 .../hadoop/hbase/rsgroup/RSGroupInfoManager.java   |  23 --
 .../hbase/rsgroup/RSGroupInfoManagerImpl.java      | 115 +-------
 .../apache/hadoop/hbase/rsgroup/RSGroupUtil.java   | 113 ++++++++
 .../hadoop/hbase/master/TestRegionPlacement2.java  |   6 +-
 .../balancer/RSGroupableBalancerTestBase.java      |  84 +++---
 .../balancer/TestRSGroupBasedLoadBalancer.java     |  42 ++-
 ...lancerWithStochasticLoadBalancerAsInternal.java |   4 +-
 .../hadoop/hbase/rsgroup/TestRSGroupsAdmin1.java   |   1 -
 .../hadoop/hbase/rsgroup/TestRSGroupsAdmin2.java   | 104 +------
 .../hadoop/hbase/rsgroup/TestRSGroupsBalance.java  |  20 +-
 .../hadoop/hbase/rsgroup/TestRSGroupsBase.java     |   8 +-
 .../hbase/rsgroup/TestRSGroupsOfflineMode.java     |   6 +-
 .../hbase/rsgroup/VerifyingRSGroupAdminClient.java |  67 +++--
 26 files changed, 590 insertions(+), 798 deletions(-)

diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/HTableDescriptor.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/HTableDescriptor.java
index 8866eba..188bed6 100644
--- a/hbase-client/src/main/java/org/apache/hadoop/hbase/HTableDescriptor.java
+++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/HTableDescriptor.java
@@ -23,6 +23,7 @@ import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 import java.util.Set;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
@@ -987,4 +988,9 @@ public class HTableDescriptor implements TableDescriptor, Comparable<HTableDescr
   protected ModifyableTableDescriptor getDelegateeForModification() {
     return delegatee;
   }
+
+  @Override
+  public Optional<String> getRegionServerGroup() {
+    return delegatee.getRegionServerGroup();
+  }
 }
diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/TableDescriptor.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/TableDescriptor.java
index fc5e69e..a452387 100644
--- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/TableDescriptor.java
+++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/TableDescriptor.java
@@ -23,6 +23,7 @@ import java.util.Collection;
 import java.util.Comparator;
 import java.util.Iterator;
 import java.util.Map;
+import java.util.Optional;
 import java.util.Set;
 import java.util.stream.Stream;
 import org.apache.hadoop.hbase.HConstants;
@@ -184,6 +185,13 @@ public interface TableDescriptor {
   String getOwnerString();
 
   /**
+   * Get the region server group this table belongs to. The regions of this table will be placed
+   * only on the region servers within this group. If not present, will be placed on
+   * {@link org.apache.hadoop.hbase.rsgroup.RSGroupInfo#DEFAULT_GROUP}.
+   */
+  Optional<String> getRegionServerGroup();
+
+  /**
    * Getter for accessing the metadata associated with the key.
    *
    * @param key The key.
diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/TableDescriptorBuilder.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/TableDescriptorBuilder.java
index 20acf3a..3b68007 100644
--- a/hbase-client/src/main/java/org/apache/hadoop/hbase/client/TableDescriptorBuilder.java
+++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/client/TableDescriptorBuilder.java
@@ -39,6 +39,7 @@ import org.apache.hadoop.hbase.Coprocessor;
 import org.apache.hadoop.hbase.HConstants;
 import org.apache.hadoop.hbase.TableName;
 import org.apache.hadoop.hbase.exceptions.DeserializationException;
+import org.apache.hadoop.hbase.rsgroup.RSGroupInfo;
 import org.apache.hadoop.hbase.security.User;
 import org.apache.hadoop.hbase.util.Bytes;
 import org.apache.yetus.audience.InterfaceAudience;
@@ -189,6 +190,9 @@ public class TableDescriptorBuilder {
   private static final Bytes PRIORITY_KEY
           = new Bytes(Bytes.toBytes(PRIORITY));
 
+  private static final Bytes RSGROUP_KEY =
+      new Bytes(Bytes.toBytes(RSGroupInfo.TABLE_DESC_PROP_GROUP));
+
   /**
    * Relative priority of the table used for rpc scheduling
    */
@@ -538,6 +542,11 @@ public class TableDescriptorBuilder {
     return this;
   }
 
+  public TableDescriptorBuilder setRegionServerGroup(String group) {
+    desc.setValue(RSGROUP_KEY, new Bytes(Bytes.toBytes(group)));
+    return this;
+  }
+
   public TableDescriptor build() {
     return new ModifyableTableDescriptor(desc);
   }
@@ -1580,6 +1589,16 @@ public class TableDescriptorBuilder {
     public int getColumnFamilyCount() {
       return families.size();
     }
+
+    @Override
+    public Optional<String> getRegionServerGroup() {
+      Bytes value = values.get(RSGROUP_KEY);
+      if (value != null) {
+        return Optional.of(Bytes.toString(value.get(), value.getOffset(), value.getLength()));
+      } else {
+        return Optional.empty();
+      }
+    }
   }
 
   private static Optional<CoprocessorDescriptor> toCoprocessorDescriptor(String spec) {
diff --git a/hbase-common/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupInfo.java b/hbase-common/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupInfo.java
index 25e827d..ad55d1f 100644
--- a/hbase-common/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupInfo.java
+++ b/hbase-common/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupInfo.java
@@ -34,21 +34,38 @@ import org.apache.yetus.audience.InterfaceAudience;
 public class RSGroupInfo {
   public static final String DEFAULT_GROUP = "default";
   public static final String NAMESPACE_DESC_PROP_GROUP = "hbase.rsgroup.name";
+  public static final String TABLE_DESC_PROP_GROUP = "hbase.rsgroup.name";
 
   private final String name;
   // Keep servers in a sorted set so has an expected ordering when displayed.
   private final SortedSet<Address> servers;
   // Keep tables sorted too.
+  /**
+   * @deprecated Since 3.0.0, will be removed in 4.0.0. The rsgroup information will be stored in
+   *             the configuration of a table so this will be removed.
+   */
+  @Deprecated
   private final SortedSet<TableName> tables;
 
   public RSGroupInfo(String name) {
     this(name, new TreeSet<Address>(), new TreeSet<TableName>());
   }
 
+  RSGroupInfo(String name, SortedSet<Address> servers) {
+    this.name = name;
+    this.servers = servers == null ? new TreeSet<>() : new TreeSet<>(servers);
+    this.tables = new TreeSet<>();
+  }
+
+  /**
+   * @deprecated Since 3.0.0, will be removed in 4.0.0. The rsgroup information for a table will be
+   *             stored in the configuration of a table so this will be removed.
+   */
+  @Deprecated
   RSGroupInfo(String name, SortedSet<Address> servers, SortedSet<TableName> tables) {
     this.name = name;
     this.servers = (servers == null) ? new TreeSet<>() : new TreeSet<>(servers);
-    this.tables  = (tables  == null) ? new TreeSet<>() : new TreeSet<>(tables);
+    this.tables = (tables == null) ? new TreeSet<>() : new TreeSet<>(tables);
   }
 
   public RSGroupInfo(RSGroupInfo src) {
@@ -100,23 +117,46 @@ public class RSGroupInfo {
 
   /**
    * Get set of tables that are members of the group.
+   * @deprecated Since 3.0.0, will be removed in 4.0.0. The rsgroup information will be stored in
+   *             the configuration of a table so this will be removed.
    */
+  @Deprecated
   public SortedSet<TableName> getTables() {
     return tables;
   }
 
+  /**
+   * @deprecated Since 3.0.0, will be removed in 4.0.0. The rsgroup information will be stored in
+   *             the configuration of a table so this will be removed.
+   */
+  @Deprecated
   public void addTable(TableName table) {
     tables.add(table);
   }
 
+  /**
+   * @deprecated Since 3.0.0, will be removed in 4.0.0. The rsgroup information will be stored in
+   *             the configuration of a table so this will be removed.
+   */
+  @Deprecated
   public void addAllTables(Collection<TableName> arg) {
     tables.addAll(arg);
   }
 
+  /**
+   * @deprecated Since 3.0.0, will be removed in 4.0.0. The rsgroup information will be stored in
+   *             the configuration of a table so this will be removed.
+   */
+  @Deprecated
   public boolean containsTable(TableName table) {
     return tables.contains(table);
   }
 
+  /**
+   * @deprecated Since 3.0.0, will be removed in 4.0.0. The rsgroup information will be stored in
+   *             the configuration of a table so this will be removed.
+   */
+  @Deprecated
   public boolean removeTable(TableName table) {
     return tables.remove(table);
   }
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 5a76b79..77ec39b 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
@@ -2012,7 +2012,7 @@ public class HMaster extends HRegionServer implements MasterServices {
   // Replace with an async implementation from which you can get
   // a success/failure result.
   @VisibleForTesting
-  public void move(final byte[] encodedRegionName, byte[] destServerName) throws HBaseIOException {
+  public void move(final byte[] encodedRegionName, byte[] destServerName) throws IOException {
     RegionState regionState = assignmentManager.getRegionStates().
       getRegionState(Bytes.toString(encodedRegionName));
 
@@ -3615,7 +3615,7 @@ public class HMaster extends HRegionServer implements MasterServices {
    * @param servers Region servers to decommission.
    */
   public void decommissionRegionServers(final List<ServerName> servers, final boolean offload)
-      throws HBaseIOException {
+      throws IOException {
     List<ServerName> serversAdded = new ArrayList<>(servers.size());
     // Place the decommission marker first.
     String parentZnode = getZooKeeper().getZNodePaths().drainingZNode;
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/LoadBalancer.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/LoadBalancer.java
index 816636f..0fc544a 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/LoadBalancer.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/LoadBalancer.java
@@ -19,12 +19,12 @@
 package org.apache.hadoop.hbase.master;
 
 import edu.umd.cs.findbugs.annotations.Nullable;
+import java.io.IOException;
 import java.util.List;
 import java.util.Map;
 import org.apache.hadoop.conf.Configurable;
 import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.hbase.ClusterMetrics;
-import org.apache.hadoop.hbase.HBaseIOException;
 import org.apache.hadoop.hbase.ServerName;
 import org.apache.hadoop.hbase.Stoppable;
 import org.apache.hadoop.hbase.TableName;
@@ -65,95 +65,72 @@ public interface LoadBalancer extends Configurable, Stoppable, ConfigurationObse
   ServerName BOGUS_SERVER_NAME = ServerName.valueOf("localhost,1,1");
 
   /**
-   * Set the current cluster status.  This allows a LoadBalancer to map host name to a server
-   * @param st
+   * Set the current cluster status. This allows a LoadBalancer to map host name to a server
    */
   void setClusterMetrics(ClusterMetrics st);
 
   /**
    * Pass RegionStates and allow balancer to set the current cluster load.
-   * @param ClusterLoad
    */
   void setClusterLoad(Map<TableName, Map<ServerName, List<RegionInfo>>> ClusterLoad);
 
   /**
    * Set the master service.
-   * @param masterServices
    */
   void setMasterServices(MasterServices masterServices);
 
   /**
    * Perform the major balance operation
-   * @param tableName
-   * @param clusterState
    * @return List of plans
    */
-  List<RegionPlan> balanceCluster(TableName tableName, Map<ServerName,
-      List<RegionInfo>> clusterState) throws HBaseIOException;
+  List<RegionPlan> balanceCluster(TableName tableName,
+      Map<ServerName, List<RegionInfo>> clusterState) throws IOException;
 
   /**
    * Perform the major balance operation
-   * @param clusterState
    * @return List of plans
    */
-  List<RegionPlan> balanceCluster(Map<ServerName,
-      List<RegionInfo>> clusterState) throws HBaseIOException;
+  List<RegionPlan> balanceCluster(Map<ServerName, List<RegionInfo>> clusterState)
+      throws IOException;
 
   /**
    * Perform a Round Robin assignment of regions.
-   * @param regions
-   * @param servers
    * @return Map of servername to regioninfos
    */
-  Map<ServerName, List<RegionInfo>> roundRobinAssignment(
-    List<RegionInfo> regions,
-    List<ServerName> servers
-  ) throws HBaseIOException;
+  Map<ServerName, List<RegionInfo>> roundRobinAssignment(List<RegionInfo> regions,
+      List<ServerName> servers) throws IOException;
 
   /**
    * Assign regions to the previously hosting region server
-   * @param regions
-   * @param servers
    * @return List of plans
    */
   @Nullable
-  Map<ServerName, List<RegionInfo>> retainAssignment(
-    Map<RegionInfo, ServerName> regions,
-    List<ServerName> servers
-  ) throws HBaseIOException;
+  Map<ServerName, List<RegionInfo>> retainAssignment(Map<RegionInfo, ServerName> regions,
+      List<ServerName> servers) throws IOException;
 
   /**
    * Get a random region server from the list
    * @param regionInfo Region for which this selection is being done.
-   * @param servers
-   * @return Servername
    */
-  ServerName randomAssignment(
-    RegionInfo regionInfo, List<ServerName> servers
-  ) throws HBaseIOException;
+  ServerName randomAssignment(RegionInfo regionInfo, List<ServerName> servers) throws IOException;
 
   /**
    * Initialize the load balancer. Must be called after setters.
-   * @throws HBaseIOException
    */
-  void initialize() throws HBaseIOException;
+  void initialize() throws IOException;
 
   /**
    * Marks the region as online at balancer.
-   * @param regionInfo
-   * @param sn
    */
   void regionOnline(RegionInfo regionInfo, ServerName sn);
 
   /**
    * Marks the region as offline at balancer.
-   * @param regionInfo
    */
   void regionOffline(RegionInfo regionInfo);
 
-  /*
+  /**
    * Notification that config has changed
-   * @param conf
    */
   @Override
   void onConfigurationChange(Configuration conf);
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/assignment/AssignmentManager.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/assignment/AssignmentManager.java
index d524c25..0f7f396 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/assignment/AssignmentManager.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/assignment/AssignmentManager.java
@@ -688,7 +688,7 @@ public class AssignmentManager {
         this.master.getServerManager().createDestinationServersList(serversToExclude));
       // Return mid-method!
       return createAssignProcedures(assignments);
-    } catch (HBaseIOException hioe) {
+    } catch (IOException hioe) {
       LOG.warn("Failed roundRobinAssignment", hioe);
     }
     // If an error above, fall-through to this simpler assign. Last resort.
@@ -2058,7 +2058,7 @@ public class AssignmentManager {
       }
       try {
         acceptPlan(regions, balancer.retainAssignment(retainMap, servers));
-      } catch (HBaseIOException e) {
+      } catch (IOException e) {
         LOG.warn("unable to retain assignment", e);
         addToPendingAssignment(regions, retainMap.keySet());
       }
@@ -2073,7 +2073,7 @@ public class AssignmentManager {
       }
       try {
         acceptPlan(regions, balancer.roundRobinAssignment(hris, servers));
-      } catch (HBaseIOException e) {
+      } catch (IOException e) {
         LOG.warn("unable to round-robin assignment", e);
         addToPendingAssignment(regions, hris);
       }
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 9ea996b..344d0b3 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,8 +20,6 @@ package org.apache.hadoop.hbase.rsgroup;
 import java.io.IOException;
 import java.util.List;
 import java.util.Set;
-
-import org.apache.hadoop.hbase.TableName;
 import org.apache.hadoop.hbase.net.Address;
 import org.apache.yetus.audience.InterfaceAudience;
 
@@ -36,22 +34,11 @@ public interface RSGroupAdmin {
   RSGroupInfo getRSGroupInfo(String groupName) throws IOException;
 
   /**
-   * Gets {@code RSGroupInfo} for the given table's group.
-   */
-  RSGroupInfo getRSGroupInfoOfTable(TableName tableName) throws IOException;
-
-  /**
    * Move given set of servers to the specified target RegionServer group.
    */
   void moveServers(Set<Address> servers, String targetGroup) throws IOException;
 
   /**
-   * 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.
-   */
-  void moveTables(Set<TableName> tables, String targetGroup) throws IOException;
-
-  /**
    * Creates a new RegionServer group with the given name.
    */
   void addRSGroup(String groupName) throws IOException;
@@ -80,16 +67,6 @@ public interface RSGroupAdmin {
   RSGroupInfo getRSGroupOfServer(Address hostPort) throws IOException;
 
   /**
-   * 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
-   */
-  void moveServersAndTables(Set<Address> servers, Set<TableName> tables,
-                            String targetGroup) 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.
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 e7ab7f2..07f0efd 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
@@ -47,6 +47,7 @@ import org.apache.hadoop.hbase.protobuf.generated.RSGroupAdminProtos.RemoveServe
 import org.apache.hadoop.hbase.protobuf.generated.RSGroupProtos;
 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;
 
 /**
@@ -62,12 +63,17 @@ public class RSGroupAdminClient implements RSGroupAdmin {
     stub = RSGroupAdminService.newBlockingStub(admin.coprocessorService());
   }
 
+  // for writing UTs
+  @VisibleForTesting
+  protected RSGroupAdminClient() {
+  }
+
   @Override
   public RSGroupInfo getRSGroupInfo(String groupName) throws IOException {
     try {
       GetRSGroupInfoResponse resp = stub.getRSGroupInfo(null,
-          GetRSGroupInfoRequest.newBuilder().setRSGroupName(groupName).build());
-      if(resp.hasRSGroupInfo()) {
+        GetRSGroupInfoRequest.newBuilder().setRSGroupName(groupName).build());
+      if (resp.hasRSGroupInfo()) {
         return ProtobufUtil.toGroupInfo(resp.getRSGroupInfo());
       }
       return null;
@@ -76,7 +82,6 @@ public class RSGroupAdminClient implements RSGroupAdmin {
     }
   }
 
-  @Override
   public RSGroupInfo getRSGroupInfoOfTable(TableName tableName) throws IOException {
     GetRSGroupInfoOfTableRequest request = GetRSGroupInfoOfTableRequest.newBuilder().setTableName(
         ProtobufUtil.toProtoTableName(tableName)).build();
@@ -111,7 +116,6 @@ public class RSGroupAdminClient implements RSGroupAdmin {
     }
   }
 
-  @Override
   public void moveTables(Set<TableName> tables, String targetGroup) throws IOException {
     MoveTablesRequest.Builder builder = MoveTablesRequest.newBuilder().setTargetGroup(targetGroup);
     for(TableName tableName: tables) {
@@ -192,7 +196,6 @@ public class RSGroupAdminClient implements RSGroupAdmin {
     }
   }
 
-  @Override
   public void moveServersAndTables(Set<Address> servers, Set<TableName> tables, String targetGroup)
       throws IOException {
     MoveServersAndTablesRequest.Builder builder =
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 2d5af04..3c4530f 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
@@ -27,13 +27,10 @@ import java.util.stream.Collectors;
 import org.apache.hadoop.hbase.CoprocessorEnvironment;
 import org.apache.hadoop.hbase.HBaseIOException;
 import org.apache.hadoop.hbase.HConstants;
-import org.apache.hadoop.hbase.MasterNotRunningException;
 import org.apache.hadoop.hbase.NamespaceDescriptor;
-import org.apache.hadoop.hbase.PleaseHoldException;
 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.SnapshotDescription;
 import org.apache.hadoop.hbase.client.TableDescriptor;
 import org.apache.hadoop.hbase.constraint.ConstraintException;
 import org.apache.hadoop.hbase.coprocessor.CoreCoprocessor;
@@ -47,21 +44,16 @@ import org.apache.hadoop.hbase.net.Address;
 import org.apache.hadoop.hbase.security.UserProvider;
 import org.apache.hadoop.hbase.security.access.AccessChecker;
 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.com.google.common.collect.Sets;
 
 // TODO: Encapsulate MasterObserver functions into separate subclass.
 @CoreCoprocessor
 @InterfaceAudience.Private
 public class RSGroupAdminEndpoint implements MasterCoprocessor, MasterObserver {
-  static final Logger LOG = LoggerFactory.getLogger(RSGroupAdminEndpoint.class);
-
-  private MasterServices master;
   // 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();
@@ -110,117 +102,91 @@ public class RSGroupAdminEndpoint implements MasterCoprocessor, MasterObserver {
     return groupAdminService;
   }
 
-  private void assignTableToGroup(TableDescriptor desc) throws IOException {
-    String groupName =
-        master.getClusterSchema().getNamespace(desc.getTableName().getNamespaceAsString())
-            .getConfigurationValue(RSGroupInfo.NAMESPACE_DESC_PROP_GROUP);
-    if (groupName == null) {
-      groupName = RSGroupInfo.DEFAULT_GROUP;
-    }
-    RSGroupInfo rsGroupInfo = groupAdminServer.getRSGroupInfo(groupName);
-    if (rsGroupInfo == null) {
-      throw new ConstraintException(
-          "Default RSGroup (" + groupName + ") for this table's namespace does not exist.");
-    }
-    if (!rsGroupInfo.containsTable(desc.getTableName())) {
-      LOG.debug("Pre-moving table " + desc.getTableName() + " to RSGroup " + groupName);
-      groupAdminServer.moveTables(Sets.newHashSet(desc.getTableName()), groupName);
-    }
-  }
-
   /////////////////////////////////////////////////////////////////////////////
   // MasterObserver overrides
   /////////////////////////////////////////////////////////////////////////////
 
-  private boolean rsgroupHasServersOnline(TableDescriptor desc) throws IOException {
-    String groupName;
-    try {
-      groupName = master.getClusterSchema().getNamespace(desc.getTableName().getNamespaceAsString())
-          .getConfigurationValue(RSGroupInfo.NAMESPACE_DESC_PROP_GROUP);
-      if (groupName == null) {
-        groupName = RSGroupInfo.DEFAULT_GROUP;
-      }
-    } catch (MasterNotRunningException | PleaseHoldException e) {
-      LOG.info("Master has not initialized yet; temporarily using default RSGroup '" +
-          RSGroupInfo.DEFAULT_GROUP + "' for deploy of system table");
-      groupName = RSGroupInfo.DEFAULT_GROUP;
+  @Override
+  public void postClearDeadServers(ObserverContext<MasterCoprocessorEnvironment> ctx,
+      List<ServerName> servers, List<ServerName> notClearedServers) throws IOException {
+    Set<Address> clearedServer =
+        servers.stream().filter(server -> !notClearedServers.contains(server))
+            .map(ServerName::getAddress).collect(Collectors.toSet());
+    if (!clearedServer.isEmpty()) {
+      groupAdminServer.removeServers(clearedServer);
     }
+  }
 
-    RSGroupInfo rsGroupInfo = groupAdminServer.getRSGroupInfo(groupName);
-    if (rsGroupInfo == null) {
-      throw new ConstraintException(
-          "Default RSGroup (" + groupName + ") for this table's " + "namespace does not exist.");
+  private void checkGroupExists(Optional<String> optGroupName) throws IOException {
+    if (optGroupName.isPresent()) {
+      String groupName = optGroupName.get();
+      if (groupAdminServer.getRSGroupInfo(groupName) == null) {
+        throw new ConstraintException("Region server group " + groupName + " does not exit");
+      }
     }
+  }
 
-    for (ServerName onlineServer : master.getServerManager().createDestinationServersList()) {
-      if (rsGroupInfo.getServers().contains(onlineServer.getAddress())) {
+  private boolean rsgroupHasServersOnline(TableDescriptor desc) throws IOException {
+    RSGroupInfo rsGroupInfo;
+    Optional<String> optGroupName = desc.getRegionServerGroup();
+    if (optGroupName.isPresent()) {
+      String groupName = optGroupName.get();
+      if (groupName.equals(RSGroupInfo.DEFAULT_GROUP)) {
+        // do not check for default group
+        return true;
+      }
+      rsGroupInfo = groupAdminServer.getRSGroupInfo(groupName);
+      if (rsGroupInfo == null) {
+        throw new ConstraintException(
+            "RSGroup " + groupName + " for table " + desc.getTableName() + " does not exist");
+      }
+    } else {
+      NamespaceDescriptor nd =
+          master.getClusterSchema().getNamespace(desc.getTableName().getNamespaceAsString());
+      String groupNameOfNs = nd.getConfigurationValue(RSGroupInfo.NAMESPACE_DESC_PROP_GROUP);
+      if (groupNameOfNs == null || groupNameOfNs.equals(RSGroupInfo.DEFAULT_GROUP)) {
+        // do not check for default group
         return true;
       }
+      rsGroupInfo = groupAdminServer.getRSGroupInfo(groupNameOfNs);
+      if (rsGroupInfo == null) {
+        throw new ConstraintException("RSGroup " + groupNameOfNs + " for table " +
+            desc.getTableName() + "(inherit from namespace) does not exist");
+      }
     }
-    return false;
+    return master.getServerManager().createDestinationServersList().stream()
+        .anyMatch(onlineServer -> rsGroupInfo.containsServer(onlineServer.getAddress()));
   }
 
   @Override
-  public void preCreateTableAction(final ObserverContext<MasterCoprocessorEnvironment> ctx,
-      final TableDescriptor desc, final RegionInfo[] regions) throws IOException {
+  public void preCreateTableAction(ObserverContext<MasterCoprocessorEnvironment> ctx,
+      TableDescriptor desc, RegionInfo[] regions) throws IOException {
+    checkGroupExists(desc.getRegionServerGroup());
     if (!desc.getTableName().isSystemTable() && !rsgroupHasServersOnline(desc)) {
-      throw new HBaseIOException("No online servers in the rsgroup, which table " +
-          desc.getTableName().getNameAsString() + " belongs to");
+      throw new HBaseIOException("No online servers in the rsgroup for " + desc);
     }
   }
 
-  // Assign table to default RSGroup.
-  @Override
-  public void postCreateTable(ObserverContext<MasterCoprocessorEnvironment> ctx,
-      TableDescriptor desc, RegionInfo[] regions) throws IOException {
-    assignTableToGroup(desc);
-  }
-
-  // Remove table from its RSGroup.
   @Override
-  public void postDeleteTable(ObserverContext<MasterCoprocessorEnvironment> ctx,
-      TableName tableName) throws IOException {
-    try {
-      RSGroupInfo group = groupAdminServer.getRSGroupInfoOfTable(tableName);
-      if (group != null) {
-        LOG.debug(String.format("Removing deleted table '%s' from rsgroup '%s'", tableName,
-          group.getName()));
-        groupAdminServer.moveTables(Sets.newHashSet(tableName), null);
-      }
-    } catch (IOException ex) {
-      LOG.debug("Failed to perform RSGroup information cleanup for table: " + tableName, ex);
-    }
+  public TableDescriptor preModifyTable(ObserverContext<MasterCoprocessorEnvironment> ctx,
+      TableName tableName, TableDescriptor currentDescriptor, TableDescriptor newDescriptor)
+      throws IOException {
+    checkGroupExists(newDescriptor.getRegionServerGroup());
+    return MasterObserver.super.preModifyTable(ctx, tableName, currentDescriptor, newDescriptor);
   }
 
   @Override
   public void preCreateNamespace(ObserverContext<MasterCoprocessorEnvironment> ctx,
       NamespaceDescriptor ns) throws IOException {
-    String group = ns.getConfigurationValue(RSGroupInfo.NAMESPACE_DESC_PROP_GROUP);
-    if (group != null && groupAdminServer.getRSGroupInfo(group) == null) {
-      throw new ConstraintException("Region server group " + group + " does not exit");
-    }
+    checkGroupExists(
+      Optional.ofNullable(ns.getConfigurationValue(RSGroupInfo.NAMESPACE_DESC_PROP_GROUP)));
   }
 
   @Override
   public void preModifyNamespace(ObserverContext<MasterCoprocessorEnvironment> ctx,
-      NamespaceDescriptor currentNsDesc, NamespaceDescriptor newNsDesc) throws IOException {
-    preCreateNamespace(ctx, newNsDesc);
-  }
-
-  @Override
-  public void preCloneSnapshot(ObserverContext<MasterCoprocessorEnvironment> ctx,
-      SnapshotDescription snapshot, TableDescriptor desc) throws IOException {
-    assignTableToGroup(desc);
-  }
-
-  @Override
-  public void postClearDeadServers(ObserverContext<MasterCoprocessorEnvironment> ctx,
-      List<ServerName> servers, List<ServerName> notClearedServers) throws IOException {
-    Set<Address> clearedServer =
-        servers.stream().filter(server -> !notClearedServers.contains(server))
-            .map(ServerName::getAddress).collect(Collectors.toSet());
-    if (!clearedServer.isEmpty()) {
-      groupAdminServer.removeServers(clearedServer);
-    }
+      NamespaceDescriptor currentNsDescriptor, NamespaceDescriptor newNsDescriptor)
+      throws IOException {
+    checkGroupExists(Optional
+        .ofNullable(newNsDescriptor.getConfigurationValue(RSGroupInfo.NAMESPACE_DESC_PROP_GROUP)));
   }
 }
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
index 07689c8..947251a 100644
--- 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
@@ -26,15 +26,16 @@ 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;
@@ -42,7 +43,6 @@ 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.AssignmentManager;
 import org.apache.hadoop.hbase.master.assignment.RegionStateNode;
 import org.apache.hadoop.hbase.net.Address;
 import org.apache.yetus.audience.InterfaceAudience;
@@ -85,14 +85,6 @@ public class RSGroupAdminServer implements RSGroupAdmin {
     return rsGroupInfoManager.getRSGroup(groupName);
   }
 
-  @Override
-  public RSGroupInfo getRSGroupInfoOfTable(TableName tableName) throws IOException {
-    // We are reading across two Maps in the below with out synchronizing across
-    // them; should be safe most of the time.
-    String groupName = rsGroupInfoManager.getRSGroupOfTable(tableName);
-    return groupName == null? null: 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.
@@ -159,104 +151,24 @@ public class RSGroupAdminServer implements RSGroupAdmin {
   }
 
   /**
-   * Check servers and tables.
-   *
-   * @param servers servers to move
-   * @param tables tables to move
-   * @param targetGroupName target group name
-   * @throws IOException if nulls or if servers and tables not belong to the same group
-   */
-  private void checkServersAndTables(Set<Address> servers, Set<TableName> tables,
-                                     String targetGroupName) throws IOException {
-    // Presume first server's source group. Later ensure all servers are from this group.
-    Address firstServer = servers.iterator().next();
-    RSGroupInfo tmpSrcGrp = rsGroupInfoManager.getRSGroupOfServer(firstServer);
-    if (tmpSrcGrp == null) {
-      // Be careful. This exception message is tested for in TestRSGroupsBase...
-      throw new ConstraintException("Source RSGroup for server " + firstServer
-              + " does not exist.");
-    }
-    RSGroupInfo srcGrp = new RSGroupInfo(tmpSrcGrp);
-
-    // Only move online servers
-    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);
-      }
-    }
-
-    // Ensure all tables and servers are of same rsgroup.
-    for (TableName table : tables) {
-      String tmpGroup = rsGroupInfoManager.getRSGroupOfTable(table);
-      if (!tmpGroup.equals(srcGrp.getName())) {
-        throw new ConstraintException("Move table request should only come from one source " +
-                "RSGroup. Expecting only " + srcGrp.getName() + " but contains " + tmpGroup);
-      }
-    }
-
-    if (srcGrp.getServers().size() <= servers.size() && srcGrp.getTables().size() > tables.size()) {
-      throw new ConstraintException("Cannot leave a RSGroup " + srcGrp.getName() +
-              " that contains tables without servers to host them.");
-    }
-  }
-
-  /**
-   * Move every region from servers which are currently located on these servers,
-   * but should not be located there.
-   *
+   * 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 {
-          RSGroupInfo group = getRSGroupInfo(targetGroupName);
-          return group.containsTable(info.getTable());
-        } catch (IOException e) {
-          e.printStackTrace();
-          return false;
-        }
-      },
-      rs -> rs.getHostname());
-  }
-
-  /**
-   * Moves regions of tables which are not on target group servers.
-   *
-   * @param tables the tables that will move to new group
-   * @param targetGroupName the target group name
-   * @throws IOException if moving the region fails
-   */
-  private void moveTableRegionsToGroup(Set<TableName> tables, String targetGroupName)
-    throws IOException {
-    moveRegionsBetweenGroups(tables, targetGroupName,
-      table -> {
-        if (master.getAssignmentManager().isTableDisabled(table)) {
-          return new ArrayList<>();
-        }
-        return master.getAssignmentManager().getRegionStates().getRegionsOfTable(table);
-      },
-      info -> {
-        try {
-          RSGroupInfo group = getRSGroupInfo(targetGroupName);
-          ServerName sn =
-              master.getAssignmentManager().getRegionStates().getRegionServerOfRegion(info);
-          return group.containsServer(sn.getAddress());
-        } catch (IOException e) {
-          e.printStackTrace();
-          return false;
-        }
-      },
-      table -> table.getNameWithNamespaceInclAsString());
+    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,
@@ -322,9 +234,6 @@ public class RSGroupAdminServer implements RSGroupAdmin {
     }
   }
 
-  @edu.umd.cs.findbugs.annotations.SuppressWarnings(
-      value="RCN_REDUNDANT_NULLCHECK_WOULD_HAVE_BEEN_A_NPE",
-      justification="Ignoring complaint because don't know what it is complaining about")
   @Override
   public void moveServers(Set<Address> servers, String targetGroupName) throws IOException {
     if (servers == null) {
@@ -365,9 +274,16 @@ public class RSGroupAdminServer implements RSGroupAdmin {
               "RSGroup. Expecting only " + srcGrp.getName() + " but contains " + tmpGroup);
         }
       }
-      if (srcGrp.getServers().size() <= servers.size() && srcGrp.getTables().size() > 0) {
-        throw new ConstraintException("Cannot leave a RSGroup " + srcGrp.getName() +
-            " that contains tables without servers to host them.");
+      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'.
@@ -379,38 +295,6 @@ public class RSGroupAdminServer implements RSGroupAdmin {
   }
 
   @Override
-  public void moveTables(Set<TableName> tables, String targetGroup) throws IOException {
-    if (tables == null) {
-      throw new ConstraintException("The list of servers cannot be null.");
-    }
-    if (tables.size() < 1) {
-      LOG.debug("moveTables() passed an empty set. Ignoring.");
-      return;
-    }
-
-    // Hold a lock on the manager instance while moving servers to prevent
-    // another writer changing our state while we are working.
-    synchronized (rsGroupInfoManager) {
-      if(targetGroup != null) {
-        RSGroupInfo destGroup = rsGroupInfoManager.getRSGroup(targetGroup);
-        if(destGroup == null) {
-          throw new ConstraintException("Target " + targetGroup + " RSGroup does not exist.");
-        }
-        if(destGroup.getServers().size() < 1) {
-          throw new ConstraintException("Target RSGroup must have at least one server.");
-        }
-      }
-      rsGroupInfoManager.moveTables(tables, targetGroup);
-
-      // targetGroup is null when a table is being deleted. In this case no further
-      // action is required.
-      if (targetGroup != null) {
-        moveTableRegionsToGroup(tables, targetGroup);
-      }
-    }
-  }
-
-  @Override
   public void addRSGroup(String name) throws IOException {
     rsGroupInfoManager.addRSGroup(new RSGroupInfo(name));
   }
@@ -424,17 +308,18 @@ public class RSGroupAdminServer implements RSGroupAdmin {
       if (rsGroupInfo == null) {
         throw new ConstraintException("RSGroup " + name + " does not exist");
       }
-      int tableCount = rsGroupInfo.getTables().size();
-      if (tableCount > 0) {
-        throw new ConstraintException("RSGroup " + name + " has " + tableCount +
-            " tables; you must remove these tables from the rsgroup before " +
-            "the rsgroup can be removed.");
-      }
       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.");
+          " 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);
@@ -459,27 +344,29 @@ public class RSGroupAdminServer implements RSGroupAdmin {
       }
 
       if (getRSGroupInfo(groupName) == null) {
-        throw new ConstraintException("RSGroup does not exist: "+groupName);
+        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));
+          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());
+          serverManager.getDeadServers());
         return false;
       }
 
-      //We balance per group instead of per table
+      // We balance per group instead of per table
       List<RegionPlan> plans = new ArrayList<>();
-      for(Map.Entry<TableName, Map<ServerName, List<RegionInfo>>> tableMap:
-          getRSGroupAssignmentsByTable(groupName).entrySet()) {
+      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);
@@ -508,104 +395,66 @@ public class RSGroupAdminServer implements RSGroupAdmin {
   }
 
   @Override
-  public void moveServersAndTables(Set<Address> servers, Set<TableName> tables, String targetGroup)
-      throws IOException {
+  public void removeServers(Set<Address> servers) throws IOException {
     if (servers == null || servers.isEmpty()) {
-      throw new ConstraintException("The list of servers to move cannot be null or empty.");
-    }
-    if (tables == null || tables.isEmpty()) {
-      throw new ConstraintException("The list of tables to move cannot be null or empty.");
+      throw new ConstraintException("The set of servers to remove cannot be null or empty.");
     }
-
-    //check target group
-    getAndCheckRSGroupInfo(targetGroup);
-
-    // Hold a lock on the manager instance while moving servers and tables to prevent
+    // Hold a lock on the manager instance while moving servers to prevent
     // another writer changing our state while we are working.
     synchronized (rsGroupInfoManager) {
-      //check servers and tables status
-      checkServersAndTables(servers, tables, targetGroup);
-
-      //Move servers and tables to a new group.
-      String srcGroup = getRSGroupOfServer(servers.iterator().next()).getName();
-      rsGroupInfoManager.moveServersAndTables(servers, tables, srcGroup, targetGroup);
-
-      //move regions on these servers which do not belong to group tables
-      moveServerRegionsFromGroup(servers, targetGroup);
-      //move regions of these tables which are not on group servers
-      moveTableRegionsToGroup(tables, targetGroup);
+      // check the set of servers
+      checkForDeadOrOnlineServers(servers);
+      rsGroupInfoManager.removeServers(servers);
+      LOG.info("Remove decommissioned servers {} from RSGroup done", servers);
     }
-    LOG.info("Move servers and tables done. Severs: {}, Tables: {} => {}", servers, tables,
-        targetGroup);
   }
 
-  @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 {
+    throws IOException {
     Map<String, RegionState> rit = Maps.newTreeMap();
-    AssignmentManager am = master.getAssignmentManager();
-    for(TableName tableName : getRSGroupInfo(groupName).getTables()) {
-      for(RegionInfo regionInfo: am.getRegionStates().getRegionsOfTable(tableName)) {
-        RegionState state = am.getRegionStates().getRegionTransitionState(regionInfo);
-        if(state != null) {
-          rit.put(regionInfo.getEncodedName(), state);
-        }
+    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 {
+    getRSGroupAssignmentsByTable(String groupName) throws IOException {
     Map<TableName, Map<ServerName, List<RegionInfo>>> result = Maps.newHashMap();
-    RSGroupInfo rsGroupInfo = getRSGroupInfo(groupName);
-    Map<TableName, Map<ServerName, List<RegionInfo>>> assignments = Maps.newHashMap();
-    for(Map.Entry<RegionInfo, ServerName> entry:
-        master.getAssignmentManager().getRegionStates().getRegionAssignments().entrySet()) {
-      TableName currTable = entry.getKey().getTable();
-      ServerName currServer = entry.getValue();
-      RegionInfo currRegion = entry.getKey();
-      if (rsGroupInfo.getTables().contains(currTable)) {
-        assignments.computeIfAbsent(currTable, key -> new HashMap<>())
-            .computeIfAbsent(currServer, key -> new ArrayList<>())
-            .add(currRegion);
+    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);
       }
     }
-
-    Map<ServerName, List<RegionInfo>> serverMap = Maps.newHashMap();
-    for(ServerName serverName: master.getServerManager().getOnlineServers().keySet()) {
-      if(rsGroupInfo.getServers().contains(serverName.getAddress())) {
-        serverMap.put(serverName, Collections.emptyList());
-      }
-    }
-
-    // add all tables that are members of the group
-    for (TableName tableName : rsGroupInfo.getTables()) {
-      if (assignments.containsKey(tableName)) {
-        Map<ServerName, List<RegionInfo>> tableResults = new HashMap<>(serverMap);
-
-        Map<ServerName, List<RegionInfo>> tableAssignments = assignments.get(tableName);
-        tableResults.putAll(tableAssignments);
-
-        result.put(tableName, tableResults);
-
-        LOG.debug("Adding assignments for {}: {}", tableName, tableAssignments);
+    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());
+        }
       }
     }
 
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 918a4fe..6bc4519 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
@@ -20,14 +20,24 @@ package org.apache.hadoop.hbase.rsgroup;
 import com.google.protobuf.RpcCallback;
 import com.google.protobuf.RpcController;
 import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
+import java.util.stream.Collectors;
+import org.apache.hadoop.hbase.HConstants;
 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;
+import org.apache.hadoop.hbase.procedure2.Procedure;
 import org.apache.hadoop.hbase.protobuf.ProtobufUtil;
 import org.apache.hadoop.hbase.protobuf.generated.HBaseProtos;
 import org.apache.hadoop.hbase.protobuf.generated.RSGroupAdminProtos;
@@ -57,6 +67,8 @@ 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;
@@ -68,6 +80,8 @@ import org.apache.hbase.thirdparty.com.google.common.collect.Sets;
  */
 class RSGroupAdminServiceImpl extends RSGroupAdminProtos.RSGroupAdminService {
 
+  private static final Logger LOG = LoggerFactory.getLogger(RSGroupAdminServiceImpl.class);
+
   private MasterServices master;
 
   private RSGroupAdminServer groupAdminServer;
@@ -107,12 +121,17 @@ class RSGroupAdminServiceImpl extends RSGroupAdminProtos.RSGroupAdminService {
     return userProvider.getCurrent();
   }
 
+  // for backward compatible
+  private RSGroupInfo fillTables(RSGroupInfo rsGroupInfo) throws IOException {
+    return RSGroupUtil.fillTables(rsGroupInfo, master.getTableDescriptors().getAll().values());
+  }
+
   @Override
   public void getRSGroupInfo(RpcController controller, GetRSGroupInfoRequest request,
       RpcCallback<GetRSGroupInfoResponse> done) {
     GetRSGroupInfoResponse.Builder builder = GetRSGroupInfoResponse.newBuilder();
     String groupName = request.getRSGroupName();
-    RSGroupAdminEndpoint.LOG.info(
+    LOG.info(
       master.getClientIdAuditPrefix() + " initiates rsgroup info retrieval, group=" + groupName);
     try {
       if (master.getMasterCoprocessorHost() != null) {
@@ -121,7 +140,7 @@ class RSGroupAdminServiceImpl extends RSGroupAdminProtos.RSGroupAdminService {
       checkPermission("getRSGroupInfo");
       RSGroupInfo rsGroupInfo = groupAdminServer.getRSGroupInfo(groupName);
       if (rsGroupInfo != null) {
-        builder.setRSGroupInfo(ProtobufUtil.toProtoGroupInfo(rsGroupInfo));
+        builder.setRSGroupInfo(ProtobufUtil.toProtoGroupInfo(fillTables(rsGroupInfo)));
       }
       if (master.getMasterCoprocessorHost() != null) {
         master.getMasterCoprocessorHost().postGetRSGroupInfo(groupName);
@@ -137,17 +156,24 @@ class RSGroupAdminServiceImpl extends RSGroupAdminProtos.RSGroupAdminService {
       RpcCallback<GetRSGroupInfoOfTableResponse> done) {
     GetRSGroupInfoOfTableResponse.Builder builder = GetRSGroupInfoOfTableResponse.newBuilder();
     TableName tableName = ProtobufUtil.toTableName(request.getTableName());
-    RSGroupAdminEndpoint.LOG.info(
+    LOG.info(
       master.getClientIdAuditPrefix() + " initiates rsgroup info retrieval, table=" + tableName);
     try {
       if (master.getMasterCoprocessorHost() != null) {
         master.getMasterCoprocessorHost().preGetRSGroupInfoOfTable(tableName);
       }
       checkPermission("getRSGroupInfoOfTable");
-      RSGroupInfo RSGroupInfo = groupAdminServer.getRSGroupInfoOfTable(tableName);
-      if (RSGroupInfo != null) {
-        builder.setRSGroupInfo(ProtobufUtil.toProtoGroupInfo(RSGroupInfo));
+      Optional<RSGroupInfo> optGroup =
+          RSGroupUtil.getRSGroupInfo(master, groupAdminServer, tableName);
+      if (optGroup.isPresent()) {
+        builder.setRSGroupInfo(ProtobufUtil.toProtoGroupInfo(fillTables(optGroup.get())));
+      } else {
+        if (master.getTableStateManager().isTablePresent(tableName)) {
+          RSGroupInfo rsGroupInfo = groupAdminServer.getRSGroupInfo(RSGroupInfo.DEFAULT_GROUP);
+          builder.setRSGroupInfo(ProtobufUtil.toProtoGroupInfo(fillTables(rsGroupInfo)));
+        }
       }
+
       if (master.getMasterCoprocessorHost() != null) {
         master.getMasterCoprocessorHost().postGetRSGroupInfoOfTable(tableName);
       }
@@ -165,8 +191,8 @@ class RSGroupAdminServiceImpl extends RSGroupAdminProtos.RSGroupAdminService {
     for (HBaseProtos.ServerName el : request.getServersList()) {
       hostPorts.add(Address.fromParts(el.getHostName(), el.getPort()));
     }
-    RSGroupAdminEndpoint.LOG.info(master.getClientIdAuditPrefix() + " move servers " + hostPorts +
-        " to rsgroup " + request.getTargetGroup());
+    LOG.info(master.getClientIdAuditPrefix() + " move servers " + hostPorts + " to rsgroup " +
+        request.getTargetGroup());
     try {
       if (master.getMasterCoprocessorHost() != null) {
         master.getMasterCoprocessorHost().preMoveServers(hostPorts, request.getTargetGroup());
@@ -182,6 +208,27 @@ class RSGroupAdminServiceImpl extends RSGroupAdminProtos.RSGroupAdminService {
     done.run(builder.build());
   }
 
+  private void moveTablesAndWait(Set<TableName> tables, String targetGroup) throws IOException {
+    List<Long> procIds = new ArrayList<Long>();
+    for (TableName tableName : tables) {
+      TableDescriptor oldTd = master.getTableDescriptors().get(tableName);
+      if (oldTd == null) {
+        continue;
+      }
+      TableDescriptor newTd =
+          TableDescriptorBuilder.newBuilder(oldTd).setRegionServerGroup(targetGroup).build();
+      procIds.add(master.modifyTable(tableName, newTd, HConstants.NO_NONCE, HConstants.NO_NONCE));
+    }
+    for (long procId : procIds) {
+      Procedure<?> proc = master.getMasterProcedureExecutor().getProcedure(procId);
+      if (proc == null) {
+        continue;
+      }
+      ProcedureSyncWait.waitForProcedureToCompleteIOE(master.getMasterProcedureExecutor(), proc,
+        Long.MAX_VALUE);
+    }
+  }
+
   @Override
   public void moveTables(RpcController controller, MoveTablesRequest request,
       RpcCallback<MoveTablesResponse> done) {
@@ -190,14 +237,14 @@ class RSGroupAdminServiceImpl extends RSGroupAdminProtos.RSGroupAdminService {
     for (HBaseProtos.TableName tableName : request.getTableNameList()) {
       tables.add(ProtobufUtil.toTableName(tableName));
     }
-    RSGroupAdminEndpoint.LOG.info(master.getClientIdAuditPrefix() + " move tables " + tables +
-        " to rsgroup " + request.getTargetGroup());
+    LOG.info(master.getClientIdAuditPrefix() + " move tables " + tables + " to rsgroup " +
+        request.getTargetGroup());
     try {
       if (master.getMasterCoprocessorHost() != null) {
         master.getMasterCoprocessorHost().preMoveTables(tables, request.getTargetGroup());
       }
       checkPermission("moveTables");
-      groupAdminServer.moveTables(tables, request.getTargetGroup());
+      moveTablesAndWait(tables, request.getTargetGroup());
       if (master.getMasterCoprocessorHost() != null) {
         master.getMasterCoprocessorHost().postMoveTables(tables, request.getTargetGroup());
       }
@@ -211,8 +258,7 @@ class RSGroupAdminServiceImpl extends RSGroupAdminProtos.RSGroupAdminService {
   public void addRSGroup(RpcController controller, AddRSGroupRequest request,
       RpcCallback<AddRSGroupResponse> done) {
     AddRSGroupResponse.Builder builder = AddRSGroupResponse.newBuilder();
-    RSGroupAdminEndpoint.LOG
-        .info(master.getClientIdAuditPrefix() + " add rsgroup " + request.getRSGroupName());
+    LOG.info(master.getClientIdAuditPrefix() + " add rsgroup " + request.getRSGroupName());
     try {
       if (master.getMasterCoprocessorHost() != null) {
         master.getMasterCoprocessorHost().preAddRSGroup(request.getRSGroupName());
@@ -232,8 +278,7 @@ class RSGroupAdminServiceImpl extends RSGroupAdminProtos.RSGroupAdminService {
   public void removeRSGroup(RpcController controller, RemoveRSGroupRequest request,
       RpcCallback<RemoveRSGroupResponse> done) {
     RemoveRSGroupResponse.Builder builder = RemoveRSGroupResponse.newBuilder();
-    RSGroupAdminEndpoint.LOG
-        .info(master.getClientIdAuditPrefix() + " remove rsgroup " + request.getRSGroupName());
+    LOG.info(master.getClientIdAuditPrefix() + " remove rsgroup " + request.getRSGroupName());
     try {
       if (master.getMasterCoprocessorHost() != null) {
         master.getMasterCoprocessorHost().preRemoveRSGroup(request.getRSGroupName());
@@ -253,7 +298,7 @@ class RSGroupAdminServiceImpl extends RSGroupAdminProtos.RSGroupAdminService {
   public void balanceRSGroup(RpcController controller, BalanceRSGroupRequest request,
       RpcCallback<BalanceRSGroupResponse> done) {
     BalanceRSGroupResponse.Builder builder = BalanceRSGroupResponse.newBuilder();
-    RSGroupAdminEndpoint.LOG.info(
+    LOG.info(
       master.getClientIdAuditPrefix() + " balance rsgroup, group=" + request.getRSGroupName());
     try {
       if (master.getMasterCoprocessorHost() != null) {
@@ -276,14 +321,28 @@ class RSGroupAdminServiceImpl extends RSGroupAdminProtos.RSGroupAdminService {
   public void listRSGroupInfos(RpcController controller, ListRSGroupInfosRequest request,
       RpcCallback<ListRSGroupInfosResponse> done) {
     ListRSGroupInfosResponse.Builder builder = ListRSGroupInfosResponse.newBuilder();
-    RSGroupAdminEndpoint.LOG.info(master.getClientIdAuditPrefix() + " list rsgroup");
+    LOG.info(master.getClientIdAuditPrefix() + " list rsgroup");
     try {
       if (master.getMasterCoprocessorHost() != null) {
         master.getMasterCoprocessorHost().preListRSGroups();
       }
       checkPermission("listRSGroup");
-      for (RSGroupInfo RSGroupInfo : groupAdminServer.listRSGroups()) {
-        builder.addRSGroupInfo(ProtobufUtil.toProtoGroupInfo(RSGroupInfo));
+      List<RSGroupInfo> rsGroupInfos = groupAdminServer.listRSGroups().stream()
+          .map(RSGroupInfo::new).collect(Collectors.toList());
+      Map<String, RSGroupInfo> name2Info = new HashMap<>();
+      for (RSGroupInfo rsGroupInfo : rsGroupInfos) {
+        name2Info.put(rsGroupInfo.getName(), rsGroupInfo);
+      }
+      for (TableDescriptor td : master.getTableDescriptors().getAll().values()) {
+        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();
@@ -300,8 +359,7 @@ class RSGroupAdminServiceImpl extends RSGroupAdminProtos.RSGroupAdminService {
     GetRSGroupInfoOfServerResponse.Builder builder = GetRSGroupInfoOfServerResponse.newBuilder();
     Address hp =
         Address.fromParts(request.getServer().getHostName(), request.getServer().getPort());
-    RSGroupAdminEndpoint.LOG
-        .info(master.getClientIdAuditPrefix() + " initiates rsgroup info retrieval, server=" + hp);
+    LOG.info(master.getClientIdAuditPrefix() + " initiates rsgroup info retrieval, server=" + hp);
     try {
       if (master.getMasterCoprocessorHost() != null) {
         master.getMasterCoprocessorHost().preGetRSGroupInfoOfServer(hp);
@@ -309,7 +367,7 @@ class RSGroupAdminServiceImpl extends RSGroupAdminProtos.RSGroupAdminService {
       checkPermission("getRSGroupInfoOfServer");
       RSGroupInfo info = groupAdminServer.getRSGroupOfServer(hp);
       if (info != null) {
-        builder.setRSGroupInfo(ProtobufUtil.toProtoGroupInfo(info));
+        builder.setRSGroupInfo(ProtobufUtil.toProtoGroupInfo(fillTables(info)));
       }
       if (master.getMasterCoprocessorHost() != null) {
         master.getMasterCoprocessorHost().postGetRSGroupInfoOfServer(hp);
@@ -332,15 +390,16 @@ class RSGroupAdminServiceImpl extends RSGroupAdminProtos.RSGroupAdminService {
     for (HBaseProtos.TableName tableName : request.getTableNameList()) {
       tables.add(ProtobufUtil.toTableName(tableName));
     }
-    RSGroupAdminEndpoint.LOG.info(master.getClientIdAuditPrefix() + " move servers " + hostPorts +
-        " and tables " + tables + " to rsgroup" + request.getTargetGroup());
+    LOG.info(master.getClientIdAuditPrefix() + " move servers " + hostPorts + " and tables " +
+        tables + " to rsgroup" + request.getTargetGroup());
     try {
       if (master.getMasterCoprocessorHost() != null) {
         master.getMasterCoprocessorHost().preMoveServersAndTables(hostPorts, tables,
           request.getTargetGroup());
       }
       checkPermission("moveServersAndTables");
-      groupAdminServer.moveServersAndTables(hostPorts, tables, request.getTargetGroup());
+      groupAdminServer.moveServers(hostPorts, request.getTargetGroup());
+      moveTablesAndWait(tables, request.getTargetGroup());
       if (master.getMasterCoprocessorHost() != null) {
         master.getMasterCoprocessorHost().postMoveServersAndTables(hostPorts, tables,
           request.getTargetGroup());
@@ -359,7 +418,7 @@ class RSGroupAdminServiceImpl extends RSGroupAdminProtos.RSGroupAdminService {
     for (HBaseProtos.ServerName el : request.getServersList()) {
       servers.add(Address.fromParts(el.getHostName(), el.getPort()));
     }
-    RSGroupAdminEndpoint.LOG.info(
+    LOG.info(
       master.getClientIdAuditPrefix() + " remove decommissioned servers from rsgroup: " + servers);
     try {
       if (master.getMasterCoprocessorHost() != null) {
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupBasedLoadBalancer.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupBasedLoadBalancer.java
index be76805..0f943d0 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupBasedLoadBalancer.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupBasedLoadBalancer.java
@@ -27,7 +27,6 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.TreeMap;
-
 import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.hbase.ClusterMetrics;
 import org.apache.hadoop.hbase.HBaseIOException;
@@ -111,13 +110,13 @@ public class RSGroupBasedLoadBalancer implements RSGroupableBalancer {
 
   @Override
   public List<RegionPlan> balanceCluster(TableName tableName, Map<ServerName, List<RegionInfo>>
-      clusterState) throws HBaseIOException {
+      clusterState) throws IOException {
     return balanceCluster(clusterState);
   }
 
   @Override
   public List<RegionPlan> balanceCluster(Map<ServerName, List<RegionInfo>> clusterState)
-      throws HBaseIOException {
+      throws IOException {
     if (!isOnline()) {
       throw new ConstraintException(
           RSGroupInfoManager.class.getSimpleName() + " is not online, unable to perform balance");
@@ -168,8 +167,8 @@ public class RSGroupBasedLoadBalancer implements RSGroupableBalancer {
   }
 
   @Override
-  public Map<ServerName, List<RegionInfo>> roundRobinAssignment(List<RegionInfo> regions,
-    List<ServerName> servers) throws HBaseIOException {
+  public Map<ServerName, List<RegionInfo>> roundRobinAssignment(
+      List<RegionInfo> regions, List<ServerName> servers) throws IOException {
     Map<ServerName, List<RegionInfo>> assignments = Maps.newHashMap();
     ListMultimap<String, RegionInfo> regionMap = ArrayListMultimap.create();
     ListMultimap<String, ServerName> serverMap = ArrayListMultimap.create();
@@ -198,12 +197,11 @@ public class RSGroupBasedLoadBalancer implements RSGroupableBalancer {
     try {
       Map<ServerName, List<RegionInfo>> assignments = new TreeMap<>();
       ListMultimap<String, RegionInfo> groupToRegion = ArrayListMultimap.create();
+      RSGroupInfo defaultInfo = rsGroupInfoManager.getRSGroup(RSGroupInfo.DEFAULT_GROUP);
       for (RegionInfo region : regions.keySet()) {
-        String groupName = rsGroupInfoManager.getRSGroupOfTable(region.getTable());
-        if (groupName == null) {
-          LOG.debug("Group not found for table " + region.getTable() + ", using default");
-          groupName = RSGroupInfo.DEFAULT_GROUP;
-        }
+        String groupName =
+          RSGroupUtil.getRSGroupInfo(masterServices, rsGroupInfoManager, region.getTable())
+              .orElse(defaultInfo).getName();
         groupToRegion.put(groupName, region);
       }
       for (String key : groupToRegion.keySet()) {
@@ -234,7 +232,7 @@ public class RSGroupBasedLoadBalancer implements RSGroupableBalancer {
 
   @Override
   public ServerName randomAssignment(RegionInfo region,
-      List<ServerName> servers) throws HBaseIOException {
+      List<ServerName> servers) throws IOException {
     ListMultimap<String,RegionInfo> regionMap = LinkedListMultimap.create();
     ListMultimap<String,ServerName> serverMap = LinkedListMultimap.create();
     generateGroupMaps(Lists.newArrayList(region), servers, regionMap, serverMap);
@@ -246,12 +244,11 @@ public class RSGroupBasedLoadBalancer implements RSGroupableBalancer {
     ListMultimap<String, RegionInfo> regionMap, ListMultimap<String, ServerName> serverMap)
     throws HBaseIOException {
     try {
+      RSGroupInfo defaultInfo = rsGroupInfoManager.getRSGroup(RSGroupInfo.DEFAULT_GROUP);
       for (RegionInfo region : regions) {
-        String groupName = rsGroupInfoManager.getRSGroupOfTable(region.getTable());
-        if (groupName == null) {
-          LOG.debug("Group not found for table " + region.getTable() + ", using default");
-          groupName = RSGroupInfo.DEFAULT_GROUP;
-        }
+        String groupName =
+            RSGroupUtil.getRSGroupInfo(masterServices, rsGroupInfoManager, region.getTable())
+                .orElse(defaultInfo).getName();
         regionMap.put(groupName, region);
       }
       for (String groupKey : regionMap.keySet()) {
@@ -299,11 +296,11 @@ public class RSGroupBasedLoadBalancer implements RSGroupableBalancer {
   }
 
   private Pair<Map<ServerName, List<RegionInfo>>, List<RegionPlan>> correctAssignments(
-      Map<ServerName, List<RegionInfo>> existingAssignments) throws HBaseIOException{
+      Map<ServerName, List<RegionInfo>> existingAssignments) throws IOException {
     // To return
     Map<ServerName, List<RegionInfo>> correctAssignments = new TreeMap<>();
     List<RegionPlan> regionPlansForMisplacedRegions = new ArrayList<>();
-
+    RSGroupInfo defaultInfo = rsGroupInfoManager.getRSGroup(RSGroupInfo.DEFAULT_GROUP);
     for (Map.Entry<ServerName, List<RegionInfo>> assignments : existingAssignments.entrySet()){
       ServerName currentHostServer = assignments.getKey();
       correctAssignments.put(currentHostServer, new LinkedList<>());
@@ -311,15 +308,11 @@ public class RSGroupBasedLoadBalancer implements RSGroupableBalancer {
       for (RegionInfo region : regions) {
         RSGroupInfo targetRSGInfo = null;
         try {
-          String groupName = rsGroupInfoManager.getRSGroupOfTable(region.getTable());
-          if (groupName == null) {
-            LOG.debug("Group not found for table " + region.getTable() + ", using default");
-            groupName = RSGroupInfo.DEFAULT_GROUP;
-          }
-          targetRSGInfo = rsGroupInfoManager.getRSGroup(groupName);
+          targetRSGInfo =
+              RSGroupUtil.getRSGroupInfo(masterServices, rsGroupInfoManager, region.getTable())
+                  .orElse(defaultInfo);
         } catch (IOException exp) {
-          LOG.debug("RSGroup information null for region of table " + region.getTable(),
-              exp);
+          LOG.debug("RSGroup information null for region of table " + region.getTable(), exp);
         }
         if (targetRSGInfo == null ||
             !targetRSGInfo.containsServer(currentHostServer.getAddress())) { // region is mis-placed
@@ -336,7 +329,7 @@ public class RSGroupBasedLoadBalancer implements RSGroupableBalancer {
   }
 
   @Override
-  public void initialize() throws HBaseIOException {
+  public void initialize() throws IOException {
     try {
       if (rsGroupInfoManager == null) {
         List<RSGroupAdminEndpoint> cps =
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 70aeabf..28f7c1f 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
@@ -20,7 +20,6 @@ package org.apache.hadoop.hbase.rsgroup;
 import java.io.IOException;
 import java.util.List;
 import java.util.Set;
-import org.apache.hadoop.hbase.TableName;
 import org.apache.hadoop.hbase.net.Address;
 import org.apache.yetus.audience.InterfaceAudience;
 
@@ -64,18 +63,6 @@ public interface RSGroupInfoManager {
   RSGroupInfo getRSGroup(String groupName) throws IOException;
 
   /**
-   * Get the group membership of a table
-   */
-  String getRSGroupOfTable(TableName tableName) throws IOException;
-
-  /**
-   * Set the group membership of a set of tables
-   * @param tableNames set of tables to move
-   * @param groupName name of group of tables to move to
-   */
-  void moveTables(Set<TableName> tableNames, String groupName) throws IOException;
-
-  /**
    * List the existing {@code RSGroupInfo}s.
    */
   List<RSGroupInfo> listRSGroups() throws IOException;
@@ -92,16 +79,6 @@ public interface RSGroupInfoManager {
   boolean isOnline();
 
   /**
-   * Move servers and tables to a new group.
-   * @param servers list of servers, must be part of the same group
-   * @param tables set of tables to move
-   * @param srcGroup groupName being moved from
-   * @param dstGroup groupName being moved to
-   */
-  void moveServersAndTables(Set<Address> servers, Set<TableName> tables, String srcGroup,
-      String dstGroup) throws IOException;
-
-  /**
    * Remove decommissioned servers from rsgroup
    * @param servers set of servers to remove
    */
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 8aa7520..37f3ce6 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
@@ -23,10 +23,8 @@ import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
-import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
-import java.util.NavigableSet;
 import java.util.OptionalLong;
 import java.util.Set;
 import java.util.SortedSet;
@@ -143,7 +141,6 @@ final class RSGroupInfoManagerImpl implements RSGroupInfoManager {
   // There two Maps are immutable and wholesale replaced on each modification
   // so are safe to access concurrently. See class comment.
   private volatile Map<String, RSGroupInfo> rsGroupMap = Collections.emptyMap();
-  private volatile Map<TableName, String> tableMap = Collections.emptyMap();
 
   private final MasterServices masterServices;
   private final AsyncClusterConnection conn;
@@ -261,44 +258,6 @@ final class RSGroupInfoManagerImpl implements RSGroupInfoManager {
   }
 
   @Override
-  public String getRSGroupOfTable(TableName tableName) {
-    return tableMap.get(tableName);
-  }
-
-  @Override
-  public synchronized void moveTables(Set<TableName> tableNames, String groupName)
-      throws IOException {
-    // Check if rsGroupMap contains the destination rsgroup
-    if (groupName != null && !rsGroupMap.containsKey(groupName)) {
-      throw new DoNotRetryIOException("Group " + groupName + " does not exist");
-    }
-
-    // Make a copy of rsGroupMap to update
-    Map<String, RSGroupInfo> newGroupMap = Maps.newHashMap(rsGroupMap);
-
-    // Remove tables from their original rsgroups
-    // and update the copy of rsGroupMap
-    for (TableName tableName : tableNames) {
-      if (tableMap.containsKey(tableName)) {
-        RSGroupInfo src = new RSGroupInfo(newGroupMap.get(tableMap.get(tableName)));
-        src.removeTable(tableName);
-        newGroupMap.put(src.getName(), src);
-      }
-    }
-
-    // Add tables to the destination rsgroup
-    // and update the copy of rsGroupMap
-    if (groupName != null) {
-      RSGroupInfo dstGroup = new RSGroupInfo(newGroupMap.get(groupName));
-      dstGroup.addAllTables(tableNames);
-      newGroupMap.put(dstGroup.getName(), dstGroup);
-    }
-
-    // Flush according to the updated copy of rsGroupMap
-    flushConfig(newGroupMap);
-  }
-
-  @Override
   public synchronized void removeRSGroup(String groupName) throws IOException {
     if (!rsGroupMap.containsKey(groupName) || groupName.equals(RSGroupInfo.DEFAULT_GROUP)) {
       throw new DoNotRetryIOException(
@@ -311,7 +270,7 @@ final class RSGroupInfoManagerImpl implements RSGroupInfoManager {
 
   @Override
   public List<RSGroupInfo> listRSGroups() {
-    return Lists.newLinkedList(rsGroupMap.values());
+    return Lists.newArrayList(rsGroupMap.values());
   }
 
   @Override
@@ -320,31 +279,6 @@ final class RSGroupInfoManagerImpl implements RSGroupInfoManager {
   }
 
   @Override
-  public void moveServersAndTables(Set<Address> servers, Set<TableName> tables, String srcGroup,
-      String dstGroup) throws IOException {
-    // get server's group
-    RSGroupInfo srcGroupInfo = getRSGroupInfo(srcGroup);
-    RSGroupInfo dstGroupInfo = getRSGroupInfo(dstGroup);
-
-    // move servers
-    for (Address el : servers) {
-      srcGroupInfo.removeServer(el);
-      dstGroupInfo.addServer(el);
-    }
-    // move tables
-    for (TableName tableName : tables) {
-      srcGroupInfo.removeTable(tableName);
-      dstGroupInfo.addTable(tableName);
-    }
-
-    // flush changed groupinfo
-    Map<String, RSGroupInfo> newGroupMap = Maps.newHashMap(rsGroupMap);
-    newGroupMap.put(srcGroupInfo.getName(), srcGroupInfo);
-    newGroupMap.put(dstGroupInfo.getName(), dstGroupInfo);
-    flushConfig(newGroupMap);
-  }
-
-  @Override
   public synchronized void removeServers(Set<Address> servers) throws IOException {
     Map<String, RSGroupInfo> rsGroupInfos = new HashMap<String, RSGroupInfo>();
     for (Address el : servers) {
@@ -425,7 +359,7 @@ final class RSGroupInfoManagerImpl implements RSGroupInfoManager {
    * startup of the manager.
    */
   private synchronized void refresh(boolean forceOnline) throws IOException {
-    List<RSGroupInfo> groupList = new LinkedList<>();
+    List<RSGroupInfo> groupList = new ArrayList<>();
 
     // Overwrite anything read from zk, group table is source of truth
     // if online read from GROUP table
@@ -437,37 +371,20 @@ final class RSGroupInfoManagerImpl implements RSGroupInfoManager {
       groupList.addAll(retrieveGroupListFromZookeeper());
     }
 
-    // refresh default group, prune
-    NavigableSet<TableName> orphanTables = new TreeSet<>();
-    for (String entry : masterServices.getTableDescriptors().getAll().keySet()) {
-      orphanTables.add(TableName.valueOf(entry));
-    }
-    for (RSGroupInfo group : groupList) {
-      if (!group.getName().equals(RSGroupInfo.DEFAULT_GROUP)) {
-        orphanTables.removeAll(group.getTables());
-      }
-    }
-
     // This is added to the last of the list so it overwrites the 'default' rsgroup loaded
     // from region group table or zk
-    groupList.add(new RSGroupInfo(RSGroupInfo.DEFAULT_GROUP, getDefaultServers(), orphanTables));
+    groupList.add(new RSGroupInfo(RSGroupInfo.DEFAULT_GROUP, getDefaultServers()));
 
     // populate the data
     HashMap<String, RSGroupInfo> newGroupMap = Maps.newHashMap();
-    HashMap<TableName, String> newTableMap = Maps.newHashMap();
     for (RSGroupInfo group : groupList) {
       newGroupMap.put(group.getName(), group);
-      for (TableName table : group.getTables()) {
-        newTableMap.put(table, group.getName());
-      }
     }
-    resetRSGroupAndTableMaps(newGroupMap, newTableMap);
+    resetRSGroupMap(newGroupMap);
     updateCacheOfRSGroups(rsGroupMap.keySet());
   }
 
-  private synchronized Map<TableName, String> flushConfigTable(Map<String, RSGroupInfo> groupMap)
-      throws IOException {
-    Map<TableName, String> newTableMap = Maps.newHashMap();
+  private void flushConfigTable(Map<String, RSGroupInfo> groupMap) throws IOException {
     List<Mutation> mutations = Lists.newArrayList();
 
     // populate deletes
@@ -484,15 +401,11 @@ final class RSGroupInfoManagerImpl implements RSGroupInfoManager {
       Put p = new Put(Bytes.toBytes(RSGroupInfo.getName()));
       p.addColumn(META_FAMILY_BYTES, META_QUALIFIER_BYTES, proto.toByteArray());
       mutations.add(p);
-      for (TableName entry : RSGroupInfo.getTables()) {
-        newTableMap.put(entry, RSGroupInfo.getName());
-      }
     }
 
     if (mutations.size() > 0) {
       multiMutate(mutations);
     }
-    return newTableMap;
   }
 
   private synchronized void flushConfig() throws IOException {
@@ -500,8 +413,6 @@ final class RSGroupInfoManagerImpl implements RSGroupInfoManager {
   }
 
   private synchronized void flushConfig(Map<String, RSGroupInfo> newGroupMap) throws IOException {
-    Map<TableName, String> newTableMap;
-
     // For offline mode persistence is still unavailable
     // We're refreshing in-memory state but only for servers in default group
     if (!isOnline()) {
@@ -516,7 +427,7 @@ final class RSGroupInfoManagerImpl implements RSGroupInfoManager {
       RSGroupInfo newDefaultGroup = newGroupMap.remove(RSGroupInfo.DEFAULT_GROUP);
       if (!oldGroupMap.equals(newGroupMap) /* compare both tables and servers in other groups */ ||
           !oldDefaultGroup.getTables().equals(newDefaultGroup.getTables())
-          /* compare tables in default group */) {
+      /* compare tables in default group */) {
         throw new IOException("Only servers in default group can be updated during offline mode");
       }
 
@@ -533,11 +444,11 @@ final class RSGroupInfoManagerImpl implements RSGroupInfoManager {
       return;
     }
 
-    /* For online mode, persist to Zookeeper */
-    newTableMap = flushConfigTable(newGroupMap);
+    /* For online mode, persist to hbase:rsgroup and Zookeeper */
+    flushConfigTable(newGroupMap);
 
     // Make changes visible after having been persisted to the source of truth
-    resetRSGroupAndTableMaps(newGroupMap, newTableMap);
+    resetRSGroupMap(newGroupMap);
 
     try {
       String groupBasePath =
@@ -575,11 +486,9 @@ final class RSGroupInfoManagerImpl implements RSGroupInfoManager {
   /**
    * Make changes visible. Caller must be synchronized on 'this'.
    */
-  private void resetRSGroupAndTableMaps(Map<String, RSGroupInfo> newRSGroupMap,
-      Map<TableName, String> newTableMap) {
+  private void resetRSGroupMap(Map<String, RSGroupInfo> newRSGroupMap) {
     // Make maps Immutable.
     this.rsGroupMap = Collections.unmodifiableMap(newRSGroupMap);
-    this.tableMap = Collections.unmodifiableMap(newTableMap);
   }
 
   /**
@@ -597,7 +506,7 @@ final class RSGroupInfoManagerImpl implements RSGroupInfoManager {
       return masterServices.getServerManager().getOnlineServersList();
     }
     LOG.debug("Reading online RS from zookeeper");
-    List<ServerName> servers = new LinkedList<>();
+    List<ServerName> servers = new ArrayList<>();
     try {
       for (String el : ZKUtil.listChildrenNoWatch(watcher, watcher.getZNodePaths().rsZNode)) {
         servers.add(ServerName.parseServerName(el));
@@ -633,7 +542,7 @@ final class RSGroupInfoManagerImpl implements RSGroupInfoManager {
   // the rsGroupMap then writing it out.
   private synchronized void updateDefaultServers(SortedSet<Address> servers) throws IOException {
     RSGroupInfo info = rsGroupMap.get(RSGroupInfo.DEFAULT_GROUP);
-    RSGroupInfo newInfo = new RSGroupInfo(info.getName(), servers, info.getTables());
+    RSGroupInfo newInfo = new RSGroupInfo(info.getName(), servers);
     HashMap<String, RSGroupInfo> newGroupMap = Maps.newHashMap(rsGroupMap);
     newGroupMap.put(newInfo.getName(), newInfo);
     flushConfig(newGroupMap);
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
new file mode 100644
index 0000000..a08d236
--- /dev/null
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupUtil.java
@@ -0,0 +1,113 @@
+/**
+ * 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.Collection;
+import java.util.Optional;
+import java.util.function.Predicate;
+import org.apache.hadoop.hbase.NamespaceDescriptor;
+import org.apache.hadoop.hbase.TableName;
+import org.apache.hadoop.hbase.client.TableDescriptor;
+import org.apache.hadoop.hbase.master.ClusterSchema;
+import org.apache.hadoop.hbase.master.MasterServices;
+import org.apache.yetus.audience.InterfaceAudience;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Helper class for RSGroup implementation
+ */
+@InterfaceAudience.Private
+final class RSGroupUtil {
+
+  private static final Logger LOG = LoggerFactory.getLogger(RSGroupUtil.class);
+
+  private RSGroupUtil() {
+  }
+
+  @FunctionalInterface
+  private interface GetRSGroup {
+    RSGroupInfo get(String groupName) throws IOException;
+  }
+
+  private static Optional<RSGroupInfo> getRSGroupInfo(MasterServices master, GetRSGroup getter,
+      TableName tableName) throws IOException {
+    TableDescriptor td = master.getTableDescriptors().get(tableName);
+    if (td == null) {
+      return Optional.empty();
+    }
+    Optional<String> optGroupNameOfTable = td.getRegionServerGroup();
+    if (optGroupNameOfTable.isPresent()) {
+      RSGroupInfo group = getter.get(optGroupNameOfTable.get());
+      if (group != null) {
+        return Optional.of(group);
+      }
+    }
+    ClusterSchema clusterSchema = master.getClusterSchema();
+    if (clusterSchema == null) {
+      if (TableName.isMetaTableName(tableName)) {
+        LOG.info("Can not get the namespace rs group config for meta table, since the" +
+            " meta table is not online yet, will use default group to assign meta first");
+      } else {
+        LOG.warn("ClusterSchema is null, can only use default rsgroup, should not happen?");
+      }
+      return Optional.empty();
+    }
+    NamespaceDescriptor nd = clusterSchema.getNamespace(tableName.getNamespaceAsString());
+    String groupNameOfNs = nd.getConfigurationValue(RSGroupInfo.NAMESPACE_DESC_PROP_GROUP);
+    if (groupNameOfNs == null) {
+      return Optional.empty();
+    }
+    return Optional.ofNullable(getter.get(groupNameOfNs));
+  }
+
+  /**
+   * 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 {
+    return getRSGroupInfo(master, manager::getRSGroup, tableName);
+  }
+
+  /**
+   * 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, RSGroupAdmin admin,
+      TableName tableName) throws IOException {
+    return getRSGroupInfo(master, admin::getRSGroupInfo, tableName);
+  }
+
+  /**
+   * Fill the tables field for {@link RSGroupInfo}, for backward compatibility.
+   */
+  @SuppressWarnings("deprecation")
+  static RSGroupInfo fillTables(RSGroupInfo rsGroupInfo, Collection<TableDescriptor> tds) {
+    RSGroupInfo newRsGroupInfo = new RSGroupInfo(rsGroupInfo);
+    Predicate<TableDescriptor> filter;
+    if (rsGroupInfo.getName().equals(RSGroupInfo.DEFAULT_GROUP)) {
+      filter = td -> {
+        Optional<String> optGroupName = td.getRegionServerGroup();
+        return !optGroupName.isPresent() || optGroupName.get().equals(RSGroupInfo.DEFAULT_GROUP);
+      };
+    } else {
+      filter = td -> {
+        Optional<String> optGroupName = td.getRegionServerGroup();
+        return optGroupName.isPresent() && optGroupName.get().equals(newRsGroupInfo.getName());
+      };
+    }
+    tds.stream().filter(filter).map(TableDescriptor::getTableName)
+        .forEach(newRsGroupInfo::addTable);
+    return newRsGroupInfo;
+  }
+}
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/TestRegionPlacement2.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/TestRegionPlacement2.java
index 6dc3711..47337f9 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/TestRegionPlacement2.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/TestRegionPlacement2.java
@@ -19,6 +19,7 @@ package org.apache.hadoop.hbase.master;
 
 import static org.junit.Assert.assertTrue;
 
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
@@ -26,7 +27,6 @@ import java.util.Map;
 import java.util.Set;
 import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.hbase.HBaseClassTestRule;
-import org.apache.hadoop.hbase.HBaseIOException;
 import org.apache.hadoop.hbase.HBaseTestingUtility;
 import org.apache.hadoop.hbase.HConstants;
 import org.apache.hadoop.hbase.ServerName;
@@ -82,7 +82,7 @@ public class TestRegionPlacement2 {
   }
 
   @Test
-  public void testFavoredNodesPresentForRoundRobinAssignment() throws HBaseIOException {
+  public void testFavoredNodesPresentForRoundRobinAssignment() throws IOException {
     LoadBalancer balancer = LoadBalancerFactory.getLoadBalancer(TEST_UTIL.getConfiguration());
     balancer.setMasterServices(TEST_UTIL.getMiniHBaseCluster().getMaster());
     balancer.initialize();
@@ -143,7 +143,7 @@ public class TestRegionPlacement2 {
   }
 
   @Test
-  public void testFavoredNodesPresentForRandomAssignment() throws HBaseIOException {
+  public void testFavoredNodesPresentForRandomAssignment() throws IOException {
     LoadBalancer balancer = LoadBalancerFactory.getLoadBalancer(TEST_UTIL.getConfiguration());
     balancer.setMasterServices(TEST_UTIL.getMiniHBaseCluster().getMaster());
     balancer.initialize();
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/balancer/RSGroupableBalancerTestBase.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/balancer/RSGroupableBalancerTestBase.java
index 570bb3a..4c00bcf 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/balancer/RSGroupableBalancerTestBase.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/balancer/RSGroupableBalancerTestBase.java
@@ -28,6 +28,7 @@ import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 import java.util.Set;
 import java.util.TreeMap;
 import java.util.TreeSet;
@@ -60,17 +61,13 @@ import org.apache.hbase.thirdparty.com.google.common.collect.Lists;
 public class RSGroupableBalancerTestBase {
 
   static SecureRandom rand = new SecureRandom();
-  static String[] groups = new String[] {RSGroupInfo.DEFAULT_GROUP, "dg2", "dg3", "dg4"};
+  static String[] groups = new String[] { RSGroupInfo.DEFAULT_GROUP, "dg2", "dg3", "dg4" };
   static TableName table0 = TableName.valueOf("dt0");
-  static TableName[] tables =
-      new TableName[] { TableName.valueOf("dt1"),
-          TableName.valueOf("dt2"),
-          TableName.valueOf("dt3"),
-          TableName.valueOf("dt4")};
+  static TableName[] tables = new TableName[] { TableName.valueOf("dt1"), TableName.valueOf("dt2"),
+      TableName.valueOf("dt3"), TableName.valueOf("dt4") };
   static List<ServerName> servers;
   static Map<String, RSGroupInfo> groupMap;
-  static Map<TableName, String> tableMap = new HashMap<>();
-  static List<TableDescriptor> tableDescs;
+  static Map<TableName, TableDescriptor> tableDescs;
   int[] regionAssignment = new int[] { 2, 5, 7, 10, 4, 3, 1 };
   static int regionId = 0;
 
@@ -113,20 +110,19 @@ public class RSGroupableBalancerTestBase {
   /**
    * All regions have an assignment.
    */
-  protected void assertImmediateAssignment(List<RegionInfo> regions,
-                                         List<ServerName> servers,
-                                         Map<RegionInfo, ServerName> assignments)
-      throws IOException {
+  protected void assertImmediateAssignment(List<RegionInfo> regions, List<ServerName> servers,
+      Map<RegionInfo, ServerName> assignments) throws IOException {
     for (RegionInfo region : regions) {
       assertTrue(assignments.containsKey(region));
       ServerName server = assignments.get(region);
       TableName tableName = region.getTable();
 
-      String groupName = getMockedGroupInfoManager().getRSGroupOfTable(tableName);
+      String groupName =
+          tableDescs.get(tableName).getRegionServerGroup().orElse(RSGroupInfo.DEFAULT_GROUP);
       assertTrue(StringUtils.isNotEmpty(groupName));
       RSGroupInfo gInfo = getMockedGroupInfoManager().getRSGroup(groupName);
       assertTrue("Region is not correctly assigned to group servers.",
-          gInfo.containsServer(server.getAddress()));
+        gInfo.containsServer(server.getAddress()));
     }
   }
 
@@ -169,16 +165,13 @@ public class RSGroupableBalancerTestBase {
         ServerName oldAssignedServer = existing.get(r);
         TableName tableName = r.getTable();
         String groupName =
-            getMockedGroupInfoManager().getRSGroupOfTable(tableName);
+            tableDescs.get(tableName).getRegionServerGroup().orElse(RSGroupInfo.DEFAULT_GROUP);
         assertTrue(StringUtils.isNotEmpty(groupName));
-        RSGroupInfo gInfo = getMockedGroupInfoManager().getRSGroup(
-            groupName);
-        assertTrue(
-            "Region is not correctly assigned to group servers.",
-            gInfo.containsServer(currentServer.getAddress()));
-        if (oldAssignedServer != null
-            && onlineHostNames.contains(oldAssignedServer
-            .getHostname())) {
+        RSGroupInfo gInfo = getMockedGroupInfoManager().getRSGroup(groupName);
+        assertTrue("Region is not correctly assigned to group servers.",
+          gInfo.containsServer(currentServer.getAddress()));
+        if (oldAssignedServer != null &&
+            onlineHostNames.contains(oldAssignedServer.getHostname())) {
           // this region was previously assigned somewhere, and that
           // host is still around, then the host must have been is a
           // different group.
@@ -358,13 +351,12 @@ public class RSGroupableBalancerTestBase {
 
   /**
    * Construct group info, with each group having at least one server.
-   *
    * @param servers the servers
    * @param groups the groups
    * @return the map
    */
-  protected static Map<String, RSGroupInfo> constructGroupInfo(
-      List<ServerName> servers, String[] groups) {
+  protected static Map<String, RSGroupInfo> constructGroupInfo(List<ServerName> servers,
+      String[] groups) {
     assertTrue(servers != null);
     assertTrue(servers.size() >= groups.length);
     int index = 0;
@@ -377,8 +369,7 @@ public class RSGroupableBalancerTestBase {
     }
     while (index < servers.size()) {
       int grpIndex = rand.nextInt(groups.length);
-      groupMap.get(groups[grpIndex]).addServer(
-          servers.get(index).getAddress());
+      groupMap.get(groups[grpIndex]).addServer(servers.get(index).getAddress());
       index++;
     }
     return groupMap;
@@ -389,29 +380,28 @@ public class RSGroupableBalancerTestBase {
    * @param hasBogusTable there is a table that does not determine the group
    * @return the list of table descriptors
    */
-  protected static List<TableDescriptor> constructTableDesc(boolean hasBogusTable) {
-    List<TableDescriptor> tds = Lists.newArrayList();
+  protected static Map<TableName, TableDescriptor> constructTableDesc(boolean hasBogusTable) {
+    Map<TableName, TableDescriptor> tds = new HashMap<>();
     int index = rand.nextInt(groups.length);
     for (int i = 0; i < tables.length; i++) {
-      TableDescriptor htd = TableDescriptorBuilder.newBuilder(tables[i]).build();
       int grpIndex = (i + index) % groups.length;
       String groupName = groups[grpIndex];
-      tableMap.put(tables[i], groupName);
-      tds.add(htd);
+      TableDescriptor htd =
+          TableDescriptorBuilder.newBuilder(tables[i]).setRegionServerGroup(groupName).build();
+      tds.put(htd.getTableName(), htd);
     }
     if (hasBogusTable) {
-      tableMap.put(table0, "");
-      tds.add(TableDescriptorBuilder.newBuilder(table0).build());
+      tds.put(table0, TableDescriptorBuilder.newBuilder(table0).setRegionServerGroup("").build());
     }
     return tds;
   }
 
   protected static MasterServices getMockedMaster() throws IOException {
     TableDescriptors tds = Mockito.mock(TableDescriptors.class);
-    Mockito.when(tds.get(tables[0])).thenReturn(tableDescs.get(0));
-    Mockito.when(tds.get(tables[1])).thenReturn(tableDescs.get(1));
-    Mockito.when(tds.get(tables[2])).thenReturn(tableDescs.get(2));
-    Mockito.when(tds.get(tables[3])).thenReturn(tableDescs.get(3));
+    Mockito.when(tds.get(tables[0])).thenReturn(tableDescs.get(tables[0]));
+    Mockito.when(tds.get(tables[1])).thenReturn(tableDescs.get(tables[1]));
+    Mockito.when(tds.get(tables[2])).thenReturn(tableDescs.get(tables[2]));
+    Mockito.when(tds.get(tables[3])).thenReturn(tableDescs.get(tables[3]));
     MasterServices services = Mockito.mock(HMaster.class);
     Mockito.when(services.getTableDescriptors()).thenReturn(tds);
     AssignmentManager am = Mockito.mock(AssignmentManager.class);
@@ -430,13 +420,6 @@ public class RSGroupableBalancerTestBase {
     Mockito.when(gm.listRSGroups()).thenReturn(
         Lists.newLinkedList(groupMap.values()));
     Mockito.when(gm.isOnline()).thenReturn(true);
-    Mockito.when(gm.getRSGroupOfTable(Mockito.any()))
-        .thenAnswer(new Answer<String>() {
-          @Override
-          public String answer(InvocationOnMock invocation) throws Throwable {
-            return tableMap.get(invocation.getArgument(0));
-          }
-        });
     return gm;
   }
 
@@ -444,15 +427,16 @@ public class RSGroupableBalancerTestBase {
     TableName tableName = null;
     RSGroupInfoManager gm = getMockedGroupInfoManager();
     RSGroupInfo groupOfServer = null;
-    for(RSGroupInfo gInfo : gm.listRSGroups()){
-      if(gInfo.containsServer(sn.getAddress())){
+    for (RSGroupInfo gInfo : gm.listRSGroups()) {
+      if (gInfo.containsServer(sn.getAddress())) {
         groupOfServer = gInfo;
         break;
       }
     }
 
-    for(TableDescriptor desc : tableDescs){
-      if(gm.getRSGroupOfTable(desc.getTableName()).endsWith(groupOfServer.getName())){
+    for (TableDescriptor desc : tableDescs.values()) {
+      Optional<String> optGroupName = desc.getRegionServerGroup();
+      if (optGroupName.isPresent() && optGroupName.get().endsWith(groupOfServer.getName())) {
         tableName = desc.getTableName();
       }
     }
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/balancer/TestRSGroupBasedLoadBalancer.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/balancer/TestRSGroupBasedLoadBalancer.java
index 4b88201..2345a39 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/balancer/TestRSGroupBasedLoadBalancer.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/balancer/TestRSGroupBasedLoadBalancer.java
@@ -96,33 +96,30 @@ public class TestRSGroupBasedLoadBalancer extends RSGroupableBalancerTestBase {
 
   /**
    * Tests the bulk assignment used during cluster startup.
-   *
-   * Round-robin. Should yield a balanced cluster so same invariant as the
-   * load balancer holds, all servers holding either floor(avg) or
-   * ceiling(avg).
+   * <p/>
+   * Round-robin. Should yield a balanced cluster so same invariant as the load balancer holds, all
+   * servers holding either floor(avg) or ceiling(avg).
    */
   @Test
   public void testBulkAssignment() throws Exception {
     List<RegionInfo> regions = randomRegions(25);
-    Map<ServerName, List<RegionInfo>> assignments = loadBalancer
-        .roundRobinAssignment(regions, servers);
-    //test empty region/servers scenario
-    //this should not throw an NPE
+    Map<ServerName, List<RegionInfo>> assignments =
+        loadBalancer.roundRobinAssignment(regions, servers);
+    // test empty region/servers scenario
+    // this should not throw an NPE
     loadBalancer.roundRobinAssignment(regions, Collections.emptyList());
-    //test regular scenario
+    // test regular scenario
     assertTrue(assignments.keySet().size() == servers.size());
     for (ServerName sn : assignments.keySet()) {
       List<RegionInfo> regionAssigned = assignments.get(sn);
       for (RegionInfo region : regionAssigned) {
         TableName tableName = region.getTable();
         String groupName =
-            getMockedGroupInfoManager().getRSGroupOfTable(tableName);
+            tableDescs.get(tableName).getRegionServerGroup().orElse(RSGroupInfo.DEFAULT_GROUP);
         assertTrue(StringUtils.isNotEmpty(groupName));
-        RSGroupInfo gInfo = getMockedGroupInfoManager().getRSGroup(
-            groupName);
-        assertTrue(
-            "Region is not correctly assigned to group servers.",
-            gInfo.containsServer(sn.getAddress()));
+        RSGroupInfo gInfo = getMockedGroupInfoManager().getRSGroup(groupName);
+        assertTrue("Region is not correctly assigned to group servers.",
+          gInfo.containsServer(sn.getAddress()));
       }
     }
     ArrayListMultimap<String, ServerAndLoad> loadMap = convertToGroupBasedMap(assignments);
@@ -158,24 +155,25 @@ public class TestRSGroupBasedLoadBalancer extends RSGroupableBalancerTestBase {
     onlineServers.addAll(servers);
     List<RegionInfo> regions = randomRegions(25);
     int bogusRegion = 0;
-    for(RegionInfo region : regions){
-      String group = tableMap.get(region.getTable());
-      if("dg3".equals(group) || "dg4".equals(group)){
+    for (RegionInfo region : regions) {
+      String group = tableDescs.get(region.getTable()).getRegionServerGroup()
+          .orElse(RSGroupInfo.DEFAULT_GROUP);
+      if ("dg3".equals(group) || "dg4".equals(group)) {
         bogusRegion++;
       }
     }
     Set<Address> offlineServers = new HashSet<Address>();
     offlineServers.addAll(groupMap.get("dg3").getServers());
     offlineServers.addAll(groupMap.get("dg4").getServers());
-    for(Iterator<ServerName> it =  onlineServers.iterator(); it.hasNext();){
+    for (Iterator<ServerName> it = onlineServers.iterator(); it.hasNext();) {
       ServerName server = it.next();
       Address address = server.getAddress();
-      if(offlineServers.contains(address)){
+      if (offlineServers.contains(address)) {
         it.remove();
       }
     }
-    Map<ServerName, List<RegionInfo>> assignments = loadBalancer
-        .roundRobinAssignment(regions, onlineServers);
+    Map<ServerName, List<RegionInfo>> assignments =
+        loadBalancer.roundRobinAssignment(regions, onlineServers);
     assertEquals(bogusRegion, assignments.get(LoadBalancer.BOGUS_SERVER_NAME).size());
   }
 }
diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/balancer/TestRSGroupBasedLoadBalancerWithStochasticLoadBalancerAsInternal.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/balancer/TestRSGroupBasedLoadBalancerWithStochasticLoadBalancerAsInternal.java
index e588a7e..a4ae636 100644
--- a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/balancer/TestRSGroupBasedLoadBalancerWithStochasticLoadBalancerAsInternal.java
+++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/balancer/TestRSGroupBasedLoadBalancerWithStochasticLoadBalancerAsInternal.java
@@ -22,6 +22,7 @@ import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
+import java.io.IOException;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -32,7 +33,6 @@ import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.hbase.ClusterMetrics;
 import org.apache.hadoop.hbase.HBaseClassTestRule;
 import org.apache.hadoop.hbase.HBaseConfiguration;
-import org.apache.hadoop.hbase.HBaseIOException;
 import org.apache.hadoop.hbase.RegionMetrics;
 import org.apache.hadoop.hbase.ServerMetrics;
 import org.apache.hadoop.hbase.ServerName;
@@ -98,7 +98,7 @@ public class TestRSGroupBasedLoadBalancerWithStochasticLoadBalancerAsInternal
    * Test HBASE-20791
    */
   @Test
-  public void testBalanceCluster() throws HBaseIOException {
+  public void testBalanceCluster() throws IOException {
     // mock cluster State
     Map<ServerName, List<RegionInfo>> clusterState = new HashMap<ServerName, List<RegionInfo>>();
     ServerName serverA = servers.get(0);
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 27511e3..7471458 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
@@ -29,7 +29,6 @@ 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;
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 4b4097f..076362e 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
@@ -21,7 +21,6 @@ import static org.apache.hadoop.hbase.rsgroup.RSGroupAdminServer.DEFAULT_MAX_RET
 import static org.apache.hadoop.hbase.util.Threads.sleep;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
@@ -408,7 +407,9 @@ public class TestRSGroupsAdmin2 extends TestRSGroupsBase {
     assertTrue(newGroupTables.contains(tableName));
 
     // verify that all region still assgin on targetServer
-    Assert.assertEquals(5, getTableServerRegionMap().get(tableName).get(targetServer).size());
+    // 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.
+    // Assert.assertEquals(5, getTableServerRegionMap().get(tableName).get(targetServer).size());
 
     assertTrue(observer.preMoveServersAndTables);
     assertTrue(observer.postMoveServersAndTables);
@@ -503,61 +504,6 @@ public class TestRSGroupsAdmin2 extends TestRSGroupsBase {
     });
   }
 
-  @Test
-  public void testFailedMoveBeforeRetryExhaustedWhenMoveTable() throws Exception {
-    final RSGroupInfo newGroup = addGroup(getGroupName(name.getMethodName()), 1);
-    Pair<ServerName, RegionStateNode> gotPair = createTableWithRegionSplitting(newGroup,
-        5);
-
-    // move table to group
-    Thread t2 = new Thread(() -> {
-      LOG.info("thread2 start running, to move regions");
-      try {
-        rsGroupAdmin.moveTables(Sets.newHashSet(tableName), newGroup.getName());
-      } catch (IOException e) {
-        LOG.error("move server error", e);
-      }
-    });
-    t2.start();
-
-    // start thread to recover region state
-    final ServerName ss = gotPair.getFirst();
-    final RegionStateNode rsn = gotPair.getSecond();
-    AtomicBoolean changed = new AtomicBoolean(false);
-
-    Thread t1 = recoverRegionStateThread(ss, server -> {
-      List<RegionInfo> regions = master.getAssignmentManager().getRegionsOnServer(ss);
-      List<RegionInfo> tableRegions = new ArrayList<>();
-      for (RegionInfo regionInfo : regions) {
-        if (regionInfo.getTable().equals(tableName)) {
-          tableRegions.add(regionInfo);
-        }
-      }
-      return tableRegions;
-    }, rsn, changed);
-    t1.start();
-
-    t1.join();
-    t2.join();
-
-    TEST_UTIL.waitFor(WAIT_TIMEOUT, new Waiter.Predicate<Exception>() {
-      @Override
-      public boolean evaluate() {
-        if (changed.get()) {
-          boolean serverHasTableRegions = false;
-          for (RegionInfo regionInfo : master.getAssignmentManager().getRegionsOnServer(ss)) {
-            if (regionInfo.getTable().equals(tableName)) {
-              serverHasTableRegions = true;
-              break;
-            }
-          }
-          return !serverHasTableRegions && !rsn.getRegionLocation().equals(ss);
-        }
-        return false;
-      }
-    });
-  }
-
   private <T> Thread recoverRegionStateThread(T owner, Function<T, List<RegionInfo>> getRegions,
       RegionStateNode rsn, AtomicBoolean changed){
     return new Thread(() -> {
@@ -653,50 +599,6 @@ public class TestRSGroupsAdmin2 extends TestRSGroupsBase {
   }
 
   @Test
-  public void testFailedMoveTablesAndRepair() throws Exception{
-    // This UT calls moveTables() 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
-    // regions on target group servers after the call.
-    final RSGroupInfo newGroup = addGroup(getGroupName(name.getMethodName()), 1);
-    Iterator iterator = newGroup.getServers().iterator();
-    Address newGroupServer1 = (Address) iterator.next();
-
-    // create table
-    // randomly set a region state to SPLITTING to make move abort
-    Pair<ServerName, RegionStateNode> gotPair = createTableWithRegionSplitting(newGroup,
-        new Random().nextInt(8) + 4);
-    RegionStateNode rsn = gotPair.getSecond();
-
-    // move table to newGroup and check regions
-    try {
-      rsGroupAdmin.moveTables(Sets.newHashSet(tableName), 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()){
-      if (regionInfo.getTable().equals(tableName) && regionInfo.equals(rsn.getRegionInfo())) {
-        assertNotEquals(master.getAssignmentManager().getRegionStates()
-            .getRegionServerOfRegion(regionInfo).getAddress(), newGroupServer1);
-      }
-    }
-
-    // retry move table to newGroup and check if all regions are corrected
-    rsn.setState(RegionState.State.OPEN);
-    rsGroupAdmin.moveTables(Sets.newHashSet(tableName), newGroup.getName());
-    for(RegionInfo regionInfo : master.getAssignmentManager().getAssignedRegions()){
-      if (regionInfo.getTable().equals(tableName)) {
-        assertEquals(master.getAssignmentManager().getRegionStates()
-            .getRegionServerOfRegion(regionInfo).getAddress(), newGroupServer1);
-      }
-    }
-  }
-
-  @Test
   public void testFailedMoveServersAndRepair() throws Exception{
     // This UT calls moveServers() twice to test the idempotency of it.
     // The first time, movement fails because a region is made in SPLITTING state
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 67f5c7e..8d10850 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
@@ -45,8 +45,6 @@ import org.junit.experimental.categories.Category;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import org.apache.hbase.thirdparty.com.google.common.collect.Sets;
-
 @Category({ MediumTests.class })
 public class TestRSGroupsBalance extends TestRSGroupsBase {
 
@@ -153,19 +151,21 @@ public class TestRSGroupsBalance extends TestRSGroupsBase {
 
   @Test
   public void testMisplacedRegions() throws Exception {
-    final TableName tableName = TableName.valueOf(tablePrefix + "_testMisplacedRegions");
-    LOG.info("testMisplacedRegions");
+    String namespace = tablePrefix + "_" + name.getMethodName();
+    TEST_UTIL.getAdmin().createNamespace(NamespaceDescriptor.create(namespace).build());
+    final TableName tableName =
+        TableName.valueOf(namespace, tablePrefix + "_" + name.getMethodName());
+    LOG.info(name.getMethodName());
 
-    final RSGroupInfo RSGroupInfo = addGroup("testMisplacedRegions", 1);
+    final RSGroupInfo rsGroupInfo = addGroup(name.getMethodName(), 1);
 
     TEST_UTIL.createMultiRegionTable(tableName, new byte[] { 'f' }, 15);
     TEST_UTIL.waitUntilAllRegionsAssigned(tableName);
-
-    rsGroupAdminEndpoint.getGroupInfoManager().moveTables(Sets.newHashSet(tableName),
-      RSGroupInfo.getName());
+    TEST_UTIL.getAdmin().modifyNamespace(NamespaceDescriptor.create(namespace)
+        .addConfiguration(RSGroupInfo.NAMESPACE_DESC_PROP_GROUP, rsGroupInfo.getName()).build());
 
     admin.balancerSwitch(true, true);
-    assertTrue(rsGroupAdmin.balanceRSGroup(RSGroupInfo.getName()));
+    assertTrue(rsGroupAdmin.balanceRSGroup(rsGroupInfo.getName()));
     admin.balancerSwitch(false, true);
     assertTrue(observer.preBalanceRSGroupCalled);
     assertTrue(observer.postBalanceRSGroupCalled);
@@ -174,7 +174,7 @@ public class TestRSGroupsBalance extends TestRSGroupsBase {
       @Override
       public boolean evaluate() throws Exception {
         ServerName serverName =
-          ServerName.valueOf(RSGroupInfo.getServers().iterator().next().toString(), 1);
+            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 0e4fb34..b91cd5e 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
@@ -74,7 +74,7 @@ public abstract class TestRSGroupsBase {
   protected static HBaseTestingUtility TEST_UTIL;
   protected static Admin admin;
   protected static HBaseCluster cluster;
-  protected static RSGroupAdmin rsGroupAdmin;
+  protected static RSGroupAdminClient rsGroupAdmin;
   protected static HMaster master;
   protected boolean INIT = false;
   protected static RSGroupAdminEndpoint rsGroupAdminEndpoint;
@@ -188,8 +188,8 @@ public abstract class TestRSGroupsBase {
     RSGroupInfo defaultInfo = rsGroupAdmin.getRSGroupInfo(RSGroupInfo.DEFAULT_GROUP);
     rsGroupAdmin.addRSGroup(groupName);
     Set<Address> set = new HashSet<>();
-    for(Address server: defaultInfo.getServers()) {
-      if(set.size() == serverCount) {
+    for (Address server : defaultInfo.getServers()) {
+      if (set.size() == serverCount) {
         break;
       }
       set.add(server);
@@ -222,7 +222,7 @@ public abstract class TestRSGroupsBase {
   }
 
   protected void deleteGroups() throws IOException {
-    RSGroupAdmin groupAdmin = new RSGroupAdminClient(TEST_UTIL.getConnection());
+    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);
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 60887e4..d3577f2 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,7 +17,6 @@
  */
 package org.apache.hadoop.hbase.rsgroup;
 
-import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 
 import org.apache.hadoop.hbase.HBaseClassTestRule;
@@ -113,7 +112,7 @@ public class TestRSGroupsOfflineMode {
     final HRegionServer groupRS = ((MiniHBaseCluster) cluster).getRegionServer(1);
     final HRegionServer failoverRS = ((MiniHBaseCluster) cluster).getRegionServer(2);
     String newGroup = "my_group";
-    RSGroupAdmin groupAdmin = new RSGroupAdminClient(TEST_UTIL.getConnection());
+    RSGroupAdminClient groupAdmin = new RSGroupAdminClient(TEST_UTIL.getConnection());
     groupAdmin.addRSGroup(newGroup);
     if (master.getAssignmentManager().getRegionStates().getRegionAssignments()
       .containsValue(failoverRS.getServerName())) {
@@ -168,9 +167,6 @@ public class TestRSGroupsOfflineMode {
       .getMasterCoprocessorHost().findCoprocessor(RSGroupAdminEndpoint.class).getGroupInfoManager();
     // Make sure balancer is in offline mode, since this is what we're testing.
     assertFalse(groupMgr.isOnline());
-    // Verify the group affiliation that's loaded from ZK instead of tables.
-    assertEquals(newGroup, groupMgr.getRSGroupOfTable(RSGroupInfoManagerImpl.RSGROUP_TABLE_NAME));
-    assertEquals(RSGroupInfo.DEFAULT_GROUP, groupMgr.getRSGroupOfTable(failoverTable));
     // Kill final regionserver to see the failover happens for all tables except GROUP table since
     // it's group does not have any online RS.
     killRS.stop("die");
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 fcaf1a7..a8cd277 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
@@ -17,17 +17,26 @@
  */
 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 org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.hbase.NamespaceDescriptor;
 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;
@@ -37,22 +46,20 @@ 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.junit.Assert;
 
 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 implements RSGroupAdmin {
-  private Table table;
+public class VerifyingRSGroupAdminClient extends RSGroupAdminClient {
+  private Connection conn;
   private ZKWatcher zkw;
-  private RSGroupAdmin wrapped;
+  private RSGroupAdminClient wrapped;
 
-  public VerifyingRSGroupAdminClient(RSGroupAdmin RSGroupAdmin, Configuration conf)
+  public VerifyingRSGroupAdminClient(RSGroupAdminClient RSGroupAdmin, Configuration conf)
       throws IOException {
     wrapped = RSGroupAdmin;
-    table = ConnectionFactory.createConnection(conf)
-        .getTable(RSGroupInfoManagerImpl.RSGROUP_TABLE_NAME);
+    conn = ConnectionFactory.createConnection(conf);
     zkw = new ZKWatcher(conf, this.getClass().getSimpleName(), null);
   }
 
@@ -121,31 +128,41 @@ public class VerifyingRSGroupAdminClient implements RSGroupAdmin {
   public void verify() throws IOException {
     Map<String, RSGroupInfo> groupMap = Maps.newHashMap();
     Set<RSGroupInfo> zList = Sets.newHashSet();
-
-    for (Result result : table.getScanner(new Scan())) {
-      RSGroupProtos.RSGroupInfo proto =
-          RSGroupProtos.RSGroupInfo.parseFrom(
-              result.getValue(
-                  RSGroupInfoManagerImpl.META_FAMILY_BYTES,
-                  RSGroupInfoManagerImpl.META_QUALIFIER_BYTES));
-      groupMap.put(proto.getName(), ProtobufUtil.toGroupInfo(proto));
+    List<TableDescriptor> tds = new ArrayList<>();
+    try (Admin admin = conn.getAdmin()) {
+      tds.addAll(admin.listTableDescriptors());
+      tds.addAll(admin.listTableDescriptorsByNamespace(NamespaceDescriptor.SYSTEM_NAMESPACE_NAME));
+    }
+    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));
+      }
     }
-    Assert.assertEquals(Sets.newHashSet(groupMap.values()),
-        Sets.newHashSet(wrapped.listRSGroups()));
+    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)) {
+      for (String znode : ZKUtil.listChildrenNoWatch(zkw, groupBasePath)) {
         byte[] data = ZKUtil.getData(zkw, ZNodePaths.joinZNode(groupBasePath, znode));
-        if(data.length > 0) {
+        if (data.length > 0) {
           ProtobufUtil.expectPBMagicPrefix(data);
-          ByteArrayInputStream bis = new ByteArrayInputStream(
-              data, ProtobufUtil.lengthOfPBMagic(), data.length);
-          zList.add(ProtobufUtil.toGroupInfo(RSGroupProtos.RSGroupInfo.parseFrom(bis)));
+          ByteArrayInputStream bis =
+              new ByteArrayInputStream(data, ProtobufUtil.lengthOfPBMagic(), data.length);
+          RSGroupInfo rsGroupInfo =
+              ProtobufUtil.toGroupInfo(RSGroupProtos.RSGroupInfo.parseFrom(bis));
+          zList.add(RSGroupUtil.fillTables(rsGroupInfo, tds));
         }
       }
-      Assert.assertEquals(zList.size(), groupMap.size());
-      for(RSGroupInfo RSGroupInfo : zList) {
-        Assert.assertTrue(groupMap.get(RSGroupInfo.getName()).equals(RSGroupInfo));
+      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);


[hbase] 05/21: HBASE-22809 Allow creating table in group when rs group contains no live servers (#464)

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

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

commit 44ddb30914535c16eb5ae07e36ca93c26c1d455c
Author: Duo Zhang <zh...@apache.org>
AuthorDate: Fri Aug 9 09:25:00 2019 +0800

    HBASE-22809 Allow creating table in group when rs group contains no live servers (#464)
    
    Signed-off-by: Guanghao Zhang <zg...@apache.org>
---
 .../hadoop/hbase/rsgroup/RSGroupAdminEndpoint.java | 119 ++++++++++++---------
 .../hadoop/hbase/rsgroup/TestRSGroupsAdmin1.java   |   4 +-
 .../hadoop/hbase/rsgroup/TestRSGroupsBasics.java   |  42 --------
 3 files changed, 71 insertions(+), 94 deletions(-)

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 3c4530f..a2a5623 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
@@ -21,11 +21,12 @@ import com.google.protobuf.Service;
 import java.io.IOException;
 import java.util.Collections;
 import java.util.List;
+import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
+import java.util.function.Supplier;
 import java.util.stream.Collectors;
 import org.apache.hadoop.hbase.CoprocessorEnvironment;
-import org.apache.hadoop.hbase.HBaseIOException;
 import org.apache.hadoop.hbase.HConstants;
 import org.apache.hadoop.hbase.NamespaceDescriptor;
 import org.apache.hadoop.hbase.ServerName;
@@ -68,7 +69,7 @@ public class RSGroupAdminEndpoint implements MasterCoprocessor, MasterObserver {
     groupInfoManager = RSGroupInfoManagerImpl.getInstance(master);
     groupAdminServer = new RSGroupAdminServer(master, groupInfoManager);
     Class<?> clazz =
-        master.getConfiguration().getClass(HConstants.HBASE_MASTER_LOADBALANCER_CLASS, null);
+      master.getConfiguration().getClass(HConstants.HBASE_MASTER_LOADBALANCER_CLASS, null);
     if (!RSGroupableBalancer.class.isAssignableFrom(clazz)) {
       throw new IOException("Configured balancer does not support RegionServer groups.");
     }
@@ -108,85 +109,101 @@ public class RSGroupAdminEndpoint implements MasterCoprocessor, MasterObserver {
 
   @Override
   public void postClearDeadServers(ObserverContext<MasterCoprocessorEnvironment> ctx,
-      List<ServerName> servers, List<ServerName> notClearedServers) throws IOException {
+    List<ServerName> servers, List<ServerName> notClearedServers) throws IOException {
     Set<Address> clearedServer =
-        servers.stream().filter(server -> !notClearedServers.contains(server))
-            .map(ServerName::getAddress).collect(Collectors.toSet());
+      servers.stream().filter(server -> !notClearedServers.contains(server))
+        .map(ServerName::getAddress).collect(Collectors.toSet());
     if (!clearedServer.isEmpty()) {
       groupAdminServer.removeServers(clearedServer);
     }
   }
 
-  private void checkGroupExists(Optional<String> optGroupName) throws IOException {
+  private RSGroupInfo checkGroupExists(Optional<String> optGroupName, Supplier<String> forWhom)
+    throws IOException {
     if (optGroupName.isPresent()) {
       String groupName = optGroupName.get();
-      if (groupAdminServer.getRSGroupInfo(groupName) == null) {
-        throw new ConstraintException("Region server group " + groupName + " does not exit");
+      RSGroupInfo group = groupAdminServer.getRSGroupInfo(groupName);
+      if (group == null) {
+        throw new ConstraintException(
+          "Region server group " + groupName + " for " + forWhom.get() + " does not exit");
       }
+      return group;
     }
+    return null;
   }
 
-  private boolean rsgroupHasServersOnline(TableDescriptor desc) throws IOException {
-    RSGroupInfo rsGroupInfo;
-    Optional<String> optGroupName = desc.getRegionServerGroup();
-    if (optGroupName.isPresent()) {
-      String groupName = optGroupName.get();
-      if (groupName.equals(RSGroupInfo.DEFAULT_GROUP)) {
-        // do not check for default group
-        return true;
-      }
-      rsGroupInfo = groupAdminServer.getRSGroupInfo(groupName);
-      if (rsGroupInfo == null) {
-        throw new ConstraintException(
-            "RSGroup " + groupName + " for table " + desc.getTableName() + " does not exist");
-      }
-    } else {
-      NamespaceDescriptor nd =
-          master.getClusterSchema().getNamespace(desc.getTableName().getNamespaceAsString());
-      String groupNameOfNs = nd.getConfigurationValue(RSGroupInfo.NAMESPACE_DESC_PROP_GROUP);
-      if (groupNameOfNs == null || groupNameOfNs.equals(RSGroupInfo.DEFAULT_GROUP)) {
-        // do not check for default group
-        return true;
-      }
-      rsGroupInfo = groupAdminServer.getRSGroupInfo(groupNameOfNs);
-      if (rsGroupInfo == null) {
-        throw new ConstraintException("RSGroup " + groupNameOfNs + " for table " +
-            desc.getTableName() + "(inherit from namespace) does not exist");
-      }
+  private Optional<String> getNamespaceGroup(NamespaceDescriptor namespaceDesc) {
+    return Optional
+      .ofNullable(namespaceDesc.getConfigurationValue(RSGroupInfo.NAMESPACE_DESC_PROP_GROUP));
+  }
+
+  // Do not allow creating new tables/namespaces which has an empty rs group, expect the default rs
+  // group. Notice that we do not check for online servers, as this is not stable because region
+  // servers can die at any time.
+  private void checkGroupNotEmpty(RSGroupInfo rsGroupInfo, Supplier<String> forWhom)
+    throws ConstraintException {
+    if (rsGroupInfo == null || rsGroupInfo.getName().equals(RSGroupInfo.DEFAULT_GROUP)) {
+      // we do not have a rs group config or we explicitly set the rs group to default, then no need
+      // to check.
+      return;
+    }
+    if (rsGroupInfo.getServers().isEmpty()) {
+      throw new ConstraintException(
+        "No servers in the rsgroup " + rsGroupInfo.getName() + " for " + forWhom.get());
     }
-    return master.getServerManager().createDestinationServersList().stream()
-        .anyMatch(onlineServer -> rsGroupInfo.containsServer(onlineServer.getAddress()));
   }
 
   @Override
   public void preCreateTableAction(ObserverContext<MasterCoprocessorEnvironment> ctx,
-      TableDescriptor desc, RegionInfo[] regions) throws IOException {
-    checkGroupExists(desc.getRegionServerGroup());
-    if (!desc.getTableName().isSystemTable() && !rsgroupHasServersOnline(desc)) {
-      throw new HBaseIOException("No online servers in the rsgroup for " + desc);
+    TableDescriptor desc, RegionInfo[] regions) throws IOException {
+    if (desc.getTableName().isSystemTable()) {
+      // do not check for system tables as we may block the bootstrap.
+      return;
+    }
+    Supplier<String> forWhom = () -> "table " + desc.getTableName();
+    RSGroupInfo rsGroupInfo = checkGroupExists(desc.getRegionServerGroup(), forWhom);
+    if (rsGroupInfo == null) {
+      // we do not set rs group info on table, check if we have one on namespace
+      String namespace = desc.getTableName().getNamespaceAsString();
+      NamespaceDescriptor nd = master.getClusterSchema().getNamespace(namespace);
+      forWhom = () -> "table " + desc.getTableName() + "(inherit from namespace)";
+      rsGroupInfo = checkGroupExists(getNamespaceGroup(nd), forWhom);
     }
+    checkGroupNotEmpty(rsGroupInfo, forWhom);
   }
 
   @Override
   public TableDescriptor preModifyTable(ObserverContext<MasterCoprocessorEnvironment> ctx,
-      TableName tableName, TableDescriptor currentDescriptor, TableDescriptor newDescriptor)
-      throws IOException {
-    checkGroupExists(newDescriptor.getRegionServerGroup());
+    TableName tableName, TableDescriptor currentDescriptor, TableDescriptor newDescriptor)
+    throws IOException {
+    if (!currentDescriptor.getRegionServerGroup().equals(newDescriptor.getRegionServerGroup())) {
+      Supplier<String> forWhom = () -> "table " + newDescriptor.getTableName();
+      RSGroupInfo rsGroupInfo = checkGroupExists(newDescriptor.getRegionServerGroup(), forWhom);
+      checkGroupNotEmpty(rsGroupInfo, forWhom);
+    }
     return MasterObserver.super.preModifyTable(ctx, tableName, currentDescriptor, newDescriptor);
   }
 
+  private void checkNamespaceGroup(NamespaceDescriptor nd) throws IOException {
+    Supplier<String> forWhom = () -> "namespace " + nd.getName();
+    RSGroupInfo rsGroupInfo = checkGroupExists(getNamespaceGroup(nd), forWhom);
+    checkGroupNotEmpty(rsGroupInfo, forWhom);
+  }
+
   @Override
   public void preCreateNamespace(ObserverContext<MasterCoprocessorEnvironment> ctx,
-      NamespaceDescriptor ns) throws IOException {
-    checkGroupExists(
-      Optional.ofNullable(ns.getConfigurationValue(RSGroupInfo.NAMESPACE_DESC_PROP_GROUP)));
+    NamespaceDescriptor ns) throws IOException {
+    checkNamespaceGroup(ns);
   }
 
   @Override
   public void preModifyNamespace(ObserverContext<MasterCoprocessorEnvironment> ctx,
-      NamespaceDescriptor currentNsDescriptor, NamespaceDescriptor newNsDescriptor)
-      throws IOException {
-    checkGroupExists(Optional
-        .ofNullable(newNsDescriptor.getConfigurationValue(RSGroupInfo.NAMESPACE_DESC_PROP_GROUP)));
+    NamespaceDescriptor currentNsDescriptor, NamespaceDescriptor newNsDescriptor)
+    throws IOException {
+    if (!Objects.equals(
+      currentNsDescriptor.getConfigurationValue(RSGroupInfo.NAMESPACE_DESC_PROP_GROUP),
+      newNsDescriptor.getConfigurationValue(RSGroupInfo.NAMESPACE_DESC_PROP_GROUP))) {
+      checkNamespaceGroup(newNsDescriptor);
+    }
   }
 }
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 7471458..aff591f 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
@@ -152,12 +152,14 @@ public class TestRSGroupsAdmin1 extends TestRSGroupsBase {
     String nsName = tablePrefix + "_foo";
     String groupName = tablePrefix + "_foo";
     LOG.info("testNamespaceConstraint");
-    rsGroupAdmin.addRSGroup(groupName);
+    addGroup(groupName, 1);
     assertTrue(observer.preAddRSGroupCalled);
     assertTrue(observer.postAddRSGroupCalled);
 
     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);
     // test removing a referenced group
     try {
       rsGroupAdmin.removeRSGroup(groupName);
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 6e89738..79aaab8 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
@@ -20,11 +20,8 @@ package org.apache.hadoop.hbase.rsgroup;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
 
 import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Iterator;
 import java.util.List;
 import java.util.Set;
 import org.apache.hadoop.hbase.HBaseClassTestRule;
@@ -137,45 +134,6 @@ public class TestRSGroupsBasics extends TestRSGroupsBase {
   }
 
   @Test
-  public void testCreateWhenRsgroupNoOnlineServers() throws Exception {
-    LOG.info("testCreateWhenRsgroupNoOnlineServers");
-
-    // set rsgroup has no online servers and test create table
-    final RSGroupInfo appInfo = addGroup("appInfo", 1);
-    Iterator<Address> iterator = appInfo.getServers().iterator();
-    List<ServerName> serversToDecommission = new ArrayList<>();
-    ServerName targetServer = getServerName(iterator.next());
-    assertTrue(master.getServerManager().getOnlineServers().containsKey(targetServer));
-    serversToDecommission.add(targetServer);
-    admin.decommissionRegionServers(serversToDecommission, true);
-    assertEquals(1, admin.listDecommissionedRegionServers().size());
-
-    final TableName tableName = TableName.valueOf(tablePrefix + "_ns", 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();
-    try {
-      admin.createTable(desc);
-      fail("Shouldn't create table successfully!");
-    } catch (Exception e) {
-      LOG.debug("create table error", e);
-    }
-
-    // recommission and test create table
-    admin.recommissionRegionServer(targetServer, null);
-    assertEquals(0, admin.listDecommissionedRegionServers().size());
-    admin.createTable(desc);
-    // wait for created table to be assigned
-    TEST_UTIL.waitFor(WAIT_TIMEOUT, new Waiter.Predicate<Exception>() {
-      @Override
-      public boolean evaluate() throws Exception {
-        return getTableRegionMap().get(desc.getTableName()) != null;
-      }
-    });
-  }
-
-  @Test
   public void testDefaultNamespaceCreateAndAssign() throws Exception {
     LOG.info("testDefaultNamespaceCreateAndAssign");
     String tableName = tablePrefix + "_testCreateAndAssign";


[hbase] 16/21: 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 master
in repository https://gitbox.apache.org/repos/asf/hbase.git

commit 5de0a5ef7e188c395bab50b3de487973982ca8c3
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] 13/21: 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 master
in repository https://gitbox.apache.org/repos/asf/hbase.git

commit e8e9eecef1d24508448e8f9f10eb1b6ef0941d92
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] 17/21: 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 master
in repository https://gitbox.apache.org/repos/asf/hbase.git

commit 7386369fec5d98b99262910c8f6afb56cc672339
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       | 12 ++++
 .../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      | 21 +++++--
 .../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, 402 insertions(+), 45 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 1e73022..2234a2a 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;
 
 /**
@@ -2323,6 +2324,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 c790cde..d1f1dc9 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;
@@ -1005,6 +1006,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));
   }
@@ -1023,5 +1035,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 70ce606..3a37af4 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;
 
 /**
@@ -1565,6 +1566,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 edbd646..3117bad 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
@@ -49,6 +49,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;
 
 /**
@@ -883,6 +884,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));
   }
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 4e0ad26..17e5d53 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);
@@ -4003,6 +4009,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 ff492ee..60eacda 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 52fc425..3ea0420 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
@@ -96,6 +96,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;
@@ -321,6 +322,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;
@@ -329,6 +332,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;
@@ -3161,4 +3166,63 @@ public class MasterRpcServices extends RSRpcServices implements
     }
     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 f443360..3ac6aba 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();
   }
 
@@ -124,6 +132,7 @@ public class TestMigrateRSGroupInfo extends TestRSGroupsBase {
     RESUME = new CountDownLatch(1);
     TEST_UTIL.getMiniHBaseCluster().startMaster();
     TEST_UTIL.invalidateConnection();
+    RS_GROUP_ADMIN_CLIENT = new RSGroupAdminClient(TEST_UTIL.getConnection());
 
     // wait until we can get the rs group info for a table
     TEST_UTIL.waitFor(30000, () -> {
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 119cada..60b9000 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
@@ -78,6 +78,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;
@@ -779,6 +780,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 a6f9fac..6adfef3 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
@@ -71,6 +71,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;
@@ -1226,4 +1227,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] 09/21: HBASE-22987 Calculate the region servers in default group in foreground (#599)

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

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

commit 0e29d62a2d46245adea8ac36e61f96f842b3bf77
Author: Duo Zhang <zh...@apache.org>
AuthorDate: Wed Sep 11 22:10:52 2019 +0800

    HBASE-22987 Calculate the region servers in default group in foreground (#599)
    
    Signed-off-by: Guanghao Zhang <zg...@apache.org>
---
 .../hbase/rsgroup/RSGroupInfoManagerImpl.java      | 138 +++++----------------
 1 file changed, 32 insertions(+), 106 deletions(-)

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 6725066..7224869 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
@@ -30,6 +30,7 @@ import java.util.OptionalLong;
 import java.util.Set;
 import java.util.SortedSet;
 import java.util.TreeSet;
+import java.util.stream.Collectors;
 import org.apache.hadoop.hbase.Coprocessor;
 import org.apache.hadoop.hbase.DoNotRetryIOException;
 import org.apache.hadoop.hbase.NamespaceDescriptor;
@@ -174,8 +175,6 @@ final class RSGroupInfoManagerImpl implements RSGroupInfoManager {
   private final RSGroupStartupWorker rsGroupStartupWorker;
   // contains list of groups that were last flushed to persistent store
   private Set<String> prevRSGroups = new HashSet<>();
-  private final ServerEventsListenerThread serverEventsListenerThread =
-    new ServerEventsListenerThread();
 
   private RSGroupInfoManagerImpl(MasterServices masterServices) throws IOException {
     this.masterServices = masterServices;
@@ -184,11 +183,34 @@ final class RSGroupInfoManagerImpl implements RSGroupInfoManager {
     this.rsGroupStartupWorker = new RSGroupStartupWorker();
   }
 
+  private synchronized void updateDefaultServers() {
+    LOG.info("Updating default servers.");
+    Map<String, RSGroupInfo> newGroupMap = Maps.newHashMap(holder.groupName2Group);
+    RSGroupInfo oldDefaultGroupInfo = getRSGroup(RSGroupInfo.DEFAULT_GROUP);
+    assert oldDefaultGroupInfo != null;
+    RSGroupInfo newDefaultGroupInfo =
+      new RSGroupInfo(RSGroupInfo.DEFAULT_GROUP, getDefaultServers());
+    newDefaultGroupInfo.addAllTables(oldDefaultGroupInfo.getTables());
+    newGroupMap.put(RSGroupInfo.DEFAULT_GROUP, newDefaultGroupInfo);
+    // do not need to persist, as we do not persist default group.
+    resetRSGroupMap(newGroupMap);
+    LOG.info("Updated default servers, {} servers", newDefaultGroupInfo.getServers().size());
+  }
 
   private synchronized void init() throws IOException {
     refresh(false);
-    serverEventsListenerThread.start();
-    masterServices.getServerManager().registerListener(serverEventsListenerThread);
+    masterServices.getServerManager().registerListener(new ServerListener() {
+
+      @Override
+      public void serverAdded(ServerName serverName) {
+        updateDefaultServers();
+      }
+
+      @Override
+      public void serverRemoved(ServerName serverName) {
+        updateDefaultServers();
+      }
+    });
     migrate();
   }
 
@@ -225,19 +247,11 @@ final class RSGroupInfoManagerImpl implements RSGroupInfoManager {
   }
 
   /**
-   * @param master the master to get online servers for
    * @return Set of online Servers named for their hostname and port (not ServerName).
    */
-  private static Set<Address> getOnlineServers(final MasterServices master) {
-    Set<Address> onlineServers = new HashSet<Address>();
-    if (master == null) {
-      return onlineServers;
-    }
-
-    for (ServerName server : master.getServerManager().getOnlineServers().keySet()) {
-      onlineServers.add(server.getAddress());
-    }
-    return onlineServers;
+  private Set<Address> getOnlineServers() {
+    return masterServices.getServerManager().getOnlineServers().keySet().stream()
+      .map(ServerName::getAddress).collect(Collectors.toSet());
   }
 
   @Override
@@ -249,8 +263,7 @@ final class RSGroupInfoManagerImpl implements RSGroupInfoManager {
     // it. If not 'default' group, add server to 'dst' rsgroup EVEN IF IT IS NOT online (could be a
     // rsgroup of dead servers that are to come back later).
     Set<Address> onlineServers =
-      dst.getName().equals(RSGroupInfo.DEFAULT_GROUP) ? getOnlineServers(this.masterServices)
-        : null;
+      dst.getName().equals(RSGroupInfo.DEFAULT_GROUP) ? getOnlineServers() : null;
     for (Address el : servers) {
       src.removeServer(el);
       if (onlineServers != null) {
@@ -617,25 +630,8 @@ final class RSGroupInfoManagerImpl implements RSGroupInfoManager {
     this.prevRSGroups.addAll(currentGroups);
   }
 
-  // Called by getDefaultServers. Presume it has lock in place.
-  private List<ServerName> getOnlineRS() throws IOException {
-    if (masterServices != null) {
-      return masterServices.getServerManager().getOnlineServersList();
-    }
-    LOG.debug("Reading online RS from zookeeper");
-    List<ServerName> servers = new ArrayList<>();
-    try {
-      for (String el : ZKUtil.listChildrenNoWatch(watcher, watcher.getZNodePaths().rsZNode)) {
-        servers.add(ServerName.parseServerName(el));
-      }
-    } catch (KeeperException e) {
-      throw new IOException("Failed to retrieve server list from zookeeper", e);
-    }
-    return servers;
-  }
-
   // Called by ServerEventsListenerThread. Presume it has lock on this manager when it runs.
-  private SortedSet<Address> getDefaultServers() throws IOException {
+  private SortedSet<Address> getDefaultServers() {
     // Build a list of servers in other groups than default group, from rsGroupMap
     Set<Address> serversInOtherGroup = new HashSet<>();
     for (RSGroupInfo group : listRSGroups() /* get from rsGroupMap */) {
@@ -646,7 +642,7 @@ final class RSGroupInfoManagerImpl implements RSGroupInfoManager {
 
     // Get all online servers from Zookeeper and find out servers in default group
     SortedSet<Address> defaultServers = Sets.newTreeSet();
-    for (ServerName serverName : getOnlineRS()) {
+    for (ServerName serverName : masterServices.getServerManager().getOnlineServers().keySet()) {
       Address server = Address.fromParts(serverName.getHostname(), serverName.getPort());
       if (!serversInOtherGroup.contains(server)) { // not in other groups
         defaultServers.add(server);
@@ -655,76 +651,6 @@ final class RSGroupInfoManagerImpl implements RSGroupInfoManager {
     return defaultServers;
   }
 
-  // Called by ServerEventsListenerThread. Synchronize on this because redoing
-  // the rsGroupMap then writing it out.
-  private synchronized void updateDefaultServers(SortedSet<Address> servers) {
-    Map<String, RSGroupInfo> rsGroupMap = holder.groupName2Group;
-    RSGroupInfo info = rsGroupMap.get(RSGroupInfo.DEFAULT_GROUP);
-    RSGroupInfo newInfo = new RSGroupInfo(info.getName(), servers);
-    HashMap<String, RSGroupInfo> newGroupMap = Maps.newHashMap(rsGroupMap);
-    newGroupMap.put(newInfo.getName(), newInfo);
-    resetRSGroupMap(newGroupMap);
-  }
-
-  /**
-   * Calls {@link RSGroupInfoManagerImpl#updateDefaultServers(SortedSet)} to update list of known
-   * servers. Notifications about server changes are received by registering {@link ServerListener}.
-   * As a listener, we need to return immediately, so the real work of updating the servers is done
-   * asynchronously in this thread.
-   */
-  private class ServerEventsListenerThread extends Thread implements ServerListener {
-    private final Logger LOG = LoggerFactory.getLogger(ServerEventsListenerThread.class);
-    private boolean changed = false;
-
-    ServerEventsListenerThread() {
-      setDaemon(true);
-    }
-
-    @Override
-    public void serverAdded(ServerName serverName) {
-      serverChanged();
-    }
-
-    @Override
-    public void serverRemoved(ServerName serverName) {
-      serverChanged();
-    }
-
-    private synchronized void serverChanged() {
-      changed = true;
-      this.notify();
-    }
-
-    @Override
-    public void run() {
-      setName(ServerEventsListenerThread.class.getName() + "-" + masterServices.getServerName());
-      SortedSet<Address> prevDefaultServers = new TreeSet<>();
-      while (isMasterRunning(masterServices)) {
-        try {
-          LOG.info("Updating default servers.");
-          SortedSet<Address> servers = RSGroupInfoManagerImpl.this.getDefaultServers();
-          if (!servers.equals(prevDefaultServers)) {
-            RSGroupInfoManagerImpl.this.updateDefaultServers(servers);
-            prevDefaultServers = servers;
-            LOG.info("Updated with servers: " + servers.size());
-          }
-          try {
-            synchronized (this) {
-              while (!changed) {
-                wait();
-              }
-              changed = false;
-            }
-          } catch (InterruptedException e) {
-            LOG.warn("Interrupted", e);
-          }
-        } catch (IOException e) {
-          LOG.warn("Failed to update default servers", e);
-        }
-      }
-    }
-  }
-
   private class RSGroupStartupWorker extends Thread {
     private final Logger LOG = LoggerFactory.getLogger(RSGroupStartupWorker.class);
     private volatile boolean online = false;


[hbase] 20/21: HBASE-23890 Update the rsgroup section in our ref guide (#1206)

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

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

commit 420e38083f13ebf9ca056d2ee2de2192c23801c7
Author: Duo Zhang <zh...@apache.org>
AuthorDate: Sat Feb 29 08:52:19 2020 +0800

    HBASE-23890 Update the rsgroup section in our ref guide (#1206)
    
    Signed-off-by: Sean Busbey <bu...@apache.org>
---
 src/main/asciidoc/_chapters/ops_mgt.adoc   | 152 +++++++++++++++++++----------
 src/main/asciidoc/_chapters/upgrading.adoc |   4 +
 2 files changed, 105 insertions(+), 51 deletions(-)

diff --git a/src/main/asciidoc/_chapters/ops_mgt.adoc b/src/main/asciidoc/_chapters/ops_mgt.adoc
index 40b899f..4f7734f 100644
--- a/src/main/asciidoc/_chapters/ops_mgt.adoc
+++ b/src/main/asciidoc/_chapters/ops_mgt.adoc
@@ -3402,40 +3402,38 @@ full implications and have a sufficient background in managing HBase clusters.
 It was developed by Yahoo! and they run it at scale on their large grid cluster.
 See link:http://www.slideshare.net/HBaseCon/keynote-apache-hbase-at-yahoo-scale[HBase at Yahoo! Scale].
 
-RSGroups are defined and managed with shell commands. The shell drives a
-Coprocessor Endpoint whose API is marked private given this is an evolving
-feature; the Coprocessor API is not for public consumption.
+RSGroups can be defined and managed with both admin methods and shell commands.
 A server can be added to a group with hostname and port pair and tables
 can be moved to this group so that only regionservers in the same rsgroup can
-host the regions of the table. RegionServers and tables can only belong to one
-rsgroup at a time. By default, all tables and regionservers belong to the
-`default` rsgroup. System tables can also be put into a rsgroup using the regular
-APIs. A custom balancer implementation tracks assignments per rsgroup and makes
-sure to move regions to the relevant regionservers in that rsgroup. The rsgroup
-information is stored in a regular HBase table, and a zookeeper-based read-only
-cache is used at cluster bootstrap time.
+host the regions of the table. The group for a table is stored in its
+TableDescriptor, the property name is `hbase.rsgroup.name`. You can also set
+this property on a namespace, so it will cause all the tables under this
+namespace to be placed into this group. RegionServers and tables can only
+belong to one rsgroup at a time. By default, all tables and regionservers
+belong to the `default` rsgroup. System tables can also be put into a
+rsgroup using the regular APIs. A custom balancer implementation tracks
+assignments per rsgroup and makes sure to move regions to the relevant
+regionservers in that rsgroup. The rsgroup information is stored in a regular
+HBase table, and a zookeeper-based read-only cache is used at cluster bootstrap
+time.
 
 To enable, add the following to your hbase-site.xml and restart your Master:
 
 [source,xml]
 ----
  <property>
-   <name>hbase.coprocessor.master.classes</name>
-   <value>org.apache.hadoop.hbase.rsgroup.RSGroupAdminEndpoint</value>
- </property>
- <property>
-   <name>hbase.master.loadbalancer.class</name>
-   <value>org.apache.hadoop.hbase.rsgroup.RSGroupBasedLoadBalancer</value>
+   <name>hbase.balancer.rsgroup.enabled</name>
+   <value>true</value>
  </property>
 ----
 
-Then use the shell _rsgroup_ commands to create and manipulate RegionServer
-groups: e.g. to add a rsgroup and then add a server to it. To see the list of
-rsgroup commands available in the hbase shell type:
+Then use the admin/shell _rsgroup_ methods/commands to create and manipulate
+RegionServer groups: e.g. to add a rsgroup and then add a server to it.
+To see the list of rsgroup commands available in the hbase shell type:
 
 [source, bash]
 ----
- hbase(main):008:0> help ‘rsgroup’
+ hbase(main):008:0> help 'rsgroup'
  Took 0.5610 seconds
 ----
 
@@ -3449,7 +3447,8 @@ Master UI home page. If you click on a table, you can see what servers it is
 deployed across. You should see here a reflection of the grouping done with
 your shell commands. View the master log if issues.
 
-Here is example using a few of the rsgroup  commands. To add a group, do as follows:
+Here is example using a few of the rsgroup commands. To add a group, do as
+follows:
 
 [source, bash]
 ----
@@ -3461,20 +3460,10 @@ Here is example using a few of the rsgroup  commands. To add a group, do as foll
 .RegionServer Groups must be Enabled
 [NOTE]
 ====
-If you have not enabled the rsgroup Coprocessor Endpoint in the master and
-you run the any of the rsgroup shell commands, you will see an error message
-like the below:
-
-[source,java]
-----
-ERROR: org.apache.hadoop.hbase.exceptions.UnknownProtocolException: No registered master coprocessor service found for name RSGroupAdminService
-    at org.apache.hadoop.hbase.master.MasterRpcServices.execMasterService(MasterRpcServices.java:604)
-    at org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos$MasterService$2.callBlockingMethod(MasterProtos.java)
-    at org.apache.hadoop.hbase.ipc.RpcServer.call(RpcServer.java:1140)
-    at org.apache.hadoop.hbase.ipc.CallRunner.run(CallRunner.java:133)
-    at org.apache.hadoop.hbase.ipc.RpcExecutor$Handler.run(RpcExecutor.java:277)
-    at org.apache.hadoop.hbase.ipc.RpcExecutor$Handler.run(RpcExecutor.java:257)
-----
+If you have not enabled the rsgroup feature and you call any of the rsgroup
+admin methods or shell commands the call will fail with a
+`DoNotRetryIOException` with a detail message that says the rsgroup feature
+is disabled.
 ====
 
 Add a server (specified by hostname + port) to the just-made group using the
@@ -3500,23 +3489,21 @@ Servers come and go over the lifetime of a Cluster. Currently, you must
 manually align the servers referenced in rsgroups with the actual state of
 nodes in the running cluster. What we mean by this is that if you decommission
 a server, then you must update rsgroups as part of your server decommission
-process removing references.
+process removing references. Notice that, by calling `clearDeadServers`
+manually will also remove the dead servers from any rsgroups, but the problem
+is that we will lost track of the dead servers after master restarts, which
+means you still need to update the rsgroup by your own.
 
-But, there is no _remove_offline_servers_rsgroup_command you say!
-
-The way to remove a server is to move it to the `default` group. The `default`
-group is special. All rsgroups, but the `default` rsgroup, are static in that
-edits via the shell commands are persisted to the system `hbase:rsgroup` table.
-If they reference a decommissioned server, then they need to be updated to undo
-the reference.
+Please use `Admin.removeServersFromRSGroup` or shell command
+_remove_servers_rsgroup_ to remove decommission servers from rsgroup.
 
 The `default` group is not like other rsgroups in that it is dynamic. Its server
 list mirrors the current state of the cluster; i.e. if you shutdown a server that
 was part of the `default` rsgroup, and then do a _get_rsgroup_ `default` to list
-its content in the shell, the server will no longer be listed. For non-`default`
-groups, though a mode may be offline, it will persist in the non-`default` group’s
+its content in the shell, the server will no longer be listed. For non-default
+groups, though a mode may be offline, it will persist in the non-default group’s
 list of servers. But if you move the offline server from the non-default rsgroup
-to default, it  will not show in the `default` list. It will just be dropped.
+to default, it will not show in the `default` list. It will just be dropped.
 
 === Best Practice
 The authors of the rsgroup feature, the Yahoo! HBase Engineering team, have been
@@ -3526,7 +3513,7 @@ practices informed by their experience.
 ==== Isolate System Tables
 Either have a system rsgroup where all the system tables are or just leave the
 system tables in `default` rsgroup and have all user-space tables are in
-non-`default` rsgroups.
+non-default rsgroups.
 
 ==== Dead Nodes
 Yahoo! Have found it useful at their scale to keep a special rsgroup of dead or
@@ -3541,10 +3528,23 @@ Viewing the Master log will give you insight on rsgroup operation.
 If it appears stuck, restart the Master process.
 
 === Remove RegionServer Grouping
-Removing RegionServer Grouping feature from a cluster on which it was enabled involves
-more steps in addition to removing the relevant properties from `hbase-site.xml`. This is 
-to clean the RegionServer grouping related meta data so that if the feature is re-enabled 
-in the future, the old meta data will not affect the functioning of the cluster.
+Simply disable RegionServer Grouping feature is easy, just remove the
+'hbase.balancer.rsgroup.enabled' from hbase-site.xml or explicitly set it to
+false in hbase-site.xml.
+
+[source,xml]
+----
+ <property>
+   <name>hbase.balancer.rsgroup.enabled</name>
+   <value>false</value>
+ </property>
+----
+
+But if you change the 'hbase.balancer.rsgroup.enabled' to true, the old rsgroup
+configs will take effect again. So if you want to completely remove the
+RegionServer Grouping feature from a cluster, so that if the feature is
+re-enabled in the future, the old meta data will not affect the functioning of
+the cluster, there are more steps to do.
 
 - Move all tables in non-default rsgroups to `default` regionserver group
 [source,bash]
@@ -3592,6 +3592,56 @@ To enable ACL, add the following to your hbase-site.xml and restart your Master:
   <value>true</value>
 <property>
 ----
+[[migrating.rsgroup]]
+=== Migrating From Old Implementation
+The coprocessor `org.apache.hadoop.hbase.rsgroup.RSGroupAdminEndpoint` is
+deprected, but for compatible, if you want the pre 3.0.0 hbase client/shell
+to communicate with the new hbase cluster, you still need to add this
+coprocessor to master.
+
+The `hbase.rsgroup.grouploadbalancer.class` config has been deprecated, as now
+the top level load balancer will always be `RSGroupBasedLoadBalaner`, and the
+`hbase.master.loadbalancer.class` config is for configuring the balancer within
+a group. This also means you should not set `hbase.master.loadbalancer.class`
+to `RSGroupBasedLoadBalaner` any more even if rsgroup feature is enabled.
+
+And we have done some special changes for compatibility. First, if coprocessor
+`org.apache.hadoop.hbase.rsgroup.RSGroupAdminEndpoint` is specified, the
+`hbase.balancer.rsgroup.enabled` flag will be set to true automatically to
+enable rs group feature. Second, we will load
+`hbase.rsgroup.grouploadbalancer.class` prior to
+`hbase.master.loadbalancer.class`. And last, if you do not set
+`hbase.rsgroup.grouploadbalancer.class` but only set
+`hbase.master.loadbalancer.class` to `RSGroupBasedLoadBalancer`, we will load
+the default load balancer to avoid infinite nesting. This means you do not need
+to change anything when upgrading if you have already enabled rs group feature.
+
+The main difference comparing to the old implementation is that, now the
+rsgroup for a table is stored in `TableDescriptor`, instead of in
+`RSGroupInfo`, so the `getTables` method of `RSGroupInfo` has been deprecated.
+And if you use the `Admin` methods to get the `RSGroupInfo`, its `getTables`
+method will always return empty. This is because that in the old
+implementation, this method is a bit broken as you can set rsgroup on namespace
+and make all the tables under this namespace into this group but you can not
+get these tables through `RSGroupInfo.getTables`. Now you should use the two
+new methods `listTablesInRSGroup` and
+`getConfiguredNamespacesAndTablesInRSGroup` in `Admin` to get tables and
+namespaces in a rsgroup.
+
+Of course the behavior for the old RSGroupAdminEndpoint is not changed,
+we will fill the tables field of the RSGroupInfo before returning, to make it
+compatible with old hbase client/shell.
+
+When upgrading, the migration between the RSGroupInfo and TableDescriptor will
+be done automatically. It will take sometime, but it is fine to restart master
+in the middle, the migration will continue after restart. And during the
+migration, the rs group feature will still work and in most cases the region
+will not be misplaced(since this is only a one time job and will not last too
+long so we have not test it very seriously to make sure the region will not be
+misplaced always, so we use the word 'in most cases'). The implementation is a
+bit tricky, you can see the code in `RSGroupInfoManagerImpl.migrate` if
+interested.
+
 
 
 
diff --git a/src/main/asciidoc/_chapters/upgrading.adoc b/src/main/asciidoc/_chapters/upgrading.adoc
index 96972d4..3c51a62 100644
--- a/src/main/asciidoc/_chapters/upgrading.adoc
+++ b/src/main/asciidoc/_chapters/upgrading.adoc
@@ -314,6 +314,10 @@ Quitting...
 . Verify HBase contents–use the HBase shell to list tables and scan some known values.
 
 == Upgrade Paths
+[[upgrade3.0]]
+=== Upgrade from 2.x to 3.x
+The RegionServer Grouping feature has been reimplemented. See section
+<<migrating.rsgroup>> in <<ops_mgt>> for more details.
 
 [[upgrade2.2]]
 === Upgrade from 2.0 or 2.1 to 2.2+


[hbase] 19/21: HBASE-23818 Cleanup the remaining RSGroupInfo.getTables call in the code base (#1152)

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

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

commit 7f2d8231641a420b3e4381ad1ee4f8e8b7b17f12
Author: Duo Zhang <zh...@apache.org>
AuthorDate: Wed Feb 12 09:45:14 2020 +0800

    HBASE-23818 Cleanup the remaining RSGroupInfo.getTables call in the code base (#1152)
    
    Signed-off-by: stack <st...@apache.org>
    Signed-off-by: Viraj Jasani <vj...@apache.org>
---
 .../apache/hadoop/hbase/RSGroupTableAccessor.java  | 86 ----------------------
 .../hbase/rsgroup/IntegrationTestRSGroup.java      |  2 +-
 .../hbase/tmpl/master/MasterStatusTmpl.jamon       |  3 +-
 .../hadoop/hbase/tmpl/master/RSGroupListTmpl.jamon |  4 +-
 .../org/apache/hadoop/hbase/master/HMaster.java    |  3 +-
 .../hadoop/hbase/master/MasterRpcServices.java     | 10 +--
 .../hadoop/hbase/rsgroup/RSGroupInfoManager.java   |  4 +-
 .../hbase/rsgroup/RSGroupMajorCompactionTTL.java   | 50 +++++--------
 .../apache/hadoop/hbase/rsgroup/RSGroupUtil.java   | 27 +++++++
 .../resources/hbase-webapps/master/rsgroup.jsp     | 10 +--
 .../hadoop/hbase/rsgroup/TestEnableRSGroups.java   |  2 +-
 .../hbase/rsgroup/TestMigrateRSGroupInfo.java      |  2 +-
 .../rsgroup/TestRSGroupMajorCompactionTTL.java     |  2 +-
 .../hadoop/hbase/rsgroup/TestRSGroupsBase.java     |  4 +-
 .../hbase/rsgroup/TestRSGroupsOfflineMode.java     |  2 +-
 .../hadoop/hbase/rsgroup/TestRSGroupsWithACL.java  |  2 +-
 .../hadoop/hbase/client/TestRSGroupShell.java      |  4 +-
 17 files changed, 71 insertions(+), 146 deletions(-)

diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/RSGroupTableAccessor.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/RSGroupTableAccessor.java
deleted file mode 100644
index 406c41e..0000000
--- a/hbase-client/src/main/java/org/apache/hadoop/hbase/RSGroupTableAccessor.java
+++ /dev/null
@@ -1,86 +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;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-
-import org.apache.hadoop.hbase.client.Connection;
-import org.apache.hadoop.hbase.client.Get;
-import org.apache.hadoop.hbase.client.Result;
-import org.apache.hadoop.hbase.client.Scan;
-import org.apache.hadoop.hbase.client.Table;
-import org.apache.hadoop.hbase.protobuf.ProtobufUtil;
-import org.apache.hadoop.hbase.protobuf.generated.RSGroupProtos;
-import org.apache.hadoop.hbase.rsgroup.RSGroupInfo;
-import org.apache.hadoop.hbase.util.Bytes;
-import org.apache.yetus.audience.InterfaceAudience;
-
-/**
- * Read rs group information from  <code>hbase:rsgroup</code>.
- */
-@InterfaceAudience.Private
-public final class RSGroupTableAccessor {
-
-  //Assigned before user tables
-  private static final TableName RSGROUP_TABLE_NAME =
-      TableName.valueOf(NamespaceDescriptor.SYSTEM_NAMESPACE_NAME_STR, "rsgroup");
-  private static final byte[] META_FAMILY_BYTES = Bytes.toBytes("m");
-  private static final byte[] META_QUALIFIER_BYTES = Bytes.toBytes("i");
-
-  private RSGroupTableAccessor() {
-  }
-
-  public static boolean isRSGroupsEnabled(Connection connection) throws IOException {
-    return connection.getAdmin().tableExists(RSGROUP_TABLE_NAME);
-  }
-
-  public static List<RSGroupInfo> getAllRSGroupInfo(Connection connection)
-      throws IOException {
-    try (Table rsGroupTable = connection.getTable(RSGROUP_TABLE_NAME)) {
-      List<RSGroupInfo> rsGroupInfos = new ArrayList<>();
-      for (Result result : rsGroupTable.getScanner(new Scan())) {
-        RSGroupInfo rsGroupInfo = getRSGroupInfo(result);
-        if (rsGroupInfo != null) {
-          rsGroupInfos.add(rsGroupInfo);
-        }
-      }
-      return rsGroupInfos;
-    }
-  }
-
-  private static RSGroupInfo getRSGroupInfo(Result result) throws IOException {
-    byte[] rsGroupInfo = result.getValue(META_FAMILY_BYTES, META_QUALIFIER_BYTES);
-    if (rsGroupInfo == null) {
-      return null;
-    }
-    RSGroupProtos.RSGroupInfo proto =
-        RSGroupProtos.RSGroupInfo.parseFrom(rsGroupInfo);
-    return ProtobufUtil.toGroupInfo(proto);
-  }
-
-  public static RSGroupInfo getRSGroupInfo(Connection connection, byte[] rsGroupName)
-      throws IOException {
-    try (Table rsGroupTable = connection.getTable(RSGROUP_TABLE_NAME)){
-      Result result = rsGroupTable.get(new Get(rsGroupName));
-      return getRSGroupInfo(result);
-    }
-  }
-}
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 12e2438..f404402 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
@@ -46,7 +46,7 @@ public class IntegrationTestRSGroup extends TestRSGroupsBase {
       TEST_UTIL = new IntegrationTestingUtility();
       TEST_UTIL.getConfiguration().set(HConstants.HBASE_MASTER_LOADBALANCER_CLASS,
         RSGroupBasedLoadBalancer.class.getName());
-      TEST_UTIL.getConfiguration().setBoolean(RSGroupInfoManager.RS_GROUP_ENABLED, true);
+      RSGroupUtil.enableRSGroup(TEST_UTIL.getConfiguration());
       ((IntegrationTestingUtility) TEST_UTIL).initializeCluster(NUM_SLAVES_BASE);
       // set shared configs
       ADMIN = TEST_UTIL.getAdmin();
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 d282f33..b5ca6b5 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
@@ -53,6 +53,7 @@ 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.rsgroup.RSGroupUtil;
 org.apache.hadoop.hbase.security.access.PermissionStorage;
 org.apache.hadoop.hbase.security.visibility.VisibilityConstants;
 org.apache.hadoop.hbase.shaded.protobuf.generated.SnapshotProtos.SnapshotDescription;
@@ -225,7 +226,7 @@ AssignmentManager assignmentManager = master.getAssignmentManager();
           <& AssignmentManagerStatusTmpl; assignmentManager=master.getAssignmentManager()&>
         </%if>
         <%if !master.isInMaintenanceMode() %>
-          <%if master.getConfiguration().getBoolean(RSGroupInfoManager.RS_GROUP_ENABLED, false) &&
+          <%if RSGroupUtil.isRSGroupEnabled(master.getConfiguration()) &&
             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 765eab9..c542c68 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
@@ -33,10 +33,10 @@ ServerManager serverManager;
     org.apache.hadoop.hbase.RegionMetrics;
     org.apache.hadoop.hbase.ServerMetrics;
     org.apache.hadoop.hbase.Size;
-    org.apache.hadoop.hbase.RSGroupTableAccessor;
     org.apache.hadoop.hbase.master.ServerManager;
     org.apache.hadoop.hbase.net.Address;
     org.apache.hadoop.hbase.rsgroup.RSGroupInfo;
+    org.apache.hadoop.hbase.rsgroup.RSGroupUtil;
     org.apache.hadoop.util.StringUtils;
     org.apache.hadoop.util.StringUtils.TraditionalBinaryPrefix;
 </%import>
@@ -128,7 +128,7 @@ if (master.getServerManager() != null) {
           deadServers++;
         }
       }
-      tables = rsGroupInfo.getTables().size();
+      tables = RSGroupUtil.listTablesInRSGroup(master, rsGroupInfo.getName()).size();
       totalTables += tables;
       double avgLoad = onlineServers == 0 ? 0 :
             (double)numRegionsOnline / (double)onlineServers;
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 a5e6bf6..a4aef98 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
@@ -189,6 +189,7 @@ 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.rsgroup.RSGroupUtil;
 import org.apache.hadoop.hbase.security.AccessDeniedException;
 import org.apache.hadoop.hbase.security.SecurityConstants;
 import org.apache.hadoop.hbase.security.UserProvider;
@@ -807,7 +808,7 @@ public class HMaster extends HRegionServer implements MasterServices {
     if (cpClasses != null) {
       for (String cpClass : cpClasses) {
         if (RSGroupAdminEndpoint.class.getName().equals(cpClass)) {
-          conf.setBoolean(RSGroupInfoManager.RS_GROUP_ENABLED, true);
+          RSGroupUtil.enableRSGroup(conf);
           break;
         }
       }
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 3ea0420..e2b543a 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
@@ -3177,14 +3177,8 @@ public class MasterRpcServices extends RSRpcServices implements
       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()));
-        }
-      }
+      RSGroupUtil.listTablesInRSGroup(master, groupName).stream()
+        .map(ProtobufUtil::toProtoTableName).forEach(builder::addTableName);
       if (master.getMasterCoprocessorHost() != null) {
         master.getMasterCoprocessorHost().postListTablesInRSGroup(groupName);
       }
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 c1b03a6..cd1ff30 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,8 +32,6 @@ import org.apache.yetus.audience.InterfaceAudience;
 @InterfaceAudience.Private
 public interface RSGroupInfoManager {
 
-  public static final String RS_GROUP_ENABLED = "hbase.balancer.rsgroup.enabled";
-
   void start();
 
   /**
@@ -84,7 +82,7 @@ public interface RSGroupInfoManager {
   RSGroupInfo getRSGroupForTable(TableName tableName) throws IOException;
 
   static RSGroupInfoManager create(MasterServices master) throws IOException {
-    if (master.getConfiguration().getBoolean(RS_GROUP_ENABLED, false)) {
+    if (RSGroupUtil.isRSGroupEnabled(master.getConfiguration())) {
       return RSGroupInfoManagerImpl.getInstance(master);
     } else {
       return new DisabledRSGroupInfoManager(master.getServerManager());
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 fa07108..172561d 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
@@ -41,8 +41,8 @@ import org.apache.hbase.thirdparty.org.apache.commons.cli.Options;
 import org.apache.hbase.thirdparty.org.apache.commons.cli.ParseException;
 
 /**
- * This script takes an rsgroup as argument and compacts part/all of regions of that table
- * based on the table's TTL.
+ * This script takes an rsgroup as argument and compacts part/all of regions of that table based on
+ * the table's TTL.
  */
 @InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.TOOLS)
 public class RSGroupMajorCompactionTTL extends MajorCompactorTTL {
@@ -55,24 +55,20 @@ public class RSGroupMajorCompactionTTL extends MajorCompactorTTL {
   }
 
   public int compactTTLRegionsOnGroup(Configuration conf, String rsgroup, int concurrency,
-      long sleep, int numServers, int numRegions, boolean dryRun, boolean skipWait)
-      throws Exception {
-
-    Connection conn = ConnectionFactory.createConnection(conf);
-    Admin admin = conn.getAdmin();
-
-    RSGroupInfo rsGroupInfo = admin.getRSGroup(rsgroup);
-    if (rsGroupInfo == null) {
-      LOG.error("Invalid rsgroup specified: " + rsgroup);
-      throw new IllegalArgumentException("Invalid rsgroup specified: " + rsgroup);
-    }
-
-    for (TableName tableName : rsGroupInfo.getTables()) {
-      int status = compactRegionsTTLOnTable(conf, tableName.getNameAsString(), concurrency, sleep,
+    long sleep, int numServers, int numRegions, boolean dryRun, boolean skipWait) throws Exception {
+    try (Connection conn = ConnectionFactory.createConnection(conf);
+      Admin admin = conn.getAdmin()) {
+      if (admin.getRSGroup(rsgroup) == null) {
+        LOG.error("Invalid rsgroup specified: " + rsgroup);
+        throw new IllegalArgumentException("Invalid rsgroup specified: " + rsgroup);
+      }
+      for (TableName tableName : admin.listTablesInRSGroup(rsgroup)) {
+        int status = compactRegionsTTLOnTable(conf, tableName.getNameAsString(), concurrency, sleep,
           numServers, numRegions, dryRun, skipWait);
-      if (status != 0) {
-        LOG.error("Failed to compact table: " + tableName);
-        return status;
+        if (status != 0) {
+          LOG.error("Failed to compact table: " + tableName);
+          return status;
+        }
       }
     }
     return 0;
@@ -81,13 +77,8 @@ public class RSGroupMajorCompactionTTL extends MajorCompactorTTL {
   protected Options getOptions() {
     Options options = getCommonOptions();
 
-    options.addOption(
-        Option.builder("rsgroup")
-            .required()
-            .desc("Tables of rsgroup to be compacted")
-            .hasArg()
-            .build()
-    );
+    options.addOption(Option.builder("rsgroup").required().desc("Tables of rsgroup to be compacted")
+      .hasArg().build());
 
     return options;
   }
@@ -101,9 +92,8 @@ public class RSGroupMajorCompactionTTL extends MajorCompactorTTL {
     try {
       commandLine = cmdLineParser.parse(options, args);
     } catch (ParseException parseException) {
-      System.out.println(
-          "ERROR: Unable to parse command-line arguments " + Arrays.toString(args) + " due to: "
-              + parseException);
+      System.out.println("ERROR: Unable to parse command-line arguments " + Arrays.toString(args) +
+        " due to: " + parseException);
       printUsage(options);
       return -1;
     }
@@ -123,7 +113,7 @@ public class RSGroupMajorCompactionTTL extends MajorCompactorTTL {
     Configuration conf = getConf();
 
     return compactTTLRegionsOnGroup(conf, rsgroup, concurrency, sleep, numServers, numRegions,
-        dryRun, skipWait);
+      dryRun, skipWait);
   }
 
   public static void main(String[] args) throws Exception {
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 8aef0a1..117c2cc 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
@@ -11,9 +11,12 @@
 package org.apache.hadoop.hbase.rsgroup;
 
 import java.io.IOException;
+import java.util.ArrayList;
 import java.util.Collection;
+import java.util.List;
 import java.util.Optional;
 import java.util.function.Predicate;
+import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.hbase.NamespaceDescriptor;
 import org.apache.hadoop.hbase.TableName;
 import org.apache.hadoop.hbase.client.TableDescriptor;
@@ -31,9 +34,33 @@ public final class RSGroupUtil {
 
   private static final Logger LOG = LoggerFactory.getLogger(RSGroupUtil.class);
 
+  public static final String RS_GROUP_ENABLED = "hbase.balancer.rsgroup.enabled";
+
   private RSGroupUtil() {
   }
 
+  public static boolean isRSGroupEnabled(Configuration conf) {
+    return conf.getBoolean(RS_GROUP_ENABLED, false);
+  }
+
+  public static void enableRSGroup(Configuration conf) {
+    conf.setBoolean(RS_GROUP_ENABLED, true);
+  }
+
+  public static List<TableName> listTablesInRSGroup(MasterServices master, String groupName)
+    throws IOException {
+    List<TableName> tables = new ArrayList<>();
+    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)) {
+        tables.add(td.getTableName());
+      }
+    }
+    return tables;
+  }
+
   /**
    * 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.
diff --git a/hbase-server/src/main/resources/hbase-webapps/master/rsgroup.jsp b/hbase-server/src/main/resources/hbase-webapps/master/rsgroup.jsp
index 8626034..429ebd3 100644
--- a/hbase-server/src/main/resources/hbase-webapps/master/rsgroup.jsp
+++ b/hbase-server/src/main/resources/hbase-webapps/master/rsgroup.jsp
@@ -27,7 +27,6 @@
   import="java.util.stream.Stream"
   import="java.util.stream.Collectors"
   import="org.apache.hadoop.hbase.HTableDescriptor"
-  import="org.apache.hadoop.hbase.RSGroupTableAccessor"
   import="org.apache.hadoop.hbase.ServerName"
   import="org.apache.hadoop.hbase.TableName"
   import="org.apache.hadoop.hbase.client.Admin"
@@ -38,6 +37,7 @@
   import="org.apache.hadoop.hbase.master.RegionState"
   import="org.apache.hadoop.hbase.net.Address"
   import="org.apache.hadoop.hbase.rsgroup.RSGroupInfo"
+  import="org.apache.hadoop.hbase.rsgroup.RSGroupUtil"
   import="org.apache.hadoop.hbase.util.Bytes"
   import="org.apache.hadoop.hbase.util.VersionInfo"
   import="org.apache.hadoop.util.StringUtils.TraditionalBinaryPrefix"%>
@@ -57,7 +57,7 @@
   RSGroupInfo rsGroupInfo = null;
   final String ZEROKB = "0 KB";
   final String ZEROMB = "0 MB";
-  if (!RSGroupTableAccessor.isRSGroupsEnabled(master.getConnection())) {
+  if (!RSGroupUtil.isRSGroupEnabled(master.getConfiguration())) {
 %>
   <div class="row inner_header">
     <div class="page-header">
@@ -67,8 +67,8 @@
   <jsp:include page="redirect.jsp" />
 <%
   } else if (rsGroupName == null || rsGroupName.isEmpty() ||
-      (rsGroupInfo = RSGroupTableAccessor.getRSGroupInfo(
-          master.getConnection(), Bytes.toBytes(rsGroupName))) == null) {
+      (rsGroupInfo = master.getRSGroupInfoManager().getRSGroup(
+          rsGroupName)) == null) {
 %>
   <div class="row inner_header">
     <div class="page-header">
@@ -81,7 +81,7 @@
     List<Address> rsGroupServers = new ArrayList<>();
     List<TableName> rsGroupTables = new ArrayList<>();
     rsGroupServers.addAll(rsGroupInfo.getServers());
-    rsGroupTables.addAll(rsGroupInfo.getTables());
+    rsGroupTables.addAll(RSGroupUtil.listTablesInRSGroup(master, rsGroupInfo.getName()));
     Collections.sort(rsGroupServers);
     rsGroupTables.sort((o1, o2) -> {
       int compare = Bytes.compareTo(o1.getNamespace(), o2.getNamespace());
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 72dacb5..6407799 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
@@ -33,7 +33,7 @@ public class TestEnableRSGroups extends EnableRSGroupsTestBase {
 
   @Override
   protected void enableRSGroup(Configuration conf) {
-    conf.setBoolean(RSGroupInfoManager.RS_GROUP_ENABLED, true);
+    conf.setBoolean(RSGroupUtil.RS_GROUP_ENABLED, true);
   }
 
 }
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 3ac6aba..4a492f8 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
@@ -72,7 +72,7 @@ public class TestMigrateRSGroupInfo extends TestRSGroupsBase {
     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().setBoolean(RSGroupUtil.RS_GROUP_ENABLED, false);
     TEST_UTIL.getConfiguration().set(CoprocessorHost.MASTER_COPROCESSOR_CONF_KEY,
       RSGroupAdminEndpoint.class.getName());
     setUpTestBeforeClass();
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 6725cf0..454c9ed 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
@@ -53,7 +53,7 @@ public class TestRSGroupMajorCompactionTTL extends TestMajorCompactorTTL {
   public void setUp() throws Exception {
     utility = new HBaseTestingUtility();
     Configuration conf = utility.getConfiguration();
-    conf.setBoolean(RSGroupInfoManager.RS_GROUP_ENABLED, true);
+    RSGroupUtil.enableRSGroup(conf);
     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/TestRSGroupsBase.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupsBase.java
index b921508..fb0fde5 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
@@ -94,8 +94,8 @@ public abstract class TestRSGroupsBase {
   public static void setUpTestBeforeClass() throws Exception {
     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(RSGroupUtil.RS_GROUP_ENABLED) == null) {
+      RSGroupUtil.enableRSGroup(conf);
     }
     if (conf.get(CoprocessorHost.MASTER_COPROCESSOR_CONF_KEY) != null) {
       conf.set(CoprocessorHost.MASTER_COPROCESSOR_CONF_KEY,
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 c4a2031..ae8177d 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
@@ -77,7 +77,7 @@ public class TestRSGroupsOfflineMode extends TestRSGroupsBase {
   @BeforeClass
   public static void setUp() throws Exception {
     TEST_UTIL = new HBaseTestingUtility();
-    TEST_UTIL.getConfiguration().setBoolean(RSGroupInfoManager.RS_GROUP_ENABLED, true);
+    RSGroupUtil.enableRSGroup(TEST_UTIL.getConfiguration());
     TEST_UTIL.getConfiguration().set(ServerManager.WAIT_ON_REGIONSERVERS_MINTOSTART, "1");
     StartMiniClusterOption option =
       StartMiniClusterOption.builder().numMasters(2).numRegionServers(3).numDataNodes(3).build();
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 12ad7f5..3378145 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
@@ -107,7 +107,7 @@ public class TestRSGroupsWithACL extends SecureTestUtil {
     // Verify enableSecurity sets up what we require
     verifyConfiguration(conf);
     // Enable rsgroup
-    conf.setBoolean(RSGroupInfoManager.RS_GROUP_ENABLED, true);
+    RSGroupUtil.enableRSGroup(conf);
 
     TEST_UTIL.startMiniCluster();
     // Wait for the ACL table to become available
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 17e8d5d..f26f9f5 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,7 +19,7 @@ package org.apache.hadoop.hbase.client;
 
 import java.io.IOException;
 import org.apache.hadoop.hbase.HBaseClassTestRule;
-import org.apache.hadoop.hbase.rsgroup.RSGroupInfoManager;
+import org.apache.hadoop.hbase.rsgroup.RSGroupUtil;
 import org.apache.hadoop.hbase.testclassification.ClientTests;
 import org.apache.hadoop.hbase.testclassification.LargeTests;
 import org.jruby.embed.PathType;
@@ -40,7 +40,7 @@ public class TestRSGroupShell extends AbstractTestShell {
     setUpConfig();
 
     // enable rs group
-    TEST_UTIL.getConfiguration().setBoolean(RSGroupInfoManager.RS_GROUP_ENABLED, true);
+    RSGroupUtil.enableRSGroup(TEST_UTIL.getConfiguration());
 
     TEST_UTIL.startMiniCluster(3);