You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hbase.apache.org by ap...@apache.org on 2017/07/19 00:44:49 UTC

[07/21] hbase git commit: HBASE-15631 Backport Regionserver Groups (HBASE-6721) to branch-1

http://git-wip-us.apache.org/repos/asf/hbase/blob/a0a7f6f4/hbase-rsgroup/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupAdminEndpoint.java
----------------------------------------------------------------------
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
new file mode 100644
index 0000000..00cd6b0
--- /dev/null
+++ b/hbase-rsgroup/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupAdminEndpoint.java
@@ -0,0 +1,955 @@
+/**
+ * Copyright The Apache Software Foundation
+ *
+ * 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.common.collect.Sets;
+import com.google.common.net.HostAndPort;
+import com.google.protobuf.RpcCallback;
+import com.google.protobuf.RpcController;
+import com.google.protobuf.Service;
+
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.hadoop.hbase.Coprocessor;
+import org.apache.hadoop.hbase.CoprocessorEnvironment;
+import org.apache.hadoop.hbase.HColumnDescriptor;
+import org.apache.hadoop.hbase.HConstants;
+import org.apache.hadoop.hbase.HRegionInfo;
+import org.apache.hadoop.hbase.HTableDescriptor;
+import org.apache.hadoop.hbase.NamespaceDescriptor;
+import org.apache.hadoop.hbase.ProcedureInfo;
+import org.apache.hadoop.hbase.ServerName;
+import org.apache.hadoop.hbase.TableName;
+import org.apache.hadoop.hbase.client.Admin.MasterSwitchType;
+import org.apache.hadoop.hbase.constraint.ConstraintException;
+import org.apache.hadoop.hbase.coprocessor.CoprocessorService;
+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.master.RegionPlan;
+import org.apache.hadoop.hbase.master.procedure.MasterProcedureEnv;
+import org.apache.hadoop.hbase.procedure2.ProcedureExecutor;
+import org.apache.hadoop.hbase.protobuf.ProtobufUtil;
+import org.apache.hadoop.hbase.protobuf.ResponseConverter;
+import org.apache.hadoop.hbase.protobuf.generated.HBaseProtos;
+import org.apache.hadoop.hbase.protobuf.generated.QuotaProtos.Quotas;
+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.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.SnapshotProtos.SnapshotDescription;
+
+
+public class RSGroupAdminEndpoint extends RSGroupAdminService
+    implements CoprocessorService, Coprocessor, MasterObserver {
+
+  private MasterServices master = null;
+
+  private static RSGroupInfoManagerImpl groupInfoManager;
+  private RSGroupAdminServer groupAdminServer;
+
+  @Override
+  public void start(CoprocessorEnvironment env) throws IOException {
+    MasterCoprocessorEnvironment menv = (MasterCoprocessorEnvironment)env;
+    master = menv.getMasterServices();
+    groupInfoManager = new RSGroupInfoManagerImpl(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 is not a GroupableBalancer");
+    }
+  }
+
+  @Override
+  public void stop(CoprocessorEnvironment env) throws IOException {
+  }
+
+  @Override
+  public Service getService() {
+    return this;
+  }
+
+  public RSGroupInfoManager getGroupInfoManager() {
+    return groupInfoManager;
+  }
+
+  @Override
+  public void getRSGroupInfo(RpcController controller,
+                           GetRSGroupInfoRequest request,
+                           RpcCallback<GetRSGroupInfoResponse> done) {
+    GetRSGroupInfoResponse response = null;
+    try {
+      GetRSGroupInfoResponse.Builder builder =
+          GetRSGroupInfoResponse.newBuilder();
+      RSGroupInfo RSGroupInfo = groupAdminServer.getRSGroupInfo(request.getRSGroupName());
+      if(RSGroupInfo != null) {
+        builder.setRSGroupInfo(ProtobufUtil.toProtoGroupInfo(RSGroupInfo));
+      }
+      response = builder.build();
+    } catch (IOException e) {
+      ResponseConverter.setControllerException(controller, e);
+    }
+    done.run(response);
+  }
+
+  @Override
+  public void getRSGroupInfoOfTable(RpcController controller,
+                                  GetRSGroupInfoOfTableRequest request,
+                                  RpcCallback<GetRSGroupInfoOfTableResponse> done) {
+    GetRSGroupInfoOfTableResponse response = null;
+    try {
+      GetRSGroupInfoOfTableResponse.Builder builder =
+          GetRSGroupInfoOfTableResponse.newBuilder();
+      TableName tableName = ProtobufUtil.toTableName(request.getTableName());
+      RSGroupInfo RSGroupInfo = groupAdminServer.getRSGroupInfoOfTable(tableName);
+      if (RSGroupInfo == null) {
+        response = builder.build();
+      } else {
+        response = builder.setRSGroupInfo(ProtobufUtil.toProtoGroupInfo(RSGroupInfo)).build();
+      }
+    } catch (IOException e) {
+      ResponseConverter.setControllerException(controller, e);
+    }
+    done.run(response);
+  }
+
+  @Override
+  public void moveServers(RpcController controller,
+                          MoveServersRequest request,
+                          RpcCallback<MoveServersResponse> done) {
+    RSGroupAdminProtos.MoveServersResponse response = null;
+    try {
+      RSGroupAdminProtos.MoveServersResponse.Builder builder =
+          RSGroupAdminProtos.MoveServersResponse.newBuilder();
+      Set<HostAndPort> hostPorts = Sets.newHashSet();
+      for(HBaseProtos.ServerName el: request.getServersList()) {
+        hostPorts.add(HostAndPort.fromParts(el.getHostName(), el.getPort()));
+      }
+      groupAdminServer.moveServers(hostPorts, request.getTargetGroup());
+      response = builder.build();
+    } catch (IOException e) {
+      ResponseConverter.setControllerException(controller, e);
+    }
+    done.run(response);
+  }
+
+  @Override
+  public void moveTables(RpcController controller,
+                         MoveTablesRequest request,
+                         RpcCallback<MoveTablesResponse> done) {
+    MoveTablesResponse response = null;
+    try {
+      MoveTablesResponse.Builder builder =
+          MoveTablesResponse.newBuilder();
+      Set<TableName> tables = new HashSet<TableName>(request.getTableNameList().size());
+      for(HBaseProtos.TableName tableName: request.getTableNameList()) {
+        tables.add(ProtobufUtil.toTableName(tableName));
+      }
+      groupAdminServer.moveTables(tables, request.getTargetGroup());
+      response = builder.build();
+    } catch (IOException e) {
+      ResponseConverter.setControllerException(controller, e);
+    }
+    done.run(response);
+  }
+
+  @Override
+  public void addRSGroup(RpcController controller,
+                       AddRSGroupRequest request,
+                       RpcCallback<AddRSGroupResponse> done) {
+    AddRSGroupResponse response = null;
+    try {
+      AddRSGroupResponse.Builder builder =
+          AddRSGroupResponse.newBuilder();
+      groupAdminServer.addRSGroup(request.getRSGroupName());
+      response = builder.build();
+    } catch (IOException e) {
+      ResponseConverter.setControllerException(controller, e);
+    }
+    done.run(response);
+  }
+
+  @Override
+  public void removeRSGroup(RpcController controller,
+                          RemoveRSGroupRequest request,
+                          RpcCallback<RemoveRSGroupResponse> done) {
+    RemoveRSGroupResponse response = null;
+    try {
+      RemoveRSGroupResponse.Builder builder =
+          RemoveRSGroupResponse.newBuilder();
+      groupAdminServer.removeRSGroup(request.getRSGroupName());
+      response = builder.build();
+    } catch (IOException e) {
+      ResponseConverter.setControllerException(controller, e);
+    }
+    done.run(response);
+  }
+
+  @Override
+  public void balanceRSGroup(RpcController controller,
+                           BalanceRSGroupRequest request,
+                           RpcCallback<BalanceRSGroupResponse> done) {
+    BalanceRSGroupResponse.Builder builder = BalanceRSGroupResponse.newBuilder();
+    try {
+      builder.setBalanceRan(groupAdminServer.balanceRSGroup(request.getRSGroupName()));
+    } catch (IOException e) {
+      ResponseConverter.setControllerException(controller, e);
+      builder.setBalanceRan(false);
+    }
+    done.run(builder.build());
+  }
+
+  @Override
+  public void listRSGroupInfos(RpcController controller,
+                             ListRSGroupInfosRequest request,
+                             RpcCallback<ListRSGroupInfosResponse> done) {
+    ListRSGroupInfosResponse response = null;
+    try {
+      ListRSGroupInfosResponse.Builder builder =
+          ListRSGroupInfosResponse.newBuilder();
+      for(RSGroupInfo RSGroupInfo : groupAdminServer.listRSGroups()) {
+        builder.addRSGroupInfo(ProtobufUtil.toProtoGroupInfo(RSGroupInfo));
+      }
+      response = builder.build();
+    } catch (IOException e) {
+      ResponseConverter.setControllerException(controller, e);
+    }
+    done.run(response);
+  }
+
+  @Override
+  public void getRSGroupInfoOfServer(RpcController controller,
+                                   GetRSGroupInfoOfServerRequest request,
+                                   RpcCallback<GetRSGroupInfoOfServerResponse> done) {
+    GetRSGroupInfoOfServerResponse.Builder builder = GetRSGroupInfoOfServerResponse.newBuilder();
+    try {
+      HostAndPort hp =
+          HostAndPort.fromParts(request.getServer().getHostName(), request.getServer().getPort());
+      RSGroupInfo RSGroupInfo = groupAdminServer.getRSGroupOfServer(hp);
+      if (RSGroupInfo != null) {
+        builder.setRSGroupInfo(ProtobufUtil.toProtoGroupInfo(RSGroupInfo));
+      }
+    } catch (IOException e) {
+      ResponseConverter.setControllerException(controller, e);
+    }
+    done.run(builder.build());
+  }
+
+  @Override
+  public void preCreateTable(ObserverContext<MasterCoprocessorEnvironment> ctx,
+      HTableDescriptor desc, HRegionInfo[] regions) throws IOException {
+    groupAdminServer.prepareRSGroupForTable(desc);
+  }
+
+  @Override
+  public void postDeleteTable(ObserverContext<MasterCoprocessorEnvironment> ctx,
+                              TableName tableName) throws IOException {
+    groupAdminServer.cleanupRSGroupForTable(tableName);
+  }
+
+  //unused cp hooks
+
+  @Override
+  public void postCreateTable(ObserverContext<MasterCoprocessorEnvironment> ctx,
+                              HTableDescriptor desc,
+                              HRegionInfo[] regions) throws IOException {
+
+  }
+
+  @Override
+  public void preCreateTableHandler(ObserverContext<MasterCoprocessorEnvironment> ctx,
+                                    HTableDescriptor desc,
+                                    HRegionInfo[] regions) throws IOException {
+
+  }
+
+  @Override
+  public void postCreateTableHandler(ObserverContext<MasterCoprocessorEnvironment> ctx,
+                                     HTableDescriptor desc,
+                                     HRegionInfo[] regions) throws IOException {
+
+  }
+
+  @Override
+  public void preDeleteTable(ObserverContext<MasterCoprocessorEnvironment> ctx,
+                             TableName tableName) throws IOException {
+
+  }
+
+  @Override
+  public void preDeleteTableHandler(ObserverContext<MasterCoprocessorEnvironment> ctx,
+                                    TableName tableName) throws IOException {
+
+  }
+
+  @Override
+  public void postDeleteTableHandler(ObserverContext<MasterCoprocessorEnvironment> ctx,
+                                     TableName tableName) throws IOException {
+
+  }
+
+  @Override
+  public void preTruncateTable(ObserverContext<MasterCoprocessorEnvironment> ctx,
+                               TableName tableName) throws IOException {
+
+  }
+
+  @Override
+  public void postTruncateTable(ObserverContext<MasterCoprocessorEnvironment> ctx,
+                                TableName tableName) throws IOException {
+
+  }
+
+  @Override
+  public void preTruncateTableHandler(ObserverContext<MasterCoprocessorEnvironment> ctx,
+                                      TableName tableName) throws IOException {
+
+  }
+
+  @Override
+  public void postTruncateTableHandler(ObserverContext<MasterCoprocessorEnvironment> ctx,
+                                       TableName tableName) throws IOException {
+
+  }
+
+  @Override
+  public void preModifyTable(ObserverContext<MasterCoprocessorEnvironment> ctx, TableName tableName,
+                             HTableDescriptor htd) throws IOException {
+
+  }
+
+  @Override
+  public void postModifyTable(ObserverContext<MasterCoprocessorEnvironment> ctx,
+                              TableName tableName, HTableDescriptor htd) throws IOException {
+
+  }
+
+  @Override
+  public void preModifyTableHandler(ObserverContext<MasterCoprocessorEnvironment> ctx,
+                                    TableName tableName, HTableDescriptor htd) throws IOException {
+
+  }
+
+  @Override
+  public void postModifyTableHandler(ObserverContext<MasterCoprocessorEnvironment> ctx,
+                                     TableName tableName, HTableDescriptor htd) throws IOException {
+
+  }
+
+  @Override
+  public void preAddColumn(ObserverContext<MasterCoprocessorEnvironment> ctx,
+                           TableName tableName, HColumnDescriptor column) throws IOException {
+
+  }
+
+  @Override
+  public void postAddColumn(ObserverContext<MasterCoprocessorEnvironment> ctx, TableName tableName,
+                            HColumnDescriptor column) throws IOException {
+
+  }
+
+  @Override
+  public void preAddColumnHandler(ObserverContext<MasterCoprocessorEnvironment> ctx,
+                                  TableName tableName, HColumnDescriptor column)
+      throws IOException {
+
+  }
+
+  @Override
+  public void postAddColumnHandler(ObserverContext<MasterCoprocessorEnvironment> ctx,
+                                   TableName tableName, HColumnDescriptor column)
+      throws IOException {
+
+  }
+
+  @Override
+  public void preModifyColumn(ObserverContext<MasterCoprocessorEnvironment> ctx,
+                              TableName tableName, HColumnDescriptor descriptor)
+      throws IOException {
+
+  }
+
+  @Override
+  public void postModifyColumn(ObserverContext<MasterCoprocessorEnvironment> ctx,
+                               TableName tableName, HColumnDescriptor descriptor)
+      throws IOException {
+
+  }
+
+  @Override
+  public void preModifyColumnHandler(ObserverContext<MasterCoprocessorEnvironment> ctx,
+                                     TableName tableName, HColumnDescriptor descriptor)
+      throws IOException {
+
+  }
+
+  @Override
+  public void postModifyColumnHandler(ObserverContext<MasterCoprocessorEnvironment> ctx,
+                                      TableName tableName, HColumnDescriptor descriptor)
+      throws IOException {
+
+  }
+
+  @Override
+  public void preDeleteColumn(ObserverContext<MasterCoprocessorEnvironment> ctx, TableName
+      tableName, byte[] c) throws IOException {
+
+  }
+
+  @Override
+  public void postDeleteColumn(ObserverContext<MasterCoprocessorEnvironment> ctx, TableName
+      tableName, byte[] c) throws IOException {
+
+  }
+
+  @Override
+  public void preDeleteColumnHandler(ObserverContext<MasterCoprocessorEnvironment> ctx, TableName
+      tableName, byte[] c) throws IOException {
+
+  }
+
+  @Override
+  public void postDeleteColumnHandler(ObserverContext<MasterCoprocessorEnvironment> ctx,
+                                      TableName tableName, byte[] c) throws IOException {
+
+  }
+
+  @Override
+  public void preEnableTable(ObserverContext<MasterCoprocessorEnvironment> ctx,
+                             TableName tableName) throws IOException {
+
+  }
+
+  @Override
+  public void postEnableTable(ObserverContext<MasterCoprocessorEnvironment> ctx,
+                              TableName tableName) throws IOException {
+
+  }
+
+  @Override
+  public void preEnableTableHandler(ObserverContext<MasterCoprocessorEnvironment> ctx,
+                                    TableName tableName) throws IOException {
+
+  }
+
+  @Override
+  public void postEnableTableHandler(ObserverContext<MasterCoprocessorEnvironment> ctx,
+                                     TableName tableName) throws IOException {
+
+  }
+
+  @Override
+  public void preDisableTable(ObserverContext<MasterCoprocessorEnvironment> ctx,
+                              TableName tableName) throws IOException {
+
+  }
+
+  @Override
+  public void postDisableTable(ObserverContext<MasterCoprocessorEnvironment> ctx,
+                               TableName tableName) throws IOException {
+
+  }
+
+  @Override
+  public void preDisableTableHandler(ObserverContext<MasterCoprocessorEnvironment> ctx,
+                                     TableName tableName) throws IOException {
+
+  }
+
+  @Override
+  public void postDisableTableHandler(ObserverContext<MasterCoprocessorEnvironment> ctx,
+                                      TableName tableName) throws IOException {
+
+  }
+
+  @Override
+  public void preMove(ObserverContext<MasterCoprocessorEnvironment> ctx, HRegionInfo region,
+                      ServerName srcServer, ServerName destServer) throws IOException {
+
+  }
+
+  @Override
+  public void postMove(ObserverContext<MasterCoprocessorEnvironment> ctx, HRegionInfo region,
+                       ServerName srcServer, ServerName destServer) throws IOException {
+
+  }
+
+  @Override
+  public void preAbortProcedure(ObserverContext<MasterCoprocessorEnvironment> ctx,
+                                ProcedureExecutor<MasterProcedureEnv> procEnv,
+                                long procId) throws IOException {
+
+  }
+
+  @Override
+  public void postAbortProcedure(ObserverContext<MasterCoprocessorEnvironment> ctx)
+      throws IOException {
+
+  }
+
+  @Override
+  public void preListProcedures(ObserverContext<MasterCoprocessorEnvironment> ctx) throws
+      IOException {
+
+  }
+
+  @Override
+  public void postListProcedures(ObserverContext<MasterCoprocessorEnvironment> ctx,
+                                 List<ProcedureInfo> procInfoList) throws IOException {
+
+  }
+
+  @Override
+  public void preAssign(ObserverContext<MasterCoprocessorEnvironment> ctx,
+                        HRegionInfo regionInfo) throws IOException {
+
+  }
+
+  @Override
+  public void postAssign(ObserverContext<MasterCoprocessorEnvironment> ctx, HRegionInfo
+      regionInfo) throws IOException {
+
+  }
+
+  @Override
+  public void preUnassign(ObserverContext<MasterCoprocessorEnvironment> ctx, HRegionInfo
+      regionInfo, boolean force) throws IOException {
+
+  }
+
+  @Override
+  public void postUnassign(ObserverContext<MasterCoprocessorEnvironment> ctx,
+                           HRegionInfo regionInfo, boolean force) throws IOException {
+
+  }
+
+  @Override
+  public void preRegionOffline(ObserverContext<MasterCoprocessorEnvironment> ctx,
+                               HRegionInfo regionInfo) throws IOException {
+
+  }
+
+  @Override
+  public void postRegionOffline(ObserverContext<MasterCoprocessorEnvironment> ctx,
+                                HRegionInfo regionInfo) throws IOException {
+
+  }
+
+  @Override
+  public void preBalance(ObserverContext<MasterCoprocessorEnvironment> ctx) throws IOException {
+
+  }
+
+  @Override
+  public void postBalance(ObserverContext<MasterCoprocessorEnvironment> ctx,
+                          List<RegionPlan> plans) throws IOException {
+
+  }
+
+  @Override
+  public boolean preSetSplitOrMergeEnabled(ObserverContext<MasterCoprocessorEnvironment> ctx,
+                                           boolean newValue, MasterSwitchType switchType) throws
+      IOException {
+    return false;
+  }
+
+  @Override
+  public void postSetSplitOrMergeEnabled(ObserverContext<MasterCoprocessorEnvironment> ctx,
+                                         boolean newValue, MasterSwitchType switchType) throws
+      IOException {
+
+  }
+
+  @Override
+  public boolean preBalanceSwitch(ObserverContext<MasterCoprocessorEnvironment> ctx,
+                                  boolean newValue) throws IOException {
+    return false;
+  }
+
+  @Override
+  public void postBalanceSwitch(ObserverContext<MasterCoprocessorEnvironment> ctx, boolean
+      oldValue, boolean newValue) throws IOException {
+
+  }
+
+  @Override
+  public void preShutdown(ObserverContext<MasterCoprocessorEnvironment> ctx) throws IOException {
+
+  }
+
+  @Override
+  public void preStopMaster(ObserverContext<MasterCoprocessorEnvironment> ctx) throws IOException {
+
+  }
+
+  @Override
+  public void postStartMaster(ObserverContext<MasterCoprocessorEnvironment> ctx)
+      throws IOException {
+
+  }
+
+  @Override
+  public void preMasterInitialization(ObserverContext<MasterCoprocessorEnvironment> ctx)
+      throws IOException {
+
+  }
+
+  @Override
+  public void preSnapshot(ObserverContext<MasterCoprocessorEnvironment> ctx,
+                          SnapshotDescription snapshot,
+                          HTableDescriptor hTableDescriptor) throws IOException {
+
+
+  }
+
+  @Override
+  public void postSnapshot(ObserverContext<MasterCoprocessorEnvironment> ctx, SnapshotDescription
+      snapshot, HTableDescriptor hTableDescriptor) throws IOException {
+
+  }
+
+  @Override
+  public void preListSnapshot(ObserverContext<MasterCoprocessorEnvironment> ctx,
+                              SnapshotDescription snapshot) throws IOException {
+
+  }
+
+  @Override
+  public void postListSnapshot(ObserverContext<MasterCoprocessorEnvironment> ctx,
+                               SnapshotDescription snapshot) throws IOException {
+
+  }
+
+  @Override
+  public void preCloneSnapshot(ObserverContext<MasterCoprocessorEnvironment> ctx,
+                               SnapshotDescription snapshot,
+                               HTableDescriptor hTableDescriptor) throws IOException {
+
+  }
+
+  @Override
+  public void postCloneSnapshot(ObserverContext<MasterCoprocessorEnvironment> ctx,
+                                SnapshotDescription snapshot,
+                                HTableDescriptor hTableDescriptor) throws IOException {
+
+  }
+
+  @Override
+  public void preRestoreSnapshot(ObserverContext<MasterCoprocessorEnvironment> ctx,
+                                 SnapshotDescription snapshot,
+                                 HTableDescriptor hTableDescriptor) throws IOException {
+
+  }
+
+  @Override
+  public void postRestoreSnapshot(ObserverContext<MasterCoprocessorEnvironment> ctx,
+                                  SnapshotDescription snapshot,
+                                  HTableDescriptor hTableDescriptor) throws IOException {
+
+  }
+
+  @Override
+  public void preDeleteSnapshot(ObserverContext<MasterCoprocessorEnvironment> ctx,
+                                SnapshotDescription snapshot) throws IOException {
+
+  }
+
+  @Override
+  public void postDeleteSnapshot(ObserverContext<MasterCoprocessorEnvironment> ctx,
+                                 SnapshotDescription snapshot) throws IOException {
+
+  }
+
+  @Override
+  public void preGetTableDescriptors(ObserverContext<MasterCoprocessorEnvironment> ctx,
+                                     List<TableName> tableNamesList,
+                                     List<HTableDescriptor> descriptors) throws IOException {
+
+  }
+
+  @Override
+  public void postGetTableDescriptors(ObserverContext<MasterCoprocessorEnvironment> ctx,
+                                      List<HTableDescriptor> descriptors) throws IOException {
+
+  }
+
+  @Override
+  public void preGetTableDescriptors(ObserverContext<MasterCoprocessorEnvironment> ctx,
+                                     List<TableName> tableNamesList,
+                                     List<HTableDescriptor> descriptors,
+                                     String regex) throws IOException {
+
+  }
+
+  @Override
+  public void postGetTableDescriptors(ObserverContext<MasterCoprocessorEnvironment> ctx,
+                                      List<TableName> tableNamesList,
+                                      List<HTableDescriptor> descriptors,
+                                      String regex) throws IOException {
+
+  }
+
+  @Override
+  public void preGetTableNames(ObserverContext<MasterCoprocessorEnvironment> ctx,
+                               List<HTableDescriptor> descriptors,
+                               String regex) throws IOException {
+
+  }
+
+  @Override
+  public void postGetTableNames(ObserverContext<MasterCoprocessorEnvironment> ctx,
+                                List<HTableDescriptor> descriptors,
+                                String regex) throws IOException {
+
+  }
+
+  @Override
+  public void preCreateNamespace(ObserverContext<MasterCoprocessorEnvironment> ctx,
+                                 NamespaceDescriptor ns) throws IOException {
+    String group = ns.getConfigurationValue(RSGroupInfo.NAMESPACEDESC_PROP_GROUP);
+    if(group != null && groupAdminServer.getRSGroupInfo(group) == null) {
+      throw new ConstraintException("Region server group "+group+" does not exit");
+    }
+  }
+
+  @Override
+  public void postCreateNamespace(ObserverContext<MasterCoprocessorEnvironment> ctx,
+                                  NamespaceDescriptor ns) throws IOException {
+
+  }
+
+  @Override
+  public void preDeleteNamespace(ObserverContext<MasterCoprocessorEnvironment> ctx,
+                                 String namespace) throws IOException {
+
+  }
+
+  @Override
+  public void postDeleteNamespace(ObserverContext<MasterCoprocessorEnvironment> ctx,
+                                  String namespace) throws IOException {
+
+  }
+
+  @Override
+  public void preModifyNamespace(ObserverContext<MasterCoprocessorEnvironment> ctx,
+                                 NamespaceDescriptor ns) throws IOException {
+    preCreateNamespace(ctx, ns);
+  }
+
+  @Override
+  public void postModifyNamespace(ObserverContext<MasterCoprocessorEnvironment> ctx,
+                                  NamespaceDescriptor ns) throws IOException {
+
+  }
+
+  @Override
+  public void preGetNamespaceDescriptor(ObserverContext<MasterCoprocessorEnvironment> ctx,
+                                        String namespace) throws IOException {
+
+  }
+
+  @Override
+  public void postGetNamespaceDescriptor(ObserverContext<MasterCoprocessorEnvironment> ctx,
+                                         NamespaceDescriptor ns) throws IOException {
+
+  }
+
+  @Override
+  public void preListNamespaceDescriptors(ObserverContext<MasterCoprocessorEnvironment> ctx,
+                                          List<NamespaceDescriptor> descriptors)
+      throws IOException {
+
+  }
+
+  @Override
+  public void postListNamespaceDescriptors(ObserverContext<MasterCoprocessorEnvironment> ctx,
+                                           List<NamespaceDescriptor> descriptors)
+      throws IOException {
+
+  }
+
+  @Override
+  public void preTableFlush(ObserverContext<MasterCoprocessorEnvironment> ctx, TableName
+      tableName) throws IOException {
+
+  }
+
+  @Override
+  public void postTableFlush(ObserverContext<MasterCoprocessorEnvironment> ctx,
+                             TableName tableName) throws IOException {
+
+  }
+
+  @Override
+  public void preSetUserQuota(ObserverContext<MasterCoprocessorEnvironment> ctx, String userName,
+                              Quotas quotas) throws IOException {
+
+  }
+
+  @Override
+  public void postSetUserQuota(ObserverContext<MasterCoprocessorEnvironment> ctx,
+                               String userName, Quotas quotas) throws IOException {
+
+  }
+
+  @Override
+  public void preSetUserQuota(ObserverContext<MasterCoprocessorEnvironment> ctx, String userName,
+                              TableName tableName, Quotas quotas) throws IOException {
+
+  }
+
+  @Override
+  public void postSetUserQuota(ObserverContext<MasterCoprocessorEnvironment> ctx, String
+      userName, TableName tableName, Quotas quotas) throws IOException {
+
+  }
+
+  @Override
+  public void preSetUserQuota(ObserverContext<MasterCoprocessorEnvironment> ctx, String userName,
+                              String namespace, Quotas quotas) throws IOException {
+
+  }
+
+  @Override
+  public void postSetUserQuota(ObserverContext<MasterCoprocessorEnvironment> ctx, String userName,
+                               String namespace, Quotas quotas) throws IOException {
+
+  }
+
+  @Override
+  public void preSetTableQuota(ObserverContext<MasterCoprocessorEnvironment> ctx,
+                               TableName tableName, Quotas quotas) throws IOException {
+
+  }
+
+  @Override
+  public void postSetTableQuota(ObserverContext<MasterCoprocessorEnvironment> ctx,
+                                TableName tableName, Quotas quotas) throws IOException {
+
+  }
+
+  @Override
+  public void preSetNamespaceQuota(ObserverContext<MasterCoprocessorEnvironment> ctx,
+                                   String namespace, Quotas quotas) throws IOException {
+
+  }
+
+  @Override
+  public void postSetNamespaceQuota(ObserverContext<MasterCoprocessorEnvironment> ctx,
+                                    String namespace, Quotas quotas) throws IOException {
+
+  }
+
+  @Override
+  public void preDispatchMerge(ObserverContext<MasterCoprocessorEnvironment> ctx,
+                               HRegionInfo regionA, HRegionInfo regionB) throws IOException {
+
+  }
+
+  @Override
+  public void postDispatchMerge(ObserverContext<MasterCoprocessorEnvironment> c,
+                                HRegionInfo regionA, HRegionInfo regionB) throws IOException {
+
+  }
+
+  @Override
+  public void preMoveServers(ObserverContext<MasterCoprocessorEnvironment> ctx,
+                             Set<HostAndPort> servers, String targetGroup) throws IOException {
+
+  }
+
+  @Override
+  public void postMoveServers(ObserverContext<MasterCoprocessorEnvironment> ctx,
+                              Set<HostAndPort> servers, String targetGroup) throws IOException {
+
+  }
+
+  @Override
+  public void preMoveTables(ObserverContext<MasterCoprocessorEnvironment> ctx, Set<TableName>
+      tables, String targetGroup) throws IOException {
+
+  }
+
+  @Override
+  public void postMoveTables(ObserverContext<MasterCoprocessorEnvironment> ctx,
+                             Set<TableName> tables, String targetGroup) throws IOException {
+
+  }
+
+  @Override
+  public void preAddRSGroup(ObserverContext<MasterCoprocessorEnvironment> ctx,
+                            String name) throws IOException {
+
+  }
+
+  @Override
+  public void postAddRSGroup(ObserverContext<MasterCoprocessorEnvironment> ctx,
+                             String name) throws IOException {
+
+  }
+
+  @Override
+  public void preRemoveRSGroup(ObserverContext<MasterCoprocessorEnvironment> ctx,
+                               String name) throws IOException {
+
+  }
+
+  @Override
+  public void postRemoveRSGroup(ObserverContext<MasterCoprocessorEnvironment> ctx,
+                                String name) throws IOException {
+
+  }
+
+  @Override
+  public void preBalanceRSGroup(ObserverContext<MasterCoprocessorEnvironment> ctx,
+                                String groupName) throws IOException {
+
+  }
+
+  @Override
+  public void postBalanceRSGroup(ObserverContext<MasterCoprocessorEnvironment> ctx,
+                                 String groupName, boolean balancerRan) throws IOException {
+
+  }
+
+
+}

http://git-wip-us.apache.org/repos/asf/hbase/blob/a0a7f6f4/hbase-rsgroup/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupAdminServer.java
----------------------------------------------------------------------
diff --git a/hbase-rsgroup/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupAdminServer.java b/hbase-rsgroup/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupAdminServer.java
new file mode 100644
index 0000000..43ac3ad
--- /dev/null
+++ b/hbase-rsgroup/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupAdminServer.java
@@ -0,0 +1,503 @@
+/**
+ * Copyright The Apache Software Foundation
+ *
+ * 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.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import com.google.common.net.HostAndPort;
+
+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.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.hadoop.hbase.HRegionInfo;
+import org.apache.hadoop.hbase.HTableDescriptor;
+import org.apache.hadoop.hbase.NamespaceDescriptor;
+import org.apache.hadoop.hbase.ServerName;
+import org.apache.hadoop.hbase.TableName;
+import org.apache.hadoop.hbase.classification.InterfaceAudience;
+import org.apache.hadoop.hbase.constraint.ConstraintException;
+import org.apache.hadoop.hbase.master.AssignmentManager;
+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.TableLockManager.TableLock;
+
+/**
+ * Service to support Region Server Grouping (HBase-6721)
+ */
+@InterfaceAudience.Private
+public class RSGroupAdminServer extends RSGroupAdmin {
+  private static final Log LOG = LogFactory.getLog(RSGroupAdminServer.class);
+
+  private MasterServices master;
+  //List of servers that are being moved from one group to another
+  //Key=host:port,Value=targetGroup
+  private ConcurrentMap<HostAndPort,String> serversInTransition =
+      new ConcurrentHashMap<HostAndPort, String>();
+  private RSGroupInfoManager RSGroupInfoManager;
+
+  public RSGroupAdminServer(MasterServices master,
+                            RSGroupInfoManager RSGroupInfoManager) throws IOException {
+    this.master = master;
+    this.RSGroupInfoManager = RSGroupInfoManager;
+  }
+
+  @Override
+  public RSGroupInfo getRSGroupInfo(String groupName) throws IOException {
+    return getRSGroupInfoManager().getRSGroup(groupName);
+  }
+
+
+  @Override
+  public RSGroupInfo getRSGroupInfoOfTable(TableName tableName) throws IOException {
+    String groupName = getRSGroupInfoManager().getRSGroupOfTable(tableName);
+    if (groupName == null) {
+      return null;
+    }
+    return getRSGroupInfoManager().getRSGroup(groupName);
+  }
+
+  @Override
+  public void moveServers(Set<HostAndPort> servers, String targetGroupName)
+      throws IOException {
+    if (servers == null) {
+      throw new ConstraintException(
+          "The list of servers cannot be null.");
+    }
+    if (StringUtils.isEmpty(targetGroupName)) {
+      throw new ConstraintException("The target group cannot be null.");
+    }
+    if (servers.size() < 1) {
+      return;
+    }
+
+    RSGroupInfo targetGrp = getRSGroupInfo(targetGroupName);
+    if (targetGrp == null) {
+      throw new ConstraintException("Group does not exist: "+targetGroupName);
+    }
+
+    RSGroupInfoManager manager = getRSGroupInfoManager();
+    synchronized (manager) {
+      if (master.getMasterCoprocessorHost() != null) {
+        master.getMasterCoprocessorHost().preMoveServers(servers, targetGroupName);
+      }
+      HostAndPort firstServer = servers.iterator().next();
+      //we only allow a move from a single source group
+      //so this should be ok
+      RSGroupInfo srcGrp = manager.getRSGroupOfServer(firstServer);
+      //only move online servers (from default)
+      //or servers from other groups
+      //this prevents bogus servers from entering groups
+      if (srcGrp == null) {
+        throw new ConstraintException(
+            "Server "+firstServer+" does not have a group.");
+      }
+      if (RSGroupInfo.DEFAULT_GROUP.equals(srcGrp.getName())) {
+        Set<HostAndPort> onlineServers = new HashSet<HostAndPort>();
+        for(ServerName server: master.getServerManager().getOnlineServers().keySet()) {
+          onlineServers.add(server.getHostPort());
+        }
+        for(HostAndPort el: servers) {
+          if(!onlineServers.contains(el)) {
+            throw new ConstraintException(
+                "Server "+el+" is not an online server in default group.");
+          }
+        }
+      }
+
+      if(srcGrp.getServers().size() <= servers.size() &&
+          srcGrp.getTables().size() > 0) {
+        throw new ConstraintException("Cannot leave a group "+srcGrp.getName()+
+            " that contains tables " +"without servers.");
+      }
+
+      String sourceGroupName = getRSGroupInfoManager()
+          .getRSGroupOfServer(srcGrp.getServers().iterator().next()).getName();
+      if(getRSGroupInfo(targetGroupName) == null) {
+        throw new ConstraintException("Target group does not exist: "+targetGroupName);
+      }
+
+      for(HostAndPort server: servers) {
+        if (serversInTransition.containsKey(server)) {
+          throw new ConstraintException(
+              "Server list contains a server that is already being moved: "+server);
+        }
+        String tmpGroup = getRSGroupInfoManager().getRSGroupOfServer(server).getName();
+        if (sourceGroupName != null && !tmpGroup.equals(sourceGroupName)) {
+          throw new ConstraintException(
+              "Move server request should only come from one source group. "+
+              "Expecting only "+sourceGroupName+" but contains "+tmpGroup);
+        }
+      }
+
+      if(sourceGroupName.equals(targetGroupName)) {
+        throw new ConstraintException(
+            "Target group is the same as source group: "+targetGroupName);
+      }
+
+      try {
+        //update the servers as in transition
+        for (HostAndPort server : servers) {
+          serversInTransition.put(server, targetGroupName);
+        }
+
+        getRSGroupInfoManager().moveServers(servers, sourceGroupName, targetGroupName);
+        boolean found;
+        List<HostAndPort> tmpServers = Lists.newArrayList(servers);
+        do {
+          found = false;
+          for (Iterator<HostAndPort> iter = tmpServers.iterator();
+               iter.hasNext(); ) {
+            HostAndPort rs = iter.next();
+            //get online regions
+            List<HRegionInfo> regions = new LinkedList<HRegionInfo>();
+            for (Map.Entry<HRegionInfo, ServerName> el :
+                master.getAssignmentManager().getRegionStates().getRegionAssignments().entrySet()) {
+              if (el.getValue().getHostPort().equals(rs)) {
+                regions.add(el.getKey());
+              }
+            }
+            Iterator<RegionState> i =
+                master.getAssignmentManager().getRegionStates().getRegionsInTransition().iterator();
+            while (i.hasNext()) {
+              RegionState state = i.next();
+              if (state.getServerName().getHostPort().equals(rs)) {
+                regions.add(state.getRegion());
+              }
+            }
+
+            //unassign regions for a server
+            LOG.info("Unassigning " + regions.size() +
+                " regions from server " + rs + " for move to " + targetGroupName);
+            if (regions.size() > 0) {
+              //TODO bulk unassign or throttled unassign?
+              for (HRegionInfo region : regions) {
+                //regions might get assigned from tables of target group
+                //so we need to filter
+                if (!targetGrp.containsTable(region.getTable())) {
+                  master.getAssignmentManager().unassign(region);
+                  found = true;
+                }
+              }
+            }
+            if (!found) {
+              iter.remove();
+            }
+          }
+          try {
+            Thread.sleep(1000);
+          } catch (InterruptedException e) {
+            LOG.warn("Sleep interrupted", e);
+            Thread.currentThread().interrupt();
+          }
+        } while (found);
+      } finally {
+        //remove from transition
+        for (HostAndPort server : servers) {
+          serversInTransition.remove(server);
+        }
+      }
+      if (master.getMasterCoprocessorHost() != null) {
+        master.getMasterCoprocessorHost().postMoveServers(servers, targetGroupName);
+      }
+      LOG.info("Move server done: "+sourceGroupName+"->"+targetGroupName);
+    }
+  }
+
+  @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;
+    }
+    RSGroupInfoManager manager = getRSGroupInfoManager();
+    synchronized (manager) {
+      if (master.getMasterCoprocessorHost() != null) {
+        master.getMasterCoprocessorHost().preMoveTables(tables, targetGroup);
+      }
+
+      if(targetGroup != null) {
+        RSGroupInfo destGroup = manager.getRSGroup(targetGroup);
+        if(destGroup == null) {
+          throw new ConstraintException("Target group does not exist: "+targetGroup);
+        }
+        if(destGroup.getServers().size() < 1) {
+          throw new ConstraintException("Target group must have at least one server.");
+        }
+      }
+
+      for(TableName table : tables) {
+        String srcGroup = manager.getRSGroupOfTable(table);
+        if(srcGroup != null && srcGroup.equals(targetGroup)) {
+          throw new ConstraintException(
+              "Source group is the same as target group for table "+table+" :"+srcGroup);
+        }
+      }
+      manager.moveTables(tables, targetGroup);
+      if (master.getMasterCoprocessorHost() != null) {
+        master.getMasterCoprocessorHost().postMoveTables(tables, targetGroup);
+      }
+    }
+    for(TableName table: tables) {
+      TableLock lock = master.getTableLockManager().writeLock(table, "Group: table move");
+      try {
+        lock.acquire();
+        for (HRegionInfo region :
+            master.getAssignmentManager().getRegionStates().getRegionsOfTable(table)) {
+          master.getAssignmentManager().unassign(region);
+        }
+      } finally {
+        lock.release();
+      }
+    }
+  }
+
+  @Override
+  public void addRSGroup(String name) throws IOException {
+    if (master.getMasterCoprocessorHost() != null) {
+      master.getMasterCoprocessorHost().preAddRSGroup(name);
+    }
+    getRSGroupInfoManager().addRSGroup(new RSGroupInfo(name));
+    if (master.getMasterCoprocessorHost() != null) {
+      master.getMasterCoprocessorHost().postAddRSGroup(name);
+    }
+  }
+
+  @Override
+  public void removeRSGroup(String name) throws IOException {
+    RSGroupInfoManager manager = getRSGroupInfoManager();
+    synchronized (manager) {
+      if (master.getMasterCoprocessorHost() != null) {
+        master.getMasterCoprocessorHost().preRemoveRSGroup(name);
+      }
+      RSGroupInfo RSGroupInfo = getRSGroupInfoManager().getRSGroup(name);
+      if(RSGroupInfo == null) {
+        throw new ConstraintException("Group "+name+" does not exist");
+      }
+      int tableCount = RSGroupInfo.getTables().size();
+      if (tableCount > 0) {
+        throw new ConstraintException("Group "+name+" must have no associated tables: "+tableCount);
+      }
+      int serverCount = RSGroupInfo.getServers().size();
+      if(serverCount > 0) {
+        throw new ConstraintException(
+            "Group "+name+" must have no associated servers: "+serverCount);
+      }
+      for(NamespaceDescriptor ns: master.getTableNamespaceManager().list()) {
+        String nsGroup = ns.getConfigurationValue(RSGroupInfo.NAMESPACEDESC_PROP_GROUP);
+        if(nsGroup != null &&  nsGroup.equals(name)) {
+          throw new ConstraintException("Group "+name+" is referenced by namespace: "+ns.getName());
+        }
+      }
+      manager.removeRSGroup(name);
+      if (master.getMasterCoprocessorHost() != null) {
+        master.getMasterCoprocessorHost().postRemoveRSGroup(name);
+      }
+    }
+  }
+
+  @Override
+  public boolean balanceRSGroup(String groupName) throws IOException {
+    ServerManager serverManager = master.getServerManager();
+    AssignmentManager assignmentManager = master.getAssignmentManager();
+    LoadBalancer balancer = master.getLoadBalancer();
+
+    boolean balancerRan;
+    synchronized (balancer) {
+      if (master.getMasterCoprocessorHost() != null) {
+        master.getMasterCoprocessorHost().preBalanceRSGroup(groupName);
+      }
+      if (getRSGroupInfo(groupName) == null) {
+        throw new ConstraintException("Group 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 " +
+          groupRIT.size() +
+          " region(s) in transition: " +
+          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<RegionPlan>();
+      for(Map.Entry<TableName, Map<ServerName, List<HRegionInfo>>> tableMap:
+          getRSGroupAssignmentsByTable(groupName).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);
+        }
+      }
+      long startTime = System.currentTimeMillis();
+      balancerRan = plans != null;
+      if (plans != null && !plans.isEmpty()) {
+        LOG.info("Group balance "+groupName+" starting with plan count: "+plans.size());
+        for (RegionPlan plan: plans) {
+          LOG.info("balance " + plan);
+          assignmentManager.balance(plan);
+        }
+        LOG.info("Group balance "+groupName+" completed after "+
+            (System.currentTimeMillis()-startTime)+" seconds");
+      }
+      if (master.getMasterCoprocessorHost() != null) {
+        master.getMasterCoprocessorHost().postBalanceRSGroup(groupName, balancerRan);
+      }
+    }
+    return balancerRan;
+  }
+
+  @Override
+  public List<RSGroupInfo> listRSGroups() throws IOException {
+    return getRSGroupInfoManager().listRSGroups();
+  }
+
+  @Override
+  public RSGroupInfo getRSGroupOfServer(HostAndPort hostPort) throws IOException {
+    return getRSGroupInfoManager().getRSGroupOfServer(hostPort);
+  }
+
+  @InterfaceAudience.Private
+  public RSGroupInfoManager getRSGroupInfoManager() throws IOException {
+    return RSGroupInfoManager;
+  }
+
+  private Map<String, RegionState> rsGroupGetRegionsInTransition(String groupName)
+      throws IOException {
+    Map<String, RegionState> rit = Maps.newTreeMap();
+    AssignmentManager am = master.getAssignmentManager();
+    RSGroupInfo RSGroupInfo = getRSGroupInfo(groupName);
+    for(TableName tableName : RSGroupInfo.getTables()) {
+      for(HRegionInfo regionInfo: am.getRegionStates().getRegionsOfTable(tableName)) {
+        RegionState state =
+            master.getAssignmentManager().getRegionStates().getRegionTransitionState(regionInfo);
+        if(state != null) {
+          rit.put(regionInfo.getEncodedName(), state);
+        }
+      }
+    }
+    return rit;
+  }
+
+  private Map<TableName, Map<ServerName, List<HRegionInfo>>>
+      getRSGroupAssignmentsByTable(String groupName) throws IOException {
+    Map<TableName, Map<ServerName, List<HRegionInfo>>> result = Maps.newHashMap();
+    RSGroupInfo RSGroupInfo = getRSGroupInfo(groupName);
+    Map<TableName, Map<ServerName, List<HRegionInfo>>> assignments = Maps.newHashMap();
+    for(Map.Entry<HRegionInfo, ServerName> entry:
+        master.getAssignmentManager().getRegionStates().getRegionAssignments().entrySet()) {
+      TableName currTable = entry.getKey().getTable();
+      ServerName currServer = entry.getValue();
+      HRegionInfo currRegion = entry.getKey();
+      if(RSGroupInfo.getTables().contains(currTable)) {
+        if(!assignments.containsKey(entry.getKey().getTable())) {
+          assignments.put(currTable, new HashMap<ServerName, List<HRegionInfo>>());
+        }
+        if(!assignments.get(currTable).containsKey(currServer)) {
+          assignments.get(currTable).put(currServer, new ArrayList<HRegionInfo>());
+        }
+        assignments.get(currTable).get(currServer).add(currRegion);
+      }
+    }
+
+    Map<ServerName, List<HRegionInfo>> serverMap = Maps.newHashMap();
+    for(ServerName serverName: master.getServerManager().getOnlineServers().keySet()) {
+      if(RSGroupInfo.getServers().contains(serverName.getHostPort())) {
+        serverMap.put(serverName, Collections.<HRegionInfo> emptyList());
+      }
+    }
+
+    //add all tables that are members of the group
+    for(TableName tableName : RSGroupInfo.getTables()) {
+      if(assignments.containsKey(tableName)) {
+        result.put(tableName, new HashMap<ServerName, List<HRegionInfo>>());
+        result.get(tableName).putAll(serverMap);
+        result.get(tableName).putAll(assignments.get(tableName));
+        LOG.debug("Adding assignments for "+tableName+": "+assignments.get(tableName));
+      }
+    }
+
+    return result;
+  }
+
+  public void prepareRSGroupForTable(HTableDescriptor desc) throws IOException {
+    String groupName =
+        master.getTableNamespaceManager().get(desc.getTableName().getNamespaceAsString())
+                .getConfigurationValue(RSGroupInfo.NAMESPACEDESC_PROP_GROUP);
+    if (groupName == null) {
+      groupName = RSGroupInfo.DEFAULT_GROUP;
+    }
+    RSGroupInfo RSGroupInfo = getRSGroupInfo(groupName);
+    if (RSGroupInfo == null) {
+      throw new ConstraintException("RSGroup " + groupName + " does not exist.");
+    }
+    if (!RSGroupInfo.containsTable(desc.getTableName())) {
+      LOG.debug("Pre-moving table " + desc.getTableName() + " to rsgroup " + groupName);
+      moveTables(Sets.newHashSet(desc.getTableName()), groupName);
+    }
+  }
+
+  public void cleanupRSGroupForTable(TableName tableName) throws IOException {
+    try {
+      RSGroupInfo group = getRSGroupInfoOfTable(tableName);
+      if (group != null) {
+        LOG.debug("Removing deleted table from table rsgroup " + group.getName());
+        moveTables(Sets.newHashSet(tableName), null);
+      }
+    } catch (ConstraintException ex) {
+      LOG.debug("Failed to perform rsgroup information cleanup for table: " + tableName, ex);
+    } catch (IOException ex) {
+      LOG.debug("Failed to perform rsgroup information cleanup for table: " + tableName, ex);
+    }
+  }
+
+  @Override
+  public void close() throws IOException {
+  }
+}

http://git-wip-us.apache.org/repos/asf/hbase/blob/a0a7f6f4/hbase-rsgroup/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupBasedLoadBalancer.java
----------------------------------------------------------------------
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
new file mode 100644
index 0000000..fea1275
--- /dev/null
+++ b/hbase-rsgroup/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupBasedLoadBalancer.java
@@ -0,0 +1,428 @@
+/**
+ * Copyright The Apache Software Foundation
+ *
+ * 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.common.collect.ArrayListMultimap;
+import com.google.common.collect.LinkedListMultimap;
+import com.google.common.collect.ListMultimap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.net.HostAndPort;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+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.Set;
+import java.util.TreeMap;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.hbase.ClusterStatus;
+import org.apache.hadoop.hbase.HBaseIOException;
+import org.apache.hadoop.hbase.HRegionInfo;
+import org.apache.hadoop.hbase.ServerName;
+import org.apache.hadoop.hbase.TableName;
+import org.apache.hadoop.hbase.classification.InterfaceAudience;
+import org.apache.hadoop.hbase.constraint.ConstraintException;
+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.util.ReflectionUtils;
+
+/**
+ * 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.
+ *
+ */
+@InterfaceAudience.Private
+public class RSGroupBasedLoadBalancer implements RSGroupableBalancer, LoadBalancer {
+  /** Config for pluggable load balancers */
+  public static final String HBASE_GROUP_LOADBALANCER_CLASS = "hbase.group.grouploadbalancer.class";
+
+  private static final Log LOG = LogFactory.getLog(RSGroupBasedLoadBalancer.class);
+
+  private Configuration config;
+  private ClusterStatus clusterStatus;
+  private MasterServices masterServices;
+  private RSGroupInfoManager RSGroupInfoManager;
+  private LoadBalancer internalBalancer;
+
+  //used during reflection by LoadBalancerFactory
+  @InterfaceAudience.Private
+  public RSGroupBasedLoadBalancer() {
+  }
+
+  //This constructor should only be used for unit testing
+  @InterfaceAudience.Private
+  public RSGroupBasedLoadBalancer(RSGroupInfoManager RSGroupInfoManager) {
+    this.RSGroupInfoManager = RSGroupInfoManager;
+  }
+
+  @Override
+  public Configuration getConf() {
+    return config;
+  }
+
+  @Override
+  public void setConf(Configuration conf) {
+    this.config = conf;
+  }
+
+  @Override
+  public void setClusterStatus(ClusterStatus st) {
+    this.clusterStatus = st;
+  }
+
+  @Override
+  public void setMasterServices(MasterServices masterServices) {
+    this.masterServices = masterServices;
+  }
+
+  @Override
+  public List<RegionPlan> balanceCluster(TableName tableName, Map<ServerName, List<HRegionInfo>>
+      clusterState) throws HBaseIOException {
+    return balanceCluster(clusterState);
+  }
+
+  @Override
+  public List<RegionPlan> balanceCluster(Map<ServerName, List<HRegionInfo>> clusterState)
+      throws HBaseIOException {
+    if (!isOnline()) {
+      throw new ConstraintException(RSGroupInfoManager.RSGROUP_TABLE_NAME +
+          " is not online, unable to perform balance");
+    }
+
+    Map<ServerName,List<HRegionInfo>> correctedState = correctAssignments(clusterState);
+    List<RegionPlan> regionPlans = new ArrayList<RegionPlan>();
+
+    List<HRegionInfo> misplacedRegions = correctedState.get(LoadBalancer.BOGUS_SERVER_NAME);
+    for (HRegionInfo regionInfo : misplacedRegions) {
+      regionPlans.add(new RegionPlan(regionInfo, null, null));
+    }
+    try {
+      for (RSGroupInfo info : RSGroupInfoManager.listRSGroups()) {
+        Map<ServerName, List<HRegionInfo>> groupClusterState =
+            new HashMap<ServerName, List<HRegionInfo>>();
+        for (HostAndPort sName : info.getServers()) {
+          for(ServerName curr: clusterState.keySet()) {
+            if(curr.getHostPort().equals(sName)) {
+              groupClusterState.put(curr, correctedState.get(curr));
+            }
+          }
+        }
+        List<RegionPlan> groupPlans = this.internalBalancer
+            .balanceCluster(groupClusterState);
+        if (groupPlans != null) {
+          regionPlans.addAll(groupPlans);
+        }
+      }
+    } catch (IOException exp) {
+      LOG.warn("Exception while balancing cluster.", exp);
+      regionPlans.clear();
+    }
+    return regionPlans;
+  }
+
+  @Override
+  public Map<ServerName, List<HRegionInfo>> roundRobinAssignment(
+      List<HRegionInfo> regions, List<ServerName> servers) throws HBaseIOException {
+    Map<ServerName, List<HRegionInfo>> assignments = Maps.newHashMap();
+    ListMultimap<String,HRegionInfo> regionMap = ArrayListMultimap.create();
+    ListMultimap<String,ServerName> serverMap = ArrayListMultimap.create();
+    generateGroupMaps(regions, servers, regionMap, serverMap);
+    for(String groupKey : regionMap.keySet()) {
+      if (regionMap.get(groupKey).size() > 0) {
+        Map<ServerName, List<HRegionInfo>> result =
+            this.internalBalancer.roundRobinAssignment(
+                regionMap.get(groupKey),
+                serverMap.get(groupKey));
+        if(result != null) {
+          assignments.putAll(result);
+        }
+      }
+    }
+    return assignments;
+  }
+
+  @Override
+  public Map<ServerName, List<HRegionInfo>> retainAssignment(
+      Map<HRegionInfo, ServerName> regions, List<ServerName> servers) throws HBaseIOException {
+    try {
+      Map<ServerName, List<HRegionInfo>> assignments = new TreeMap<ServerName, List<HRegionInfo>>();
+      ListMultimap<String, HRegionInfo> groupToRegion = ArrayListMultimap.create();
+      Set<HRegionInfo> misplacedRegions = getMisplacedRegions(regions);
+      for (HRegionInfo region : regions.keySet()) {
+        if (!misplacedRegions.contains(region)) {
+          String groupName = RSGroupInfoManager.getRSGroupOfTable(region.getTable());
+          groupToRegion.put(groupName, region);
+        }
+      }
+      // Now the "groupToRegion" map has only the regions which have correct
+      // assignments.
+      for (String key : groupToRegion.keySet()) {
+        Map<HRegionInfo, ServerName> currentAssignmentMap = new TreeMap<HRegionInfo, ServerName>();
+        List<HRegionInfo> regionList = groupToRegion.get(key);
+        RSGroupInfo info = RSGroupInfoManager.getRSGroup(key);
+        List<ServerName> candidateList = filterOfflineServers(info, servers);
+        for (HRegionInfo region : regionList) {
+          currentAssignmentMap.put(region, regions.get(region));
+        }
+        if(candidateList.size() > 0) {
+          assignments.putAll(this.internalBalancer.retainAssignment(
+              currentAssignmentMap, candidateList));
+        }
+      }
+
+      for (HRegionInfo region : misplacedRegions) {
+        String groupName = RSGroupInfoManager.getRSGroupOfTable(
+            region.getTable());
+        RSGroupInfo info = RSGroupInfoManager.getRSGroup(groupName);
+        List<ServerName> candidateList = filterOfflineServers(info, servers);
+        ServerName server = this.internalBalancer.randomAssignment(region,
+            candidateList);
+        if (server != null && !assignments.containsKey(server)) {
+          assignments.put(server, new ArrayList<HRegionInfo>());
+        } else if (server != null) {
+          assignments.get(server).add(region);
+        } else {
+          //if not server is available assign to bogus so it ends up in RIT
+          if(!assignments.containsKey(LoadBalancer.BOGUS_SERVER_NAME)) {
+            assignments.put(LoadBalancer.BOGUS_SERVER_NAME, new ArrayList<HRegionInfo>());
+          }
+          assignments.get(LoadBalancer.BOGUS_SERVER_NAME).add(region);
+        }
+      }
+      return assignments;
+    } catch (IOException e) {
+      throw new HBaseIOException("Failed to do online retain assignment", e);
+    }
+  }
+
+  @Override
+  public Map<HRegionInfo, ServerName> immediateAssignment(List<HRegionInfo> regions,
+      List<ServerName> servers) throws HBaseIOException {
+    throw new UnsupportedOperationException("immediateAssignment is not supported");
+  }
+
+  @Override
+  public ServerName randomAssignment(HRegionInfo region,
+      List<ServerName> servers) throws HBaseIOException {
+    ListMultimap<String,HRegionInfo> regionMap = LinkedListMultimap.create();
+    ListMultimap<String,ServerName> serverMap = LinkedListMultimap.create();
+    generateGroupMaps(Lists.newArrayList(region), servers, regionMap, serverMap);
+    List<ServerName> filteredServers = serverMap.get(regionMap.keySet().iterator().next());
+    return this.internalBalancer.randomAssignment(region, filteredServers);
+  }
+
+  private void generateGroupMaps(
+    List<HRegionInfo> regions,
+    List<ServerName> servers,
+    ListMultimap<String, HRegionInfo> regionMap,
+    ListMultimap<String, ServerName> serverMap) throws HBaseIOException {
+    try {
+      for (HRegionInfo region : regions) {
+        String groupName = RSGroupInfoManager.getRSGroupOfTable(region.getTable());
+        if(groupName == null) {
+          LOG.warn("Group for table "+region.getTable()+" is null");
+        }
+        regionMap.put(groupName, region);
+      }
+      for (String groupKey : regionMap.keySet()) {
+        RSGroupInfo info = RSGroupInfoManager.getRSGroup(groupKey);
+        serverMap.putAll(groupKey, filterOfflineServers(info, servers));
+        if(serverMap.get(groupKey).size() < 1) {
+          serverMap.put(groupKey, LoadBalancer.BOGUS_SERVER_NAME);
+        }
+      }
+    } catch(IOException e) {
+      throw new HBaseIOException("Failed to generate group maps", e);
+    }
+  }
+
+  private List<ServerName> filterOfflineServers(RSGroupInfo RSGroupInfo,
+                                                List<ServerName> onlineServers) {
+    if (RSGroupInfo != null) {
+      return filterServers(RSGroupInfo.getServers(), onlineServers);
+    } else {
+      LOG.debug("Group Information found to be null. Some regions might be unassigned.");
+      return Collections.EMPTY_LIST;
+    }
+  }
+
+  /**
+   * Filter servers based on the online servers.
+   *
+   * @param servers
+   *          the servers
+   * @param onlineServers
+   *          List of servers which are online.
+   * @return the list
+   */
+  private List<ServerName> filterServers(Collection<HostAndPort> servers,
+      Collection<ServerName> onlineServers) {
+    ArrayList<ServerName> finalList = new ArrayList<ServerName>();
+    for (HostAndPort server : servers) {
+      for(ServerName curr: onlineServers) {
+        if(curr.getHostPort().equals(server)) {
+          finalList.add(curr);
+        }
+      }
+    }
+    return finalList;
+  }
+
+  private ListMultimap<String, HRegionInfo> groupRegions(
+      List<HRegionInfo> regionList) throws IOException {
+    ListMultimap<String, HRegionInfo> regionGroup = ArrayListMultimap
+        .create();
+    for (HRegionInfo region : regionList) {
+      String groupName = RSGroupInfoManager.getRSGroupOfTable(region.getTable());
+      regionGroup.put(groupName, region);
+    }
+    return regionGroup;
+  }
+
+  private Set<HRegionInfo> getMisplacedRegions(
+      Map<HRegionInfo, ServerName> regions) throws IOException {
+    Set<HRegionInfo> misplacedRegions = new HashSet<HRegionInfo>();
+    for (HRegionInfo region : regions.keySet()) {
+      ServerName assignedServer = regions.get(region);
+      RSGroupInfo info =
+          RSGroupInfoManager.getRSGroup(RSGroupInfoManager.getRSGroupOfTable(region.getTable()));
+      if (assignedServer != null &&
+          (info == null || !info.containsServer(assignedServer.getHostPort()))) {
+        LOG.debug("Found misplaced region: " + region.getRegionNameAsString() +
+            " on server: " + assignedServer +
+            " found in group: " +
+            RSGroupInfoManager.getRSGroupOfServer(assignedServer.getHostPort()) +
+            " outside of group: " + info.getName());
+        misplacedRegions.add(region);
+      }
+    }
+    return misplacedRegions;
+  }
+
+  private Map<ServerName, List<HRegionInfo>> correctAssignments(
+       Map<ServerName, List<HRegionInfo>> existingAssignments){
+    Map<ServerName, List<HRegionInfo>> correctAssignments =
+        new TreeMap<ServerName, List<HRegionInfo>>();
+    List<HRegionInfo> misplacedRegions = new LinkedList<HRegionInfo>();
+    correctAssignments.put(LoadBalancer.BOGUS_SERVER_NAME, new LinkedList<HRegionInfo>());
+    for (ServerName sName : existingAssignments.keySet()) {
+      correctAssignments.put(sName, new LinkedList<HRegionInfo>());
+      List<HRegionInfo> regions = existingAssignments.get(sName);
+      for (HRegionInfo region : regions) {
+        RSGroupInfo info = null;
+        try {
+          info = RSGroupInfoManager.getRSGroup(
+              RSGroupInfoManager.getRSGroupOfTable(region.getTable()));
+        }catch(IOException exp){
+          LOG.debug("Group information null for region of table " + region.getTable(),
+              exp);
+        }
+        if ((info == null) || (!info.containsServer(sName.getHostPort()))) {
+          correctAssignments.get(LoadBalancer.BOGUS_SERVER_NAME).add(region);
+        } else {
+          correctAssignments.get(sName).add(region);
+        }
+      }
+    }
+
+    //TODO bulk unassign?
+    //unassign misplaced regions, so that they are assigned to correct groups.
+    for(HRegionInfo info: misplacedRegions) {
+      this.masterServices.getAssignmentManager().unassign(info);
+    }
+    return correctAssignments;
+  }
+
+  @Override
+  public void initialize() throws HBaseIOException {
+    try {
+      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();
+      }
+    } catch (IOException e) {
+      throw new HBaseIOException("Failed to initialize GroupInfoManagerImpl", e);
+    }
+
+    // Create the balancer
+    Class<? extends LoadBalancer> balancerKlass = config.getClass(
+        HBASE_GROUP_LOADBALANCER_CLASS,
+        StochasticLoadBalancer.class, LoadBalancer.class);
+    internalBalancer = ReflectionUtils.newInstance(balancerKlass, config);
+    internalBalancer.setClusterStatus(clusterStatus);
+    internalBalancer.setMasterServices(masterServices);
+    internalBalancer.setConf(config);
+    internalBalancer.initialize();
+  }
+
+  public boolean isOnline() {
+    return RSGroupInfoManager != null && RSGroupInfoManager.isOnline();
+  }
+
+  @Override
+  public void regionOnline(HRegionInfo regionInfo, ServerName sn) {
+  }
+
+  @Override
+  public void regionOffline(HRegionInfo regionInfo) {
+  }
+
+  @Override
+  public void onConfigurationChange(Configuration conf) {
+    //DO nothing for now
+  }
+
+  @Override
+  public void stop(String why) {
+  }
+
+  @Override
+  public boolean isStopped() {
+    return false;
+  }
+}

http://git-wip-us.apache.org/repos/asf/hbase/blob/a0a7f6f4/hbase-rsgroup/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupInfoManager.java
----------------------------------------------------------------------
diff --git a/hbase-rsgroup/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupInfoManager.java b/hbase-rsgroup/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupInfoManager.java
new file mode 100644
index 0000000..434c85f
--- /dev/null
+++ b/hbase-rsgroup/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupInfoManager.java
@@ -0,0 +1,132 @@
+/**
+ * Copyright The Apache Software Foundation
+ *
+ * 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.common.net.HostAndPort;
+
+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.util.Bytes;
+
+/**
+ * Interface used to manage RSGroupInfo storage. An implementation
+ * has the option to support offline mode.
+ * See {@link RSGroupBasedLoadBalancer}
+ */
+public interface RSGroupInfoManager {
+  //Assigned before user tables
+  public static final TableName RSGROUP_TABLE_NAME =
+      TableName.valueOf(NamespaceDescriptor.SYSTEM_NAMESPACE_NAME_STR, "rsgroup");
+  public static final byte[] RSGROUP_TABLE_NAME_BYTES = RSGROUP_TABLE_NAME.toBytes();
+  public static final String rsGroupZNode = "rsgroup";
+  public static final byte[] META_FAMILY_BYTES = Bytes.toBytes("m");
+  public static final byte[] META_QUALIFIER_BYTES = Bytes.toBytes("i");
+  public static final byte[] ROW_KEY = {0};
+
+
+  /**
+   * Adds the group.
+   *
+   * @param rsGroupInfo the group name
+   * @throws java.io.IOException Signals that an I/O exception has occurred.
+   */
+  void addRSGroup(RSGroupInfo rsGroupInfo) throws IOException;
+
+  /**
+   * Remove a region server group.
+   *
+   * @param groupName the group name
+   * @throws java.io.IOException Signals that an I/O exception has occurred.
+   */
+  void removeRSGroup(String groupName) throws IOException;
+
+  /**
+   * move servers to a new group.
+   * @param hostPorts list of servers, must be part of the same group
+   * @param srcGroup groupName being moved from
+   * @param dstGroup groupName being moved to
+   * @return true if move was successful
+   * @throws java.io.IOException on move failure
+   */
+  boolean moveServers(Set<HostAndPort> hostPorts,
+                      String srcGroup, String dstGroup) throws IOException;
+
+  /**
+   * Gets the group info of server.
+   *
+   * @param hostPort the server
+   * @return An instance of RSGroupInfo
+   */
+  RSGroupInfo getRSGroupOfServer(HostAndPort hostPort) throws IOException;
+
+  /**
+   * Gets the group information.
+   *
+   * @param groupName the group name
+   * @return An instance of RSGroupInfo
+   */
+  RSGroupInfo getRSGroup(String groupName) throws IOException;
+
+  /**
+   * Get the group membership of a table
+   * @param tableName name of table to get group membership
+   * @return Group name of table
+   * @throws java.io.IOException on failure to retrive information
+   */
+  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
+   * @throws java.io.IOException on failure to move
+   */
+  void moveTables(Set<TableName> tableNames, String groupName) throws IOException;
+
+  /**
+   * List the groups
+   *
+   * @return list of RSGroupInfo
+   * @throws java.io.IOException on failure
+   */
+  List<RSGroupInfo> listRSGroups() throws IOException;
+
+  /**
+   * Refresh/reload the group information from
+   * the persistent store
+   *
+   * @throws java.io.IOException on failure to refresh
+   */
+  void refresh() throws IOException;
+
+  /**
+   * Whether the manager is able to fully
+   * return group metadata
+   *
+   * @return whether the manager is in online mode
+   */
+  boolean isOnline();
+}