You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hbase.apache.org by zg...@apache.org on 2020/07/08 06:39:51 UTC

[hbase] branch branch-2 updated: HBASE-24431 RSGroupInfo add configuration map to store something extra (#2031)

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

zghao pushed a commit to branch branch-2
in repository https://gitbox.apache.org/repos/asf/hbase.git


The following commit(s) were added to refs/heads/branch-2 by this push:
     new 5fcffae  HBASE-24431 RSGroupInfo add configuration map to store something extra (#2031)
5fcffae is described below

commit 5fcffae5db8ba6d3afb2c4292fbac4b9795fc53d
Author: XinSun <dd...@gmail.com>
AuthorDate: Wed Jul 8 14:39:41 2020 +0800

    HBASE-24431 RSGroupInfo add configuration map to store something extra (#2031)
    
    Signed-off-by: Guanghao Zhang <zg...@apache.org>
---
 .../apache/hadoop/hbase/rsgroup/RSGroupInfo.java   | 34 +++++++-
 hbase-protocol/src/main/protobuf/RSGroup.proto     |  1 +
 .../apache/hadoop/hbase/rsgroup/RSGroupAdmin.java  |  9 +++
 .../hadoop/hbase/rsgroup/RSGroupAdminClient.java   | 20 +++++
 .../hadoop/hbase/rsgroup/RSGroupAdminEndpoint.java | 28 +++++++
 .../hadoop/hbase/rsgroup/RSGroupAdminServer.java   |  8 ++
 .../hadoop/hbase/rsgroup/RSGroupInfoManager.java   |  9 +++
 .../hbase/rsgroup/RSGroupInfoManagerImpl.java      | 11 +++
 .../hadoop/hbase/rsgroup/RSGroupProtobufUtil.java  | 19 +++--
 hbase-rsgroup/src/main/protobuf/RSGroupAdmin.proto | 11 +++
 .../hadoop/hbase/rsgroup/TestRSGroupConfig.java    | 94 ++++++++++++++++++++++
 .../hbase/rsgroup/VerifyingRSGroupAdminClient.java |  7 ++
 .../hadoop/hbase/coprocessor/MasterObserver.java   | 19 +++++
 .../hadoop/hbase/master/MasterCoprocessorHost.java | 21 +++++
 hbase-shell/src/main/ruby/hbase/rsgroup_admin.rb   | 37 +++++++++
 hbase-shell/src/main/ruby/shell.rb                 |  2 +
 .../ruby/shell/commands/alter_rsgroup_config.rb    | 38 +++++++++
 .../ruby/shell/commands/show_rsgroup_config.rb     | 41 ++++++++++
 .../src/test/ruby/shell/rsgroup_shell_test.rb      | 14 ++++
 19 files changed, 416 insertions(+), 7 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 25e827d..2fe2ba9 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,6 +19,10 @@
 package org.apache.hadoop.hbase.rsgroup;
 
 import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 import java.util.SortedSet;
 import java.util.TreeSet;
@@ -41,18 +45,22 @@ public class RSGroupInfo {
   // Keep tables sorted too.
   private final SortedSet<TableName> tables;
 
+  private final Map<String, String> configuration;
+
   public RSGroupInfo(String name) {
-    this(name, new TreeSet<Address>(), new TreeSet<TableName>());
+    this(name, new TreeSet<>(), new TreeSet<>());
   }
 
   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);
+    configuration = new HashMap<>();
   }
 
   public RSGroupInfo(RSGroupInfo src) {
     this(src.name, src.servers, src.tables);
+    src.configuration.forEach(this::setConfiguration);
   }
 
   /**
@@ -121,6 +129,30 @@ public class RSGroupInfo {
     return tables.remove(table);
   }
 
+  /**
+   * Getter for fetching an unmodifiable {@link #configuration} map.
+   */
+  public Map<String, String> getConfiguration() {
+    // shallow pointer copy
+    return Collections.unmodifiableMap(configuration);
+  }
+
+  /**
+   * Setter for storing a configuration setting in {@link #configuration} map.
+   * @param key Config key.
+   * @param value String value.
+   */
+  public void setConfiguration(String key, String value) {
+    configuration.put(key, Objects.requireNonNull(value));
+  }
+
+  /**
+   * Remove a config setting represented by the key from the {@link #configuration} map
+   */
+  public void removeConfiguration(final String key) {
+    configuration.remove(key);
+  }
+
   @Override
   public String toString() {
     StringBuilder sb = new StringBuilder();
diff --git a/hbase-protocol/src/main/protobuf/RSGroup.proto b/hbase-protocol/src/main/protobuf/RSGroup.proto
index ba82175..5ab949c 100644
--- a/hbase-protocol/src/main/protobuf/RSGroup.proto
+++ b/hbase-protocol/src/main/protobuf/RSGroup.proto
@@ -31,5 +31,6 @@ message RSGroupInfo {
   required string name = 1;
   repeated ServerName servers = 4;
   repeated TableName tables = 3;
+  repeated NameStringPair configuration = 5;
 }
 
diff --git a/hbase-rsgroup/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupAdmin.java b/hbase-rsgroup/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupAdmin.java
index c389dc5..15daf14 100644
--- a/hbase-rsgroup/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupAdmin.java
+++ b/hbase-rsgroup/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupAdmin.java
@@ -19,6 +19,7 @@ package org.apache.hadoop.hbase.rsgroup;
 
 import java.io.IOException;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 import org.apache.hadoop.hbase.TableName;
@@ -105,4 +106,12 @@ public interface RSGroupAdmin {
    * @param newName new rsgroup name
    */
   void renameRSGroup(String oldName, String newName) throws IOException;
+
+  /**
+   * Update RSGroup configuration
+   * @param groupName the group name
+   * @param configuration new configuration of the group name to be set
+   * @throws IOException if a remote or network exception occurs
+   */
+  void updateRSGroupConfig(String groupName, Map<String, String> configuration) throws IOException;
 }
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 bcfe3a4..b4e5e80 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
@@ -22,6 +22,7 @@ import com.google.protobuf.ServiceException;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 import org.apache.hadoop.hbase.TableName;
@@ -31,6 +32,7 @@ import org.apache.hadoop.hbase.client.Connection;
 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.HBaseProtos.NameStringPair;
 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;
@@ -47,6 +49,7 @@ import org.apache.hadoop.hbase.protobuf.generated.RSGroupAdminProtos.RSGroupAdmi
 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.RSGroupAdminProtos.RenameRSGroupRequest;
+import org.apache.hadoop.hbase.protobuf.generated.RSGroupAdminProtos.UpdateRSGroupConfigRequest;
 import org.apache.hadoop.hbase.protobuf.generated.RSGroupProtos;
 import org.apache.yetus.audience.InterfaceAudience;
 
@@ -249,4 +252,21 @@ public class RSGroupAdminClient implements RSGroupAdmin {
       throw ProtobufUtil.handleRemoteException(e);
     }
   }
+
+  @Override
+  public void updateRSGroupConfig(String groupName, Map<String, String> configuration)
+      throws IOException {
+    UpdateRSGroupConfigRequest.Builder builder = UpdateRSGroupConfigRequest.newBuilder()
+        .setGroupName(groupName);
+    if (configuration != null) {
+      configuration.entrySet().forEach(e ->
+          builder.addConfiguration(NameStringPair.newBuilder().setName(e.getKey())
+              .setValue(e.getValue()).build()));
+    }
+    try {
+      stub.updateRSGroupConfig(null, builder.build());
+    } catch (ServiceException e) {
+      throw ProtobufUtil.handleRemoteException(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 ad4c1af..0a05c85 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
@@ -26,6 +26,7 @@ import java.io.IOException;
 import java.util.Collections;
 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;
@@ -81,6 +82,8 @@ import org.apache.hadoop.hbase.protobuf.generated.RSGroupAdminProtos.RemoveServe
 import org.apache.hadoop.hbase.protobuf.generated.RSGroupAdminProtos.RemoveServersResponse;
 import org.apache.hadoop.hbase.protobuf.generated.RSGroupAdminProtos.RenameRSGroupRequest;
 import org.apache.hadoop.hbase.protobuf.generated.RSGroupAdminProtos.RenameRSGroupResponse;
+import org.apache.hadoop.hbase.protobuf.generated.RSGroupAdminProtos.UpdateRSGroupConfigRequest;
+import org.apache.hadoop.hbase.protobuf.generated.RSGroupAdminProtos.UpdateRSGroupConfigResponse;
 import org.apache.hadoop.hbase.protobuf.generated.TableProtos;
 import org.apache.hadoop.hbase.security.User;
 import org.apache.hadoop.hbase.security.UserProvider;
@@ -90,6 +93,8 @@ import org.apache.hadoop.util.Shell.ShellCommandExecutor;
 import org.apache.yetus.audience.InterfaceAudience;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+
+import org.apache.hbase.thirdparty.com.google.common.collect.Maps;
 import org.apache.hbase.thirdparty.com.google.common.collect.Sets;
 
 // TODO: Encapsulate MasterObserver functions into separate subclass.
@@ -425,6 +430,29 @@ public class RSGroupAdminEndpoint implements MasterCoprocessor, MasterObserver {
       }
       done.run(builder.build());
     }
+
+    @Override
+    public void updateRSGroupConfig(RpcController controller, UpdateRSGroupConfigRequest request,
+                                    RpcCallback<UpdateRSGroupConfigResponse> done) {
+      UpdateRSGroupConfigResponse.Builder builder = UpdateRSGroupConfigResponse.newBuilder();
+      String groupName = request.getGroupName();
+      Map<String, String> configuration = Maps.newHashMap();
+      request.getConfigurationList().forEach(p -> configuration.put(p.getName(), p.getValue()));
+      LOG.info("{} update rsgroup {} configuration {}", master.getClientIdAuditPrefix(), groupName,
+          configuration);
+      try {
+        if (master.getMasterCoprocessorHost() != null) {
+          master.getMasterCoprocessorHost().preUpdateRSGroupConfig(groupName, configuration);
+        }
+        groupAdminServer.updateRSGroupConfig(groupName, configuration);
+        if (master.getMasterCoprocessorHost() != null) {
+          master.getMasterCoprocessorHost().postUpdateRSGroupConfig(groupName, configuration);
+        }
+      } catch (IOException e) {
+        CoprocessorRpcUtils.setControllerException(controller, e);
+      }
+      done.run(builder.build());
+    }
   }
 
   boolean rsgroupHasServersOnline(TableDescriptor desc) throws IOException {
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
index bf9f8488..a1f988d 100644
--- 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
@@ -532,6 +532,14 @@ public class RSGroupAdminServer implements RSGroupAdmin {
     }
   }
 
+  @Override
+  public void updateRSGroupConfig(String groupName, Map<String, String> configuration)
+      throws IOException {
+    synchronized (rsGroupInfoManager) {
+      rsGroupInfoManager.updateRSGroupConfig(groupName, configuration);
+    }
+  }
+
   private Map<String, RegionState> rsGroupGetRegionsInTransition(String groupName)
       throws IOException {
     Map<String, RegionState> rit = Maps.newTreeMap();
diff --git a/hbase-rsgroup/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupInfoManager.java b/hbase-rsgroup/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupInfoManager.java
index 594f0f0..e64e2d2 100644
--- a/hbase-rsgroup/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupInfoManager.java
+++ b/hbase-rsgroup/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupInfoManager.java
@@ -21,6 +21,7 @@ package org.apache.hadoop.hbase.rsgroup;
 
 import java.io.IOException;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 import org.apache.hadoop.hbase.NamespaceDescriptor;
@@ -139,4 +140,12 @@ public interface RSGroupInfoManager {
    * @return {@link RSGroupInfo} which table should belong to
    */
   RSGroupInfo determineRSGroupInfoForTable(TableName tableName) throws IOException;
+
+  /**
+   * Update RSGroup configuration
+   * @param groupName the group name
+   * @param configuration new configuration of the group name to be set
+   * @throws IOException if a remote or network exception occurs
+   */
+  void updateRSGroupConfig(String groupName, Map<String, String> configuration) throws IOException;
 }
diff --git a/hbase-rsgroup/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupInfoManagerImpl.java b/hbase-rsgroup/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupInfoManagerImpl.java
index 8c2c313..c6a53e9 100644
--- a/hbase-rsgroup/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupInfoManagerImpl.java
+++ b/hbase-rsgroup/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupInfoManagerImpl.java
@@ -32,6 +32,7 @@ import java.util.OptionalLong;
 import java.util.Set;
 import java.util.SortedSet;
 import java.util.TreeSet;
+
 import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.hbase.Coprocessor;
 import org.apache.hadoop.hbase.DoNotRetryIOException;
@@ -452,6 +453,16 @@ final class RSGroupInfoManagerImpl implements RSGroupInfoManager {
     return getRSGroup(RSGroupInfo.DEFAULT_GROUP);
   }
 
+  @Override
+  public void updateRSGroupConfig(String groupName, Map<String, String> configuration)
+      throws IOException {
+    RSGroupInfo rsGroupInfo = getRSGroupInfo(groupName);
+    new HashSet<>(rsGroupInfo.getConfiguration().keySet())
+        .forEach(rsGroupInfo::removeConfiguration);
+    configuration.forEach(rsGroupInfo::setConfiguration);
+    flushConfig();
+  }
+
   List<RSGroupInfo> retrieveGroupListFromGroupTable() throws IOException {
     List<RSGroupInfo> rsGroupInfoList = Lists.newArrayList();
     try (Table table = conn.getTable(RSGROUP_TABLE_NAME);
diff --git a/hbase-rsgroup/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupProtobufUtil.java b/hbase-rsgroup/src/main/java/org/apache/hadoop/hbase/rsgroup/RSGroupProtobufUtil.java
index 9b6ba1c..9f092c5 100644
--- 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
@@ -20,11 +20,13 @@ package org.apache.hadoop.hbase.rsgroup;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.stream.Collectors;
 
 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.HBaseProtos.NameStringPair;
 import org.apache.hadoop.hbase.protobuf.generated.RSGroupProtos;
 import org.apache.hadoop.hbase.protobuf.generated.TableProtos;
 import org.apache.yetus.audience.InterfaceAudience;
@@ -35,14 +37,16 @@ final class RSGroupProtobufUtil {
   }
 
   static RSGroupInfo toGroupInfo(RSGroupProtos.RSGroupInfo proto) {
-    RSGroupInfo RSGroupInfo = new RSGroupInfo(proto.getName());
+    RSGroupInfo rsGroupInfo = new RSGroupInfo(proto.getName());
     for(HBaseProtos.ServerName el: proto.getServersList()) {
-      RSGroupInfo.addServer(Address.fromParts(el.getHostName(), el.getPort()));
+      rsGroupInfo.addServer(Address.fromParts(el.getHostName(), el.getPort()));
     }
     for(TableProtos.TableName pTableName: proto.getTablesList()) {
-      RSGroupInfo.addTable(ProtobufUtil.toTableName(pTableName));
+      rsGroupInfo.addTable(ProtobufUtil.toTableName(pTableName));
     }
-    return RSGroupInfo;
+    proto.getConfigurationList().forEach(pair ->
+        rsGroupInfo.setConfiguration(pair.getName(), pair.getValue()));
+    return rsGroupInfo;
   }
 
   static RSGroupProtos.RSGroupInfo toProtoGroupInfo(RSGroupInfo pojo) {
@@ -57,8 +61,11 @@ final class RSGroupProtobufUtil {
           .setPort(el.getPort())
           .build());
     }
+    List<NameStringPair> configuration = pojo.getConfiguration().entrySet()
+        .stream().map(entry -> NameStringPair.newBuilder()
+            .setName(entry.getKey()).setValue(entry.getValue()).build())
+        .collect(Collectors.toList());
     return RSGroupProtos.RSGroupInfo.newBuilder().setName(pojo.getName())
-        .addAllServers(hostports)
-        .addAllTables(tables).build();
+        .addAllServers(hostports).addAllTables(tables).addAllConfiguration(configuration).build();
   }
 }
diff --git a/hbase-rsgroup/src/main/protobuf/RSGroupAdmin.proto b/hbase-rsgroup/src/main/protobuf/RSGroupAdmin.proto
index 10f6570..54add90 100644
--- a/hbase-rsgroup/src/main/protobuf/RSGroupAdmin.proto
+++ b/hbase-rsgroup/src/main/protobuf/RSGroupAdmin.proto
@@ -131,6 +131,14 @@ message RenameRSGroupRequest {
 message RenameRSGroupResponse {
 }
 
+message UpdateRSGroupConfigRequest {
+  required string group_name = 1;
+  repeated NameStringPair configuration = 2;
+}
+
+message UpdateRSGroupConfigResponse {
+}
+
 service RSGroupAdminService {
   rpc GetRSGroupInfo(GetRSGroupInfoRequest)
     returns (GetRSGroupInfoResponse);
@@ -167,4 +175,7 @@ service RSGroupAdminService {
 
   rpc RenameRSGroup(RenameRSGroupRequest)
     returns (RenameRSGroupResponse);
+
+  rpc UpdateRSGroupConfig(UpdateRSGroupConfigRequest)
+    returns (UpdateRSGroupConfigResponse);
 }
diff --git a/hbase-rsgroup/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupConfig.java b/hbase-rsgroup/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupConfig.java
new file mode 100644
index 0000000..79d0a1e
--- /dev/null
+++ b/hbase-rsgroup/src/test/java/org/apache/hadoop/hbase/rsgroup/TestRSGroupConfig.java
@@ -0,0 +1,94 @@
+/**
+ * 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.assertNotNull;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.hadoop.hbase.HBaseClassTestRule;
+import org.apache.hadoop.hbase.testclassification.MediumTests;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.rules.TestName;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.apache.hbase.thirdparty.com.google.common.collect.Maps;
+
+@Category(MediumTests.class)
+public class TestRSGroupConfig extends TestRSGroupsBase {
+
+  @ClassRule
+  public static final HBaseClassTestRule CLASS_RULE =
+      HBaseClassTestRule.forClass(TestRSGroupConfig.class);
+
+  @Rule
+  public TestName name = new TestName();
+
+  protected static final Logger LOG = LoggerFactory.getLogger(TestRSGroupConfig.class);
+
+  @BeforeClass
+  public static void setUpBeforeClass() throws Exception {
+    TestRSGroupsBase.setUpTestBeforeClass();
+  }
+
+  @AfterClass
+  public static void tearDownAfterClass() throws Exception {
+    TestRSGroupsBase.tearDownAfterClass();
+  }
+
+  @Test
+  public void testSetDefaultGroupConfiguration() throws IOException {
+    testSetConfiguration(RSGroupInfo.DEFAULT_GROUP);
+  }
+
+  @Test
+  public void testSetNonDefaultGroupConfiguration() throws IOException {
+    String group = getGroupName(name.getMethodName());
+    rsGroupAdmin.addRSGroup(group);
+    testSetConfiguration(RSGroupInfo.DEFAULT_GROUP);
+    rsGroupAdmin.removeRSGroup(group);
+  }
+
+  private void testSetConfiguration(String group) throws IOException {
+    Map<String, String> configuration = new HashMap<>();
+    configuration.put("aaa", "111");
+    configuration.put("bbb", "222");
+    rsGroupAdmin.updateRSGroupConfig(group, configuration);
+    RSGroupInfo rsGroup = rsGroupAdmin.getRSGroupInfo(group);
+    Map<String, String> configFromGroup = Maps.newHashMap(rsGroup.getConfiguration());
+    assertNotNull(configFromGroup);
+    assertEquals(2, configFromGroup.size());
+    assertEquals("111", configFromGroup.get("aaa"));
+    // unset configuration
+    rsGroupAdmin.updateRSGroupConfig(group, null);
+    rsGroup = rsGroupAdmin.getRSGroupInfo(group);
+    configFromGroup = rsGroup.getConfiguration();
+    assertNotNull(configFromGroup);
+    assertEquals(0, configFromGroup.size());
+  }
+
+}
\ No newline at end of file
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 923a77d..faa0bc0 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
@@ -125,6 +125,13 @@ public class VerifyingRSGroupAdminClient implements RSGroupAdmin {
     verify();
   }
 
+  @Override
+  public void updateRSGroupConfig(String groupName, Map<String, String> configuration)
+      throws IOException {
+    wrapped.updateRSGroupConfig(groupName, configuration);
+    verify();
+  }
+
   public void verify() throws IOException {
     Map<String, RSGroupInfo> groupMap = Maps.newHashMap();
     Set<RSGroupInfo> zList = Sets.newHashSet();
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 48c259b..4ea80de 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
@@ -20,6 +20,7 @@ package org.apache.hadoop.hbase.coprocessor;
 
 import java.io.IOException;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 import org.apache.hadoop.hbase.ClusterMetrics;
 import org.apache.hadoop.hbase.HBaseInterfaceAudience;
@@ -1646,4 +1647,22 @@ public interface MasterObserver {
   default void postRenameRSGroup(final ObserverContext<MasterCoprocessorEnvironment> ctx,
       final String oldName, final String newName) throws IOException {
   }
+
+  /**
+   * Called before update rsgroup config.
+   * @param ctx the environment to interact with the framework and master
+   * @param groupName the group name
+   * @param configuration new configuration of the group name to be set
+   */
+  default void preUpdateRSGroupConfig(final ObserverContext<MasterCoprocessorEnvironment> ctx,
+      final String groupName, final Map<String, String> configuration) throws IOException {}
+
+  /**
+   * Called after update rsgroup config.
+   * @param ctx the environment to interact with the framework and master
+   * @param groupName the group name
+   * @param configuration new configuration of the group name to be set
+   */
+  default void postUpdateRSGroupConfig(final ObserverContext<MasterCoprocessorEnvironment> ctx,
+      final String groupName, final Map<String, String> configuration) throws IOException {}
 }
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 236eda0..2a29c72 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
@@ -22,6 +22,7 @@ import com.google.protobuf.Service;
 import java.io.IOException;
 import java.lang.reflect.InvocationTargetException;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.hbase.ClusterMetrics;
@@ -1493,6 +1494,26 @@ public class MasterCoprocessorHost
     });
   }
 
+  public void preUpdateRSGroupConfig(final String groupName,
+                                     final Map<String, String> configuration) throws IOException {
+    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
+      @Override
+      protected void call(MasterObserver observer) throws IOException {
+        observer.preUpdateRSGroupConfig(this, groupName, configuration);
+      }
+    });
+  }
+
+  public void postUpdateRSGroupConfig(final String groupName,
+                                      final Map<String, String> configuration) throws IOException {
+    execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
+      @Override
+      protected void call(MasterObserver observer) throws IOException {
+        observer.postUpdateRSGroupConfig(this, groupName, configuration);
+      }
+    });
+  }
+
   public void preAddReplicationPeer(final String peerId, final ReplicationPeerConfig peerConfig)
       throws IOException {
     execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() {
diff --git a/hbase-shell/src/main/ruby/hbase/rsgroup_admin.rb b/hbase-shell/src/main/ruby/hbase/rsgroup_admin.rb
index 76a480b..e163875 100644
--- a/hbase-shell/src/main/ruby/hbase/rsgroup_admin.rb
+++ b/hbase-shell/src/main/ruby/hbase/rsgroup_admin.rb
@@ -185,5 +185,42 @@ module Hbase
     def rename_rsgroup(oldname, newname)
       @admin.renameRSGroup(oldname, newname)
     end
+
+    #----------------------------------------------------------------------------------------------
+    # modify a rsgroup configuration
+    def alter_rsgroup_config(rsgroup_name, *args)
+      # Fail if table name is not a string
+      raise(ArgumentError, 'RSGroup name must be of type String') unless rsgroup_name.is_a?(String)
+
+      group = @admin.getRSGroupInfo(rsgroup_name)
+
+      raise(ArgumentError, 'RSGroup does not exist') unless group
+
+      configuration = java.util.HashMap.new
+      configuration.putAll(group.getConfiguration)
+
+      # Flatten params array
+      args = args.flatten.compact
+
+      # Start defining the table
+      args.each do |arg|
+        unless arg.is_a?(Hash)
+          raise(ArgumentError, "#{arg.class} of #{arg.inspect} is not of Hash type")
+        end
+        method = arg[METHOD]
+        if method == 'unset'
+          configuration.remove(arg[NAME])
+        elsif method == 'set'
+          arg.delete(METHOD)
+          for k, v in arg
+            v = v.to_s unless v.nil?
+            configuration.put(k, v)
+          end
+        else
+          raise(ArgumentError, "Unknown method #{method}")
+        end
+      end
+      @admin.updateRSGroupConfig(rsgroup_name, configuration)
+    end
   end
 end
diff --git a/hbase-shell/src/main/ruby/shell.rb b/hbase-shell/src/main/ruby/shell.rb
index 6303d6b..b638bbe 100644
--- a/hbase-shell/src/main/ruby/shell.rb
+++ b/hbase-shell/src/main/ruby/shell.rb
@@ -508,5 +508,7 @@ Shell.load_command_group(
     get_table_rsgroup
     remove_servers_rsgroup
     rename_rsgroup
+    alter_rsgroup_config
+    show_rsgroup_config
   ]
 )
diff --git a/hbase-shell/src/main/ruby/shell/commands/alter_rsgroup_config.rb b/hbase-shell/src/main/ruby/shell/commands/alter_rsgroup_config.rb
new file mode 100644
index 0000000..a8489b8
--- /dev/null
+++ b/hbase-shell/src/main/ruby/shell/commands/alter_rsgroup_config.rb
@@ -0,0 +1,38 @@
+#
+#
+# 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.
+#
+
+module Shell
+  module Commands
+    class AlterRsgroupConfig < Command
+      def help
+        <<-EOF
+Alter RSGroup configuration.
+Example:
+    hbase> alter_rsgroup_config 'grp1', {METHOD => 'set', 'PROPERTY_NAME' => 'PROPERTY_VALUE'}
+To delete a property:
+    hbase> alter_rsgroup_config 'grp1', {METHOD => 'unset', NAME=>'PROPERTY_NAME'}
+EOF
+      end
+
+      def command(group, args)
+        rsgroup_admin.alter_rsgroup_config(group, args)
+      end
+    end
+  end
+end
\ No newline at end of file
diff --git a/hbase-shell/src/main/ruby/shell/commands/show_rsgroup_config.rb b/hbase-shell/src/main/ruby/shell/commands/show_rsgroup_config.rb
new file mode 100644
index 0000000..54344a1
--- /dev/null
+++ b/hbase-shell/src/main/ruby/shell/commands/show_rsgroup_config.rb
@@ -0,0 +1,41 @@
+#
+#
+# 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.
+#
+
+module Shell
+  module Commands
+    class ShowRsgroupConfig < Command
+      def help
+        <<-EOF
+Show the configuration of a special RSGroup.
+Example:
+    hbase> show_rsgroup_config 'group'
+EOF
+      end
+
+      def command(group)
+        formatter.header(%w['KEY' 'VALUE'])
+        config = rsgroup_admin.get_rsgroup(group).getConfiguration
+        config.each { |key, val|
+          formatter.row([key, val])
+        }
+        formatter.footer(config.size)
+      end
+    end
+  end
+end
\ No newline at end of file
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 5e7f37d..8b1fba4 100644
--- a/hbase-shell/src/test/ruby/shell/rsgroup_shell_test.rb
+++ b/hbase-shell/src/test/ruby/shell/rsgroup_shell_test.rb
@@ -142,5 +142,19 @@ module Hbase
       assert_equal(1, @rsgroup_admin.getRSGroupInfo(new_rs_group_name).getTables.count)
       assert_equal(table_name, @rsgroup_admin.getRSGroupInfo(new_rs_group_name).getTables.iterator.next.toString)
     end
+
+    define_test 'Test alter rsgroup configuration' do
+      group_name = 'grp1'
+      @shell.command('add_rsgroup', group_name)
+      assert_not_nil(@rsgroup_admin.getRSGroupInfo(group_name))
+
+      @hbase.rsgroup_admin.alter_rsgroup_config(group_name, {'METHOD' => 'set', 'a' => 'a'})
+      assert_equal(1, @rsgroup_admin.getRSGroupInfo(group_name).getConfiguration.size)
+      @hbase.rsgroup_admin.alter_rsgroup_config(group_name, {'METHOD' => 'unset', 'NAME' => 'a'})
+      assert_equal(0, @rsgroup_admin.getRSGroupInfo(group_name).getConfiguration.size)
+
+      @shell.command('remove_rsgroup', group_name)
+      assert_nil(@rsgroup_admin.getRSGroupInfo(group_name))
+    end
   end
 end