From 7e228c3540ef0f1648fb263969c3c5d5c38cfcc1 Mon Sep 17 00:00:00 2001 From: meiyi Date: Thu, 10 Jan 2019 14:52:38 +0800 Subject: [PATCH] HBASE-21064 Support set region server quota and allow exceed user/table/ns quota when rs has available quota --- .../apache/hadoop/hbase/quotas/QuotaFilter.java | 17 +++ .../apache/hadoop/hbase/quotas/QuotaSettings.java | 29 ++++- .../hadoop/hbase/quotas/QuotaSettingsFactory.java | 123 ++++++++++++++------- .../apache/hadoop/hbase/quotas/QuotaTableUtil.java | 74 +++++++++++-- .../hadoop/hbase/quotas/SpaceLimitSettings.java | 10 +- .../hadoop/hbase/quotas/ThrottleSettings.java | 22 ++-- .../hadoop/hbase/shaded/protobuf/ProtobufUtil.java | 23 +++- .../quotas/TestQuotaGlobalsSettingsBypass.java | 50 ++++----- .../hadoop/hbase/quotas/TestThrottleSettings.java | 12 +- .../src/main/protobuf/Master.proto | 1 + .../src/main/protobuf/Quota.proto | 1 + .../hadoop/hbase/coprocessor/MasterObserver.java | 18 +++ .../hadoop/hbase/master/MasterCoprocessorHost.java | 20 ++++ .../hbase/quotas/AllowExceedOperationQuota.java | 74 +++++++++++++ .../hadoop/hbase/quotas/DefaultOperationQuota.java | 34 +++--- .../hadoop/hbase/quotas/GlobalQuotaSettings.java | 5 +- .../hbase/quotas/GlobalQuotaSettingsImpl.java | 30 ++--- .../hadoop/hbase/quotas/MasterQuotaManager.java | 63 +++++++++-- .../hadoop/hbase/quotas/NoopQuotaLimiter.java | 5 + .../org/apache/hadoop/hbase/quotas/QuotaCache.java | 41 ++++++- .../apache/hadoop/hbase/quotas/QuotaLimiter.java | 3 + .../org/apache/hadoop/hbase/quotas/QuotaUtil.java | 33 +++++- .../apache/hadoop/hbase/quotas/RateLimiter.java | 23 +++- .../hbase/quotas/RegionServerRpcQuotaManager.java | 25 +++-- .../hadoop/hbase/quotas/TimeBasedLimiter.java | 14 ++- .../hbase/security/access/AccessController.java | 6 + .../hbase/quotas/TestGlobalQuotaSettingsImpl.java | 10 +- .../apache/hadoop/hbase/quotas/TestQuotaAdmin.java | 46 ++++++-- .../hadoop/hbase/quotas/TestQuotaThrottle.java | 56 +++++++++- .../security/access/TestAccessController.java | 13 +++ hbase-shell/src/main/ruby/hbase/quotas.rb | 16 ++- hbase-shell/src/main/ruby/hbase_constants.rb | 1 + .../src/main/ruby/shell/commands/set_quota.rb | 6 +- hbase-shell/src/test/ruby/hbase/quotas_test.rb | 13 +++ 34 files changed, 731 insertions(+), 186 deletions(-) create mode 100644 hbase-server/src/main/java/org/apache/hadoop/hbase/quotas/AllowExceedOperationQuota.java diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/quotas/QuotaFilter.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/quotas/QuotaFilter.java index 30cdaf1..d5190ad 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/quotas/QuotaFilter.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/quotas/QuotaFilter.java @@ -33,6 +33,7 @@ public class QuotaFilter { private String namespaceRegex; private String tableRegex; private String userRegex; + private String regionServerRegex; public QuotaFilter() { } @@ -71,6 +72,17 @@ public class QuotaFilter { } /** + * Set the region server filter regex + * @param regex the region server filter + * @return the quota filter object + */ + public QuotaFilter setRegionServerFilter(final String regex) { + this.regionServerRegex = regex; + hasFilters |= StringUtils.isNotEmpty(regex); + return this; + } + + /** * Add a type to the filter list * @param type the type to filter on * @return the quota filter object @@ -105,4 +117,9 @@ public class QuotaFilter { public String getUserFilter() { return userRegex; } + + /** @return the RegionServer filter regex */ + public String getRegionServerFilter() { + return regionServerRegex; + } } diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/quotas/QuotaSettings.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/quotas/QuotaSettings.java index 3351a25..950eb13 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/quotas/QuotaSettings.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/quotas/QuotaSettings.java @@ -22,24 +22,26 @@ import java.util.Objects; import java.util.concurrent.TimeUnit; import org.apache.hadoop.hbase.TableName; +import org.apache.hadoop.hbase.quotas.QuotaSettingsFactory.QuotaGlobalsSettingsBypass; import org.apache.yetus.audience.InterfaceAudience; import org.apache.hbase.thirdparty.com.google.protobuf.TextFormat; import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.SetQuotaRequest; -import org.apache.hadoop.hbase.quotas.QuotaSettingsFactory.QuotaGlobalsSettingsBypass; @InterfaceAudience.Public public abstract class QuotaSettings { private final String userName; private final String namespace; private final TableName tableName; + private final String regionServer; - protected QuotaSettings(final String userName, final TableName tableName, - final String namespace) { + protected QuotaSettings(final String userName, final TableName tableName, final String namespace, + final String regionServer) { this.userName = userName; this.namespace = namespace; this.tableName = tableName; + this.regionServer = regionServer; } public abstract QuotaType getQuotaType(); @@ -56,6 +58,10 @@ public abstract class QuotaSettings { return namespace; } + public String getRegionServer() { + return regionServer; + } + /** * Converts the protocol buffer request into a QuotaSetting POJO. Arbitrarily * enforces that the request only contain one "limit", despite the message @@ -78,6 +84,10 @@ public abstract class QuotaSettings { if (request.hasNamespace()) { namespace = request.getNamespace(); } + String regionServer = null; + if (request.hasRegionServer()) { + regionServer = request.getRegionServer(); + } if (request.hasBypassGlobals()) { // Make sure we don't have either of the two below limits also included if (request.hasSpaceLimit() || request.hasThrottle()) { @@ -85,7 +95,7 @@ public abstract class QuotaSettings { "SetQuotaRequest has multiple limits: " + TextFormat.shortDebugString(request)); } return new QuotaGlobalsSettingsBypass( - username, tableName, namespace, request.getBypassGlobals()); + username, tableName, namespace, regionServer, request.getBypassGlobals()); } else if (request.hasSpaceLimit()) { // Make sure we don't have the below limit as well if (request.hasThrottle()) { @@ -100,7 +110,7 @@ public abstract class QuotaSettings { return QuotaSettingsFactory.fromSpace( tableName, namespace, request.getSpaceLimit().getQuota()); } else if (request.hasThrottle()) { - return new ThrottleSettings(username, tableName, namespace, request.getThrottle()); + return new ThrottleSettings(username, tableName, namespace, regionServer, request.getThrottle()); } else { throw new IllegalStateException("Unhandled SetRequestRequest state"); } @@ -123,6 +133,9 @@ public abstract class QuotaSettings { if (settings.getNamespace() != null) { builder.setNamespace(settings.getNamespace()); } + if (settings.getRegionServer() != null) { + builder.setRegionServer(settings.getRegionServer()); + } settings.setupSetQuotaRequest(builder); return builder.build(); } @@ -152,6 +165,9 @@ public abstract class QuotaSettings { builder.append(namespace); builder.append("', "); } + if (regionServer != null) { + builder.append("REGIONSERVER => ").append(regionServer).append(","); + } return builder.toString(); } @@ -203,5 +219,8 @@ public abstract class QuotaSettings { if (!Objects.equals(getNamespace(), mergee.getNamespace())) { throw new IllegalArgumentException("Mismatched namespace on settings to merge"); } + if (!Objects.equals(getRegionServer(), mergee.getRegionServer())) { + throw new IllegalArgumentException("Mismatched regionServer on settings to merge"); + } } } diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/quotas/QuotaSettingsFactory.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/quotas/QuotaSettingsFactory.java index 14d1ad3..54056ea 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/quotas/QuotaSettingsFactory.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/quotas/QuotaSettingsFactory.java @@ -25,8 +25,8 @@ import java.util.concurrent.TimeUnit; import org.apache.hadoop.hbase.TableName; import org.apache.yetus.audience.InterfaceAudience; -import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.SetQuotaRequest; import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil; +import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.SetQuotaRequest; import org.apache.hadoop.hbase.shaded.protobuf.generated.QuotaProtos; import org.apache.hadoop.hbase.shaded.protobuf.generated.QuotaProtos.Quotas; import org.apache.hadoop.hbase.shaded.protobuf.generated.QuotaProtos.SpaceQuota; @@ -37,8 +37,8 @@ public class QuotaSettingsFactory { private final boolean bypassGlobals; QuotaGlobalsSettingsBypass(final String userName, final TableName tableName, - final String namespace, final boolean bypassGlobals) { - super(userName, tableName, namespace); + final String namespace, final String regionServer, final boolean bypassGlobals) { + super(userName, tableName, namespace, regionServer); this.bypassGlobals = bypassGlobals; } @@ -80,35 +80,42 @@ public class QuotaSettingsFactory { * QuotaSettings from the Quotas object */ static List fromUserQuotas(final String userName, final Quotas quotas) { - return fromQuotas(userName, null, null, quotas); + return fromQuotas(userName, null, null, null, quotas); } static List fromUserQuotas(final String userName, final TableName tableName, final Quotas quotas) { - return fromQuotas(userName, tableName, null, quotas); + return fromQuotas(userName, tableName, null, null, quotas); } static List fromUserQuotas(final String userName, final String namespace, final Quotas quotas) { - return fromQuotas(userName, null, namespace, quotas); + return fromQuotas(userName, null, namespace, null, quotas); } static List fromTableQuotas(final TableName tableName, final Quotas quotas) { - return fromQuotas(null, tableName, null, quotas); + return fromQuotas(null, tableName, null, null, quotas); } static List fromNamespaceQuotas(final String namespace, final Quotas quotas) { - return fromQuotas(null, null, namespace, quotas); + return fromQuotas(null, null, namespace, null, quotas); + } + + static List fromRegionServerQuotas(final String regionServer, + final Quotas quotas) { + return fromQuotas(null, null, null, regionServer, quotas); } private static List fromQuotas(final String userName, final TableName tableName, - final String namespace, final Quotas quotas) { + final String namespace, final String regionServer, final Quotas quotas) { List settings = new ArrayList<>(); if (quotas.hasThrottle()) { - settings.addAll(fromThrottle(userName, tableName, namespace, quotas.getThrottle())); + settings + .addAll(fromThrottle(userName, tableName, namespace, regionServer, quotas.getThrottle())); } if (quotas.getBypassGlobals() == true) { - settings.add(new QuotaGlobalsSettingsBypass(userName, tableName, namespace, true)); + settings + .add(new QuotaGlobalsSettingsBypass(userName, tableName, namespace, regionServer, true)); } if (quotas.hasSpace()) { settings.add(fromSpace(tableName, namespace, quotas.getSpace())); @@ -116,43 +123,44 @@ public class QuotaSettingsFactory { return settings; } - protected static List fromThrottle(final String userName, final TableName tableName, - final String namespace, final QuotaProtos.Throttle throttle) { + protected static List fromThrottle(final String userName, + final TableName tableName, final String namespace, final String regionServer, + final QuotaProtos.Throttle throttle) { List settings = new ArrayList<>(); if (throttle.hasReqNum()) { - settings.add(ThrottleSettings.fromTimedQuota(userName, tableName, namespace, - ThrottleType.REQUEST_NUMBER, throttle.getReqNum())); + settings.add(ThrottleSettings.fromTimedQuota(userName, tableName, namespace, regionServer, + ThrottleType.REQUEST_NUMBER, throttle.getReqNum())); } if (throttle.hasReqSize()) { - settings.add(ThrottleSettings.fromTimedQuota(userName, tableName, namespace, - ThrottleType.REQUEST_SIZE, throttle.getReqSize())); + settings.add(ThrottleSettings.fromTimedQuota(userName, tableName, namespace, regionServer, + ThrottleType.REQUEST_SIZE, throttle.getReqSize())); } if (throttle.hasWriteNum()) { - settings.add(ThrottleSettings.fromTimedQuota(userName, tableName, namespace, - ThrottleType.WRITE_NUMBER, throttle.getWriteNum())); + settings.add(ThrottleSettings.fromTimedQuota(userName, tableName, namespace, regionServer, + ThrottleType.WRITE_NUMBER, throttle.getWriteNum())); } if (throttle.hasWriteSize()) { - settings.add(ThrottleSettings.fromTimedQuota(userName, tableName, namespace, - ThrottleType.WRITE_SIZE, throttle.getWriteSize())); + settings.add(ThrottleSettings.fromTimedQuota(userName, tableName, namespace, regionServer, + ThrottleType.WRITE_SIZE, throttle.getWriteSize())); } if (throttle.hasReadNum()) { - settings.add(ThrottleSettings.fromTimedQuota(userName, tableName, namespace, - ThrottleType.READ_NUMBER, throttle.getReadNum())); + settings.add(ThrottleSettings.fromTimedQuota(userName, tableName, namespace, regionServer, + ThrottleType.READ_NUMBER, throttle.getReadNum())); } if (throttle.hasReadSize()) { - settings.add(ThrottleSettings.fromTimedQuota(userName, tableName, namespace, - ThrottleType.READ_SIZE, throttle.getReadSize())); + settings.add(ThrottleSettings.fromTimedQuota(userName, tableName, namespace, regionServer, + ThrottleType.READ_SIZE, throttle.getReadSize())); } if (throttle.hasReqCapacityUnit()) { - settings.add(ThrottleSettings.fromTimedQuota(userName, tableName, namespace, + settings.add(ThrottleSettings.fromTimedQuota(userName, tableName, namespace, regionServer, ThrottleType.REQUEST_CAPACITY_UNIT, throttle.getReqCapacityUnit())); } if (throttle.hasReadCapacityUnit()) { - settings.add(ThrottleSettings.fromTimedQuota(userName, tableName, namespace, + settings.add(ThrottleSettings.fromTimedQuota(userName, tableName, namespace, regionServer, ThrottleType.READ_CAPACITY_UNIT, throttle.getReadCapacityUnit())); } if (throttle.hasWriteCapacityUnit()) { - settings.add(ThrottleSettings.fromTimedQuota(userName, tableName, namespace, + settings.add(ThrottleSettings.fromTimedQuota(userName, tableName, namespace, regionServer, ThrottleType.WRITE_CAPACITY_UNIT, throttle.getWriteCapacityUnit())); } return settings; @@ -195,7 +203,7 @@ public class QuotaSettingsFactory { */ public static QuotaSettings throttleUser(final String userName, final ThrottleType type, final long limit, final TimeUnit timeUnit) { - return throttle(userName, null, null, type, limit, timeUnit); + return throttle(userName, null, null, null, type, limit, timeUnit); } /** @@ -210,7 +218,7 @@ public class QuotaSettingsFactory { */ public static QuotaSettings throttleUser(final String userName, final TableName tableName, final ThrottleType type, final long limit, final TimeUnit timeUnit) { - return throttle(userName, tableName, null, type, limit, timeUnit); + return throttle(userName, tableName, null, null, type, limit, timeUnit); } /** @@ -225,7 +233,7 @@ public class QuotaSettingsFactory { */ public static QuotaSettings throttleUser(final String userName, final String namespace, final ThrottleType type, final long limit, final TimeUnit timeUnit) { - return throttle(userName, null, namespace, type, limit, timeUnit); + return throttle(userName, null, namespace, null, type, limit, timeUnit); } /** @@ -235,7 +243,7 @@ public class QuotaSettingsFactory { * @return the quota settings */ public static QuotaSettings unthrottleUser(final String userName) { - return throttle(userName, null, null, null, 0, null); + return throttle(userName, null, null, null, null, 0, null); } /** @@ -246,7 +254,7 @@ public class QuotaSettingsFactory { * @return the quota settings */ public static QuotaSettings unthrottleUser(final String userName, final TableName tableName) { - return throttle(userName, tableName, null, null, 0, null); + return throttle(userName, tableName, null, null, null, 0, null); } /** @@ -257,7 +265,7 @@ public class QuotaSettingsFactory { * @return the quota settings */ public static QuotaSettings unthrottleUser(final String userName, final String namespace) { - return throttle(userName, null, namespace, null, 0, null); + return throttle(userName, null, namespace, null, null, 0, null); } /** @@ -271,7 +279,7 @@ public class QuotaSettingsFactory { */ public static QuotaSettings throttleTable(final TableName tableName, final ThrottleType type, final long limit, final TimeUnit timeUnit) { - return throttle(null, tableName, null, type, limit, timeUnit); + return throttle(null, tableName, null, null, type, limit, timeUnit); } /** @@ -281,7 +289,7 @@ public class QuotaSettingsFactory { * @return the quota settings */ public static QuotaSettings unthrottleTable(final TableName tableName) { - return throttle(null, tableName, null, null, 0, null); + return throttle(null, tableName, null, null, null, 0, null); } /** @@ -295,7 +303,7 @@ public class QuotaSettingsFactory { */ public static QuotaSettings throttleNamespace(final String namespace, final ThrottleType type, final long limit, final TimeUnit timeUnit) { - return throttle(null, null, namespace, type, limit, timeUnit); + return throttle(null, null, namespace, null, type, limit, timeUnit); } /** @@ -305,12 +313,45 @@ public class QuotaSettingsFactory { * @return the quota settings */ public static QuotaSettings unthrottleNamespace(final String namespace) { - return throttle(null, null, namespace, null, 0, null); + return throttle(null, null, namespace, null, null, 0, null); + } + + /** + * Throttle the specified region server. + * + * @param regionServer the region server to throttle + * @param type the type of throttling + * @param limit the allowed number of request/data per timeUnit + * @param timeUnit the limit time unit + * @param allowExceed if allow exceed quota + * @return the quota settings + */ + public static QuotaSettings throttleRegionServer(final String regionServer, + final ThrottleType type, final long limit, final TimeUnit timeUnit, boolean allowExceed) { + QuotaProtos.ThrottleRequest.Builder builder = QuotaProtos.ThrottleRequest.newBuilder(); + if (type != null) { + builder.setType(ProtobufUtil.toProtoThrottleType(type)); + } + if (timeUnit != null) { + builder.setTimedQuota( + ProtobufUtil.toTimedQuota(limit, timeUnit, QuotaScope.MACHINE, allowExceed)); + } + return new ThrottleSettings(null, null, null, regionServer, builder.build()); + } + + /** + * Remove the throttling for the specified region server. + * + * @param regionServer the region Server + * @return the quota settings + */ + public static QuotaSettings unthrottleRegionServer(final String regionServer) { + return throttle(null, null, null, regionServer, null, 0, null); } /* Throttle helper */ private static QuotaSettings throttle(final String userName, final TableName tableName, - final String namespace, final ThrottleType type, final long limit, + final String namespace, final String regionServer, final ThrottleType type, final long limit, final TimeUnit timeUnit) { QuotaProtos.ThrottleRequest.Builder builder = QuotaProtos.ThrottleRequest.newBuilder(); if (type != null) { @@ -319,7 +360,7 @@ public class QuotaSettingsFactory { if (timeUnit != null) { builder.setTimedQuota(ProtobufUtil.toTimedQuota(limit, timeUnit, QuotaScope.MACHINE)); } - return new ThrottleSettings(userName, tableName, namespace, builder.build()); + return new ThrottleSettings(userName, tableName, namespace, regionServer, builder.build()); } /* ========================================================================== @@ -334,7 +375,7 @@ public class QuotaSettingsFactory { * @return the quota settings */ public static QuotaSettings bypassGlobals(final String userName, final boolean bypassGlobals) { - return new QuotaGlobalsSettingsBypass(userName, null, null, bypassGlobals); + return new QuotaGlobalsSettingsBypass(userName, null, null, null, bypassGlobals); } /* ========================================================================== diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/quotas/QuotaTableUtil.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/quotas/QuotaTableUtil.java index 419091d..fcf9338 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/quotas/QuotaTableUtil.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/quotas/QuotaTableUtil.java @@ -35,10 +35,6 @@ import org.apache.hadoop.hbase.CompareOperator; import org.apache.hadoop.hbase.NamespaceDescriptor; import org.apache.hadoop.hbase.ServerName; import org.apache.hadoop.hbase.TableName; -import org.apache.yetus.audience.InterfaceAudience; -import org.apache.yetus.audience.InterfaceStability; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.apache.hadoop.hbase.client.ClusterConnection; import org.apache.hadoop.hbase.client.Connection; import org.apache.hadoop.hbase.client.Get; @@ -55,9 +51,14 @@ import org.apache.hadoop.hbase.filter.QualifierFilter; import org.apache.hadoop.hbase.filter.RegexStringComparator; import org.apache.hadoop.hbase.filter.RowFilter; import org.apache.hadoop.hbase.protobuf.ProtobufMagic; +import org.apache.hadoop.hbase.util.Bytes; +import org.apache.yetus.audience.InterfaceAudience; +import org.apache.yetus.audience.InterfaceStability; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import org.apache.hbase.thirdparty.com.google.protobuf.ByteString; import org.apache.hbase.thirdparty.com.google.protobuf.InvalidProtocolBufferException; -import org.apache.hbase.thirdparty.com.google.protobuf.TextFormat; import org.apache.hbase.thirdparty.com.google.protobuf.UnsafeByteOperations; import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil; import org.apache.hadoop.hbase.shaded.protobuf.generated.HBaseProtos; @@ -69,7 +70,6 @@ import org.apache.hadoop.hbase.shaded.protobuf.generated.QuotaProtos.GetSpaceQuo import org.apache.hadoop.hbase.shaded.protobuf.generated.QuotaProtos.GetSpaceQuotaRegionSizesResponse.RegionSizes; import org.apache.hadoop.hbase.shaded.protobuf.generated.QuotaProtos.Quotas; import org.apache.hadoop.hbase.shaded.protobuf.generated.QuotaProtos.SpaceQuota; -import org.apache.hadoop.hbase.util.Bytes; /** * Helper class to interact with the quota table. @@ -108,6 +108,7 @@ public class QuotaTableUtil { protected static final byte[] QUOTA_USER_ROW_KEY_PREFIX = Bytes.toBytes("u."); protected static final byte[] QUOTA_TABLE_ROW_KEY_PREFIX = Bytes.toBytes("t."); protected static final byte[] QUOTA_NAMESPACE_ROW_KEY_PREFIX = Bytes.toBytes("n."); + protected static final byte[] QUOTA_REGION_SERVER_ROW_KEY_PREFIX = Bytes.toBytes("r."); /* ========================================================================= * Quota "settings" helpers @@ -143,6 +144,11 @@ public class QuotaTableUtil { return getQuotas(connection, rowKey, QUOTA_QUALIFIER_SETTINGS); } + public static Quotas getRegionServerQuota(final Connection connection, final String regionServer) + throws IOException { + return getQuotas(connection, getRegionServerRowKey(regionServer)); + } + private static Quotas getQuotas(final Connection connection, final byte[] rowKey, final byte[] qualifier) throws IOException { Get get = new Get(rowKey); @@ -166,6 +172,12 @@ public class QuotaTableUtil { return get; } + public static Get makeGetForRegionServerQuotas(final String regionServer) { + Get get = new Get(getRegionServerRowKey(regionServer)); + get.addFamily(QUOTA_FAMILY_INFO); + return get; + } + public static Get makeGetForUserQuotas(final String user, final Iterable tables, final Iterable namespaces) { Get get = new Get(getUserRowKey(user)); @@ -229,6 +241,9 @@ public class QuotaTableUtil { } else if (StringUtils.isNotEmpty(filter.getNamespaceFilter())) { filterList.addFilter(new RowFilter(CompareOperator.EQUAL, new RegexStringComparator(getNamespaceRowKeyRegex(filter.getNamespaceFilter()), 0))); + } else if (StringUtils.isNotEmpty(filter.getRegionServerFilter())) { + filterList.addFilter(new RowFilter(CompareOperator.EQUAL, + new RegexStringComparator(getRegionServerRowKeyRegex(filter.getRegionServerFilter()), 0))); } return filterList; } @@ -327,8 +342,13 @@ public class QuotaTableUtil { throws IOException; } - public static interface QuotasVisitor extends UserQuotasVisitor, - TableQuotasVisitor, NamespaceQuotasVisitor { + public static interface RegionServerQuotasVisitor { + void visitRegionServerQuotas(final String regionServer, final Quotas quotas) + throws IOException; + } + + public static interface QuotasVisitor extends UserQuotasVisitor, TableQuotasVisitor, + NamespaceQuotasVisitor, RegionServerQuotasVisitor { } public static void parseResult(final Result result, final QuotasVisitor visitor) @@ -340,6 +360,8 @@ public class QuotaTableUtil { parseTableResult(result, visitor); } else if (isUserRowKey(row)) { parseUserResult(result, visitor); + } else if (isRegionServerRowKey(row)) { + parseRegionServerResult(result, visitor); } else { LOG.warn("unexpected row-key: " + Bytes.toString(row)); } @@ -373,6 +395,11 @@ public class QuotaTableUtil { public void visitNamespaceQuotas(String namespace, Quotas quotas) { quotaSettings.addAll(QuotaSettingsFactory.fromNamespaceQuotas(namespace, quotas)); } + + @Override + public void visitRegionServerQuotas(String regionServer, Quotas quotas) { + quotaSettings.addAll(QuotaSettingsFactory.fromRegionServerQuotas(regionServer, quotas)); + } }); } @@ -391,6 +418,21 @@ public class QuotaTableUtil { } } + public static void parseRegionServerResult(final Result result, + final RegionServerQuotasVisitor visitor) throws IOException { + String rs = getRegionServerFromRowKey(result.getRow()); + parseRegionServerResult(rs, result, visitor); + } + + protected static void parseRegionServerResult(final String regionServer, final Result result, + final RegionServerQuotasVisitor visitor) throws IOException { + byte[] data = result.getValue(QUOTA_FAMILY_INFO, QUOTA_QUALIFIER_SETTINGS); + if (data != null) { + Quotas quotas = quotasFromData(data); + visitor.visitRegionServerQuotas(regionServer, quotas); + } + } + public static void parseTableResult(final Result result, final TableQuotasVisitor visitor) throws IOException { TableName table = getTableFromRowKey(result.getRow()); @@ -711,6 +753,10 @@ public class QuotaTableUtil { return Bytes.add(QUOTA_NAMESPACE_ROW_KEY_PREFIX, Bytes.toBytes(namespace)); } + protected static byte[] getRegionServerRowKey(final String regionServer) { + return Bytes.add(QUOTA_REGION_SERVER_ROW_KEY_PREFIX, Bytes.toBytes(regionServer)); + } + protected static byte[] getSettingsQualifierForUserTable(final TableName tableName) { return Bytes.add(QUOTA_QUALIFIER_SETTINGS_PREFIX, tableName.getName()); } @@ -732,6 +778,10 @@ public class QuotaTableUtil { return getRowKeyRegEx(QUOTA_NAMESPACE_ROW_KEY_PREFIX, namespace); } + protected static String getRegionServerRowKeyRegex(final String regionServer) { + return getRowKeyRegEx(QUOTA_REGION_SERVER_ROW_KEY_PREFIX, regionServer); + } + private static String getRowKeyRegEx(final byte[] prefix, final String regex) { return '^' + Pattern.quote(Bytes.toString(prefix)) + regex + '$'; } @@ -754,6 +804,14 @@ public class QuotaTableUtil { return Bytes.toString(key, QUOTA_NAMESPACE_ROW_KEY_PREFIX.length); } + protected static boolean isRegionServerRowKey(final byte[] key) { + return Bytes.startsWith(key, QUOTA_REGION_SERVER_ROW_KEY_PREFIX); + } + + protected static String getRegionServerFromRowKey(final byte[] key) { + return Bytes.toString(key, QUOTA_REGION_SERVER_ROW_KEY_PREFIX.length); + } + protected static boolean isTableRowKey(final byte[] key) { return Bytes.startsWith(key, QUOTA_TABLE_ROW_KEY_PREFIX); } diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/quotas/SpaceLimitSettings.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/quotas/SpaceLimitSettings.java index 8b31e94..18dc427 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/quotas/SpaceLimitSettings.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/quotas/SpaceLimitSettings.java @@ -37,7 +37,7 @@ class SpaceLimitSettings extends QuotaSettings { private final SpaceLimitRequest proto; SpaceLimitSettings(TableName tableName, long sizeLimit, SpaceViolationPolicy violationPolicy) { - super(null, Objects.requireNonNull(tableName), null); + super(null, Objects.requireNonNull(tableName), null, null); if (sizeLimit < 0L) { throw new IllegalArgumentException("Size limit must be a non-negative value."); } @@ -48,12 +48,12 @@ class SpaceLimitSettings extends QuotaSettings { * Constructs a {@code SpaceLimitSettings} to remove a space quota on the given {@code tableName}. */ SpaceLimitSettings(TableName tableName) { - super(null, Objects.requireNonNull(tableName), null); + super(null, Objects.requireNonNull(tableName), null, null); proto = buildProtoRemoveQuota(); } SpaceLimitSettings(String namespace, long sizeLimit, SpaceViolationPolicy violationPolicy) { - super(null, null, Objects.requireNonNull(namespace)); + super(null, null, Objects.requireNonNull(namespace), null); if (sizeLimit < 0L) { throw new IllegalArgumentException("Size limit must be a non-negative value."); } @@ -64,12 +64,12 @@ class SpaceLimitSettings extends QuotaSettings { * Constructs a {@code SpaceLimitSettings} to remove a space quota on the given {@code namespace}. */ SpaceLimitSettings(String namespace) { - super(null, null, Objects.requireNonNull(namespace)); + super(null, null, Objects.requireNonNull(namespace), null); proto = buildProtoRemoveQuota(); } SpaceLimitSettings(TableName tableName, String namespace, SpaceLimitRequest req) { - super(null, tableName, namespace); + super(null, tableName, namespace, null); proto = req; } diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/quotas/ThrottleSettings.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/quotas/ThrottleSettings.java index 05fb70b..6004292 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/quotas/ThrottleSettings.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/quotas/ThrottleSettings.java @@ -25,9 +25,9 @@ import org.apache.hadoop.hbase.TableName; import org.apache.yetus.audience.InterfaceAudience; import org.apache.yetus.audience.InterfaceStability; import org.apache.hbase.thirdparty.com.google.common.annotations.VisibleForTesting; +import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil; import org.apache.hadoop.hbase.shaded.protobuf.generated.MasterProtos.SetQuotaRequest; import org.apache.hadoop.hbase.shaded.protobuf.generated.QuotaProtos.TimedQuota; -import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil; import org.apache.hadoop.hbase.shaded.protobuf.generated.QuotaProtos; @InterfaceAudience.Private @@ -35,9 +35,9 @@ import org.apache.hadoop.hbase.shaded.protobuf.generated.QuotaProtos; class ThrottleSettings extends QuotaSettings { final QuotaProtos.ThrottleRequest proto; - ThrottleSettings(final String userName, final TableName tableName, - final String namespace, final QuotaProtos.ThrottleRequest proto) { - super(userName, tableName, namespace); + ThrottleSettings(final String userName, final TableName tableName, final String namespace, + final String regionServer, final QuotaProtos.ThrottleRequest proto) { + super(userName, tableName, namespace, regionServer); this.proto = proto; } @@ -111,6 +111,9 @@ class ThrottleSettings extends QuotaSettings { builder.append(", SCOPE => "); builder.append(timedQuota.getScope().toString()); } + if (timedQuota.hasAllowExceed() && timedQuota.getAllowExceed()) { + builder.append(", ALLOW_EXCEED => true"); + } } else { builder.append(", LIMIT => NONE"); } @@ -146,7 +149,8 @@ class ThrottleSettings extends QuotaSettings { QuotaProtos.ThrottleRequest mergedReq = builder.setTimedQuota( timedQuotaBuilder.build()).build(); - return new ThrottleSettings(getUserName(), getTableName(), getNamespace(), mergedReq); + return new ThrottleSettings(getUserName(), getTableName(), getNamespace(), + getRegionServer(), mergedReq); } } return this; @@ -159,12 +163,12 @@ class ThrottleSettings extends QuotaSettings { } } - static ThrottleSettings fromTimedQuota(final String userName, - final TableName tableName, final String namespace, - ThrottleType type, QuotaProtos.TimedQuota timedQuota) { + static ThrottleSettings fromTimedQuota(final String userName, final TableName tableName, + final String namespace, final String regionServer, ThrottleType type, + QuotaProtos.TimedQuota timedQuota) { QuotaProtos.ThrottleRequest.Builder builder = QuotaProtos.ThrottleRequest.newBuilder(); builder.setType(ProtobufUtil.toProtoThrottleType(type)); builder.setTimedQuota(timedQuota); - return new ThrottleSettings(userName, tableName, namespace, builder.build()); + return new ThrottleSettings(userName, tableName, namespace, regionServer, builder.build()); } } diff --git a/hbase-client/src/main/java/org/apache/hadoop/hbase/shaded/protobuf/ProtobufUtil.java b/hbase-client/src/main/java/org/apache/hadoop/hbase/shaded/protobuf/ProtobufUtil.java index fea81f1..d53328c 100644 --- a/hbase-client/src/main/java/org/apache/hadoop/hbase/shaded/protobuf/ProtobufUtil.java +++ b/hbase-client/src/main/java/org/apache/hadoop/hbase/shaded/protobuf/ProtobufUtil.java @@ -2540,11 +2540,24 @@ public final class ProtobufUtil { */ public static QuotaProtos.TimedQuota toTimedQuota(final long limit, final TimeUnit timeUnit, final QuotaScope scope) { - return QuotaProtos.TimedQuota.newBuilder() - .setSoftLimit(limit) - .setTimeUnit(toProtoTimeUnit(timeUnit)) - .setScope(toProtoQuotaScope(scope)) - .build(); + return toTimedQuota(limit, timeUnit, scope, false); + } + + /** + * Build a protocol buffer TimedQuota, only used for region server throttle quota because + * allowExceed param may be true + * + * @param limit the allowed number of request/data per timeUnit + * @param timeUnit the limit time unit + * @param scope the quota scope + * @param allowExceed True if allow exceed quota + * @return the protocol buffer TimedQuota + */ + public static QuotaProtos.TimedQuota toTimedQuota(final long limit, final TimeUnit timeUnit, + final QuotaScope scope, boolean allowExceed) { + return QuotaProtos.TimedQuota.newBuilder().setSoftLimit(limit) + .setTimeUnit(toProtoTimeUnit(timeUnit)).setScope(toProtoQuotaScope(scope)) + .setAllowExceed(allowExceed).build(); } /** diff --git a/hbase-client/src/test/java/org/apache/hadoop/hbase/quotas/TestQuotaGlobalsSettingsBypass.java b/hbase-client/src/test/java/org/apache/hadoop/hbase/quotas/TestQuotaGlobalsSettingsBypass.java index 5b716f1..e4a60c7 100644 --- a/hbase-client/src/test/java/org/apache/hadoop/hbase/quotas/TestQuotaGlobalsSettingsBypass.java +++ b/hbase-client/src/test/java/org/apache/hadoop/hbase/quotas/TestQuotaGlobalsSettingsBypass.java @@ -39,26 +39,26 @@ public class TestQuotaGlobalsSettingsBypass { @Test public void testMerge() throws IOException { - QuotaGlobalsSettingsBypass orig = new QuotaGlobalsSettingsBypass("joe", null, null, true); - assertFalse(orig.merge(new QuotaGlobalsSettingsBypass( - "joe", null, null, false)).getBypass()); + QuotaGlobalsSettingsBypass orig = new QuotaGlobalsSettingsBypass("joe", null, null, null, true); + assertFalse( + orig.merge(new QuotaGlobalsSettingsBypass("joe", null, null, null, false)).getBypass()); } @Test public void testInvalidMerges() throws IOException { - QuotaGlobalsSettingsBypass userBypass = new QuotaGlobalsSettingsBypass( - "joe", null, null, true); - QuotaGlobalsSettingsBypass tableBypass = new QuotaGlobalsSettingsBypass( - null, TableName.valueOf("table"), null, true); - QuotaGlobalsSettingsBypass namespaceBypass = new QuotaGlobalsSettingsBypass( - null, null, "ns", true); - QuotaGlobalsSettingsBypass userOnTableBypass = new QuotaGlobalsSettingsBypass( - "joe", TableName.valueOf("table"), null, true); - QuotaGlobalsSettingsBypass userOnNamespaceBypass = new QuotaGlobalsSettingsBypass( - "joe", null, "ns", true); + QuotaGlobalsSettingsBypass userBypass = + new QuotaGlobalsSettingsBypass("joe", null, null, null, true); + QuotaGlobalsSettingsBypass tableBypass = + new QuotaGlobalsSettingsBypass(null, TableName.valueOf("table"), null, null, true); + QuotaGlobalsSettingsBypass namespaceBypass = + new QuotaGlobalsSettingsBypass(null, null, "ns", null, true); + QuotaGlobalsSettingsBypass userOnTableBypass = + new QuotaGlobalsSettingsBypass("joe", TableName.valueOf("table"), null, null, true); + QuotaGlobalsSettingsBypass userOnNamespaceBypass = + new QuotaGlobalsSettingsBypass("joe", null, "ns", null, true); assertTrue(userBypass.merge(userBypass).getBypass()); - expectFailure(userBypass, new QuotaGlobalsSettingsBypass("frank", null, null, false)); + expectFailure(userBypass, new QuotaGlobalsSettingsBypass("frank", null, null, null, false)); expectFailure(userBypass, tableBypass); expectFailure(userBypass, namespaceBypass); expectFailure(userBypass, userOnTableBypass); @@ -66,8 +66,8 @@ public class TestQuotaGlobalsSettingsBypass { assertTrue(tableBypass.merge(tableBypass).getBypass()); expectFailure(tableBypass, userBypass); - expectFailure(tableBypass, new QuotaGlobalsSettingsBypass( - null, TableName.valueOf("foo"), null, false)); + expectFailure(tableBypass, + new QuotaGlobalsSettingsBypass(null, TableName.valueOf("foo"), null, null, false)); expectFailure(tableBypass, namespaceBypass); expectFailure(tableBypass, userOnTableBypass); expectFailure(tableBypass, userOnNamespaceBypass); @@ -75,7 +75,7 @@ public class TestQuotaGlobalsSettingsBypass { assertTrue(namespaceBypass.merge(namespaceBypass).getBypass()); expectFailure(namespaceBypass, userBypass); expectFailure(namespaceBypass, tableBypass); - expectFailure(namespaceBypass, new QuotaGlobalsSettingsBypass(null, null, "sn", false)); + expectFailure(namespaceBypass, new QuotaGlobalsSettingsBypass(null, null, "sn", null, false)); expectFailure(namespaceBypass, userOnTableBypass); expectFailure(namespaceBypass, userOnNamespaceBypass); @@ -84,11 +84,11 @@ public class TestQuotaGlobalsSettingsBypass { expectFailure(userOnTableBypass, tableBypass); expectFailure(userOnTableBypass, namespaceBypass); // Incorrect user - expectFailure(userOnTableBypass, new QuotaGlobalsSettingsBypass( - "frank", TableName.valueOf("foo"), null, false)); + expectFailure(userOnTableBypass, + new QuotaGlobalsSettingsBypass("frank", TableName.valueOf("foo"), null, null, false)); // Incorrect tablename - expectFailure(userOnTableBypass, new QuotaGlobalsSettingsBypass( - "joe", TableName.valueOf("bar"), null, false)); + expectFailure(userOnTableBypass, + new QuotaGlobalsSettingsBypass("joe", TableName.valueOf("bar"), null, null, false)); expectFailure(userOnTableBypass, userOnNamespaceBypass); assertTrue(userOnNamespaceBypass.merge(userOnNamespaceBypass).getBypass()); @@ -96,10 +96,10 @@ public class TestQuotaGlobalsSettingsBypass { expectFailure(userOnNamespaceBypass, tableBypass); expectFailure(userOnNamespaceBypass, namespaceBypass); expectFailure(userOnNamespaceBypass, userOnTableBypass); - expectFailure(userOnNamespaceBypass, new QuotaGlobalsSettingsBypass( - "frank", null, "ns", false)); - expectFailure(userOnNamespaceBypass, new QuotaGlobalsSettingsBypass( - "joe", null, "sn", false)); + expectFailure(userOnNamespaceBypass, + new QuotaGlobalsSettingsBypass("frank", null, "ns", null, false)); + expectFailure(userOnNamespaceBypass, + new QuotaGlobalsSettingsBypass("joe", null, "sn", null, false)); } void expectFailure(QuotaSettings one, QuotaSettings two) throws IOException { diff --git a/hbase-client/src/test/java/org/apache/hadoop/hbase/quotas/TestThrottleSettings.java b/hbase-client/src/test/java/org/apache/hadoop/hbase/quotas/TestThrottleSettings.java index 91241ae..53fb9bd 100644 --- a/hbase-client/src/test/java/org/apache/hadoop/hbase/quotas/TestThrottleSettings.java +++ b/hbase-client/src/test/java/org/apache/hadoop/hbase/quotas/TestThrottleSettings.java @@ -48,7 +48,7 @@ public class TestThrottleSettings { .setTimeUnit(HBaseProtos.TimeUnit.MINUTES).build(); ThrottleRequest tr1 = ThrottleRequest.newBuilder().setTimedQuota(tq1) .setType(QuotaProtos.ThrottleType.REQUEST_NUMBER).build(); - ThrottleSettings orig = new ThrottleSettings("joe", null, null, tr1); + ThrottleSettings orig = new ThrottleSettings("joe", null, null, null, tr1); TimedQuota tq2 = TimedQuota.newBuilder().setSoftLimit(10) .setScope(QuotaProtos.QuotaScope.MACHINE) @@ -56,7 +56,7 @@ public class TestThrottleSettings { ThrottleRequest tr2 = ThrottleRequest.newBuilder().setTimedQuota(tq2) .setType(QuotaProtos.ThrottleType.REQUEST_NUMBER).build(); - ThrottleSettings merged = orig.merge(new ThrottleSettings("joe", null, null, tr2)); + ThrottleSettings merged = orig.merge(new ThrottleSettings("joe", null, null, null, tr2)); assertEquals(10, merged.getSoftLimit()); assertEquals(ThrottleType.REQUEST_NUMBER, merged.getThrottleType()); @@ -70,7 +70,7 @@ public class TestThrottleSettings { .setTimeUnit(HBaseProtos.TimeUnit.MINUTES).build(); ThrottleRequest requestsQuotaReq = ThrottleRequest.newBuilder().setTimedQuota(requestsQuota) .setType(QuotaProtos.ThrottleType.REQUEST_NUMBER).build(); - ThrottleSettings orig = new ThrottleSettings("joe", null, null, requestsQuotaReq); + ThrottleSettings orig = new ThrottleSettings("joe", null, null, null, requestsQuotaReq); TimedQuota readsQuota = TimedQuota.newBuilder().setSoftLimit(10) .setScope(QuotaProtos.QuotaScope.MACHINE) @@ -79,7 +79,7 @@ public class TestThrottleSettings { .setType(QuotaProtos.ThrottleType.READ_NUMBER).build(); try { - orig.merge(new ThrottleSettings("joe", null, null, readsQuotaReq)); + orig.merge(new ThrottleSettings("joe", null, null, null, readsQuotaReq)); fail("A read throttle should not be capable of being merged with a request quota"); } catch (IllegalArgumentException e) { // Pass @@ -93,13 +93,13 @@ public class TestThrottleSettings { .setTimeUnit(HBaseProtos.TimeUnit.MINUTES).build(); ThrottleRequest tr1 = ThrottleRequest.newBuilder().setTimedQuota(tq1) .setType(QuotaProtos.ThrottleType.REQUEST_NUMBER).build(); - ThrottleSettings orig = new ThrottleSettings("joe", null, null, tr1); + ThrottleSettings orig = new ThrottleSettings("joe", null, null, null, tr1); ThrottleRequest tr2 = ThrottleRequest.newBuilder() .setType(QuotaProtos.ThrottleType.REQUEST_NUMBER).build(); assertTrue( "The same object should be returned by merge, but it wasn't", - orig == orig.merge(new ThrottleSettings("joe", null, null, tr2))); + orig == orig.merge(new ThrottleSettings("joe", null, null, null, tr2))); } } diff --git a/hbase-protocol-shaded/src/main/protobuf/Master.proto b/hbase-protocol-shaded/src/main/protobuf/Master.proto index 5cb4309..b1ed94f 100644 --- a/hbase-protocol-shaded/src/main/protobuf/Master.proto +++ b/hbase-protocol-shaded/src/main/protobuf/Master.proto @@ -572,6 +572,7 @@ message SetQuotaRequest { optional ThrottleRequest throttle = 7; optional SpaceLimitRequest space_limit = 8; + optional string region_server = 9; } message SetQuotaResponse { diff --git a/hbase-protocol-shaded/src/main/protobuf/Quota.proto b/hbase-protocol-shaded/src/main/protobuf/Quota.proto index 5b00d74..3e21697 100644 --- a/hbase-protocol-shaded/src/main/protobuf/Quota.proto +++ b/hbase-protocol-shaded/src/main/protobuf/Quota.proto @@ -37,6 +37,7 @@ message TimedQuota { optional uint64 soft_limit = 2; optional float share = 3; optional QuotaScope scope = 4 [default = MACHINE]; + optional bool allow_exceed = 5 [default = false]; } enum ThrottleType { 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 5d43f10..63ed125 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 @@ -1093,6 +1093,24 @@ public interface MasterObserver { final String namespace, final GlobalQuotaSettings quotas) throws IOException {} /** + * Called before the quota for the region server is stored. + * @param ctx the environment to interact with the framework and master + * @param regionServer the name of the region server + * @param quotas the current quota for the region server + */ + default void preSetRegionServerQuota(final ObserverContext ctx, + final String regionServer, final GlobalQuotaSettings quotas) throws IOException {} + + /** + * Called after the quota for the region server is stored. + * @param ctx the environment to interact with the framework and master + * @param regionServer the name of the region server + * @param quotas the resulting quota for the region server + */ + default void postSetRegionServerQuota(final ObserverContext ctx, + final String regionServer, final GlobalQuotaSettings quotas) throws IOException {} + + /** * Called before merge regions request. * @param ctx coprocessor environment * @param regionsToMerge regions to be merged 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 dd02a36..b4a4421 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 @@ -1285,6 +1285,26 @@ public class MasterCoprocessorHost }); } + public void preSetRegionServerQuota(final String regionServer, final GlobalQuotaSettings quotas) + throws IOException { + execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() { + @Override + public void call(MasterObserver observer) throws IOException { + observer.preSetRegionServerQuota(this, regionServer, quotas); + } + }); + } + + public void postSetRegionServerQuota(final String regionServer, final GlobalQuotaSettings quotas) + throws IOException { + execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() { + @Override + public void call(MasterObserver observer) throws IOException { + observer.postSetRegionServerQuota(this, regionServer, quotas); + } + }); + } + public void preMoveServersAndTables(final Set
servers, final Set tables, final String targetGroup) throws IOException { execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() { diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/quotas/AllowExceedOperationQuota.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/quotas/AllowExceedOperationQuota.java new file mode 100644 index 0000000..18e8350 --- /dev/null +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/quotas/AllowExceedOperationQuota.java @@ -0,0 +1,74 @@ +/** + * 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.quotas; + +import org.apache.hadoop.conf.Configuration; +import org.apache.yetus.audience.InterfaceAudience; +import org.apache.yetus.audience.InterfaceStability; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@InterfaceAudience.Private +@InterfaceStability.Evolving +public class AllowExceedOperationQuota extends DefaultOperationQuota { + private static final Logger LOG = LoggerFactory.getLogger(AllowExceedOperationQuota.class); + private QuotaLimiter regionServerLimiter; + + public AllowExceedOperationQuota(final Configuration conf, QuotaLimiter regionServerLimiter, + final QuotaLimiter... limiters) { + super(conf, limiters); + this.regionServerLimiter = regionServerLimiter; + } + + @Override + public void checkQuota(int numWrites, int numReads, int numScans) throws RpcThrottlingException { + boolean exceed = false; + try { + super.checkQuota(numWrites, numReads, numScans); + } catch (RpcThrottlingException e) { + exceed = true; + if (LOG.isDebugEnabled()) { + LOG.debug("Read/Write requests num exceeds quota: writes:{} reads:{} scan:{}, " + + "try use region server quota", + numWrites, numReads, numScans); + } + } + if (regionServerLimiter.isBypass()) return; + + regionServerLimiter.checkQuota(numWrites, writeConsumed, numReads + numScans, readConsumed, + writeCapacityUnitConsumed, readCapacityUnitConsumed); + readAvailable = Math.max(readAvailable, regionServerLimiter.getReadAvailable()); + writeAvailable = Math.max(writeAvailable, regionServerLimiter.getWriteAvailable()); + regionServerLimiter.grabQuota(numWrites, writeConsumed, numReads + numScans, readConsumed, + writeCapacityUnitConsumed, writeCapacityUnitConsumed); + if (exceed) { + for (final QuotaLimiter limiter : limiters) { + limiter.grabQuota(numWrites, writeConsumed, numReads + numScans, readConsumed, + writeCapacityUnitConsumed, writeCapacityUnitConsumed); + } + } + } + + @Override + public void close() { + super.close(); + if (writeDiff != 0) regionServerLimiter.consumeWrite(writeDiff, writeCapacityUnitDiff); + if (readDiff != 0) regionServerLimiter.consumeRead(readDiff, readCapacityUnitDiff); + } +} diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/quotas/DefaultOperationQuota.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/quotas/DefaultOperationQuota.java index f9b3ca5..6374492 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/quotas/DefaultOperationQuota.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/quotas/DefaultOperationQuota.java @@ -22,29 +22,37 @@ import java.util.Arrays; import java.util.List; import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hbase.client.Mutation; +import org.apache.hadoop.hbase.client.Result; import org.apache.yetus.audience.InterfaceAudience; import org.apache.yetus.audience.InterfaceStability; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.apache.hadoop.hbase.client.Mutation; -import org.apache.hadoop.hbase.client.Result; @InterfaceAudience.Private @InterfaceStability.Evolving public class DefaultOperationQuota implements OperationQuota { private static final Logger LOG = LoggerFactory.getLogger(DefaultOperationQuota.class); - private final List limiters; + protected final List limiters; private final long writeCapacityUnit; private final long readCapacityUnit; - private long writeAvailable = 0; - private long readAvailable = 0; - private long writeConsumed = 0; - private long readConsumed = 0; - private long writeCapacityUnitConsumed = 0; - private long readCapacityUnitConsumed = 0; + // the available read/write quota size in bytes + protected long writeAvailable = 0; + protected long readAvailable = 0; + // estimated quota + protected long writeConsumed = 0; + protected long readConsumed = 0; + protected long writeCapacityUnitConsumed = 0; + protected long readCapacityUnitConsumed = 0; + // real consumed quota private final long[] operationSize; + // quota difference between estimated quota and real consumed quota + protected long writeDiff = 0; + protected long readDiff = 0; + protected long writeCapacityUnitDiff = 0; + protected long readCapacityUnitDiff = 0; public DefaultOperationQuota(final Configuration conf, final QuotaLimiter... limiters) { this(conf, Arrays.asList(limiters)); @@ -96,12 +104,12 @@ public class DefaultOperationQuota implements OperationQuota { @Override public void close() { // Adjust the quota consumed for the specified operation - long writeDiff = operationSize[OperationType.MUTATE.ordinal()] - writeConsumed; - long readDiff = operationSize[OperationType.GET.ordinal()] + writeDiff = operationSize[OperationType.MUTATE.ordinal()] - writeConsumed; + readDiff = operationSize[OperationType.GET.ordinal()] + operationSize[OperationType.SCAN.ordinal()] - readConsumed; - long writeCapacityUnitDiff = calculateWriteCapacityUnitDiff( + writeCapacityUnitDiff = calculateWriteCapacityUnitDiff( operationSize[OperationType.MUTATE.ordinal()], writeConsumed); - long readCapacityUnitDiff = calculateReadCapacityUnitDiff( + readCapacityUnitDiff = calculateReadCapacityUnitDiff( operationSize[OperationType.GET.ordinal()] + operationSize[OperationType.SCAN.ordinal()], readConsumed); diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/quotas/GlobalQuotaSettings.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/quotas/GlobalQuotaSettings.java index 23dc7d5..b722e84 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/quotas/GlobalQuotaSettings.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/quotas/GlobalQuotaSettings.java @@ -35,8 +35,9 @@ import org.apache.hadoop.hbase.shaded.protobuf.generated.QuotaProtos.Quotas; @InterfaceStability.Evolving public abstract class GlobalQuotaSettings extends QuotaSettings { - protected GlobalQuotaSettings(String userName, TableName tableName, String namespace) { - super(userName, tableName, namespace); + protected GlobalQuotaSettings(String userName, TableName tableName, String namespace, + String regionServer) { + super(userName, tableName, namespace, regionServer); } /** diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/quotas/GlobalQuotaSettingsImpl.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/quotas/GlobalQuotaSettingsImpl.java index e47e4ff..6272851 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/quotas/GlobalQuotaSettingsImpl.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/quotas/GlobalQuotaSettingsImpl.java @@ -27,13 +27,14 @@ import java.util.Map.Entry; import org.apache.hadoop.hbase.DoNotRetryIOException; import org.apache.hadoop.hbase.TableName; import org.apache.hadoop.hbase.quotas.QuotaSettingsFactory.QuotaGlobalsSettingsBypass; +import org.apache.yetus.audience.InterfaceAudience; + import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil; import org.apache.hadoop.hbase.shaded.protobuf.generated.QuotaProtos; import org.apache.hadoop.hbase.shaded.protobuf.generated.QuotaProtos.Quotas; import org.apache.hadoop.hbase.shaded.protobuf.generated.QuotaProtos.SpaceQuota; import org.apache.hadoop.hbase.shaded.protobuf.generated.QuotaProtos.Throttle; import org.apache.hadoop.hbase.shaded.protobuf.generated.QuotaProtos.TimedQuota; -import org.apache.yetus.audience.InterfaceAudience; /** * Implementation of {@link GlobalQuotaSettings} to hide the Protobuf messages we use internally. @@ -45,18 +46,18 @@ public class GlobalQuotaSettingsImpl extends GlobalQuotaSettings { private final Boolean bypassGlobals; private final QuotaProtos.SpaceQuota spaceProto; - protected GlobalQuotaSettingsImpl( - String username, TableName tableName, String namespace, QuotaProtos.Quotas quotas) { - this(username, tableName, namespace, + protected GlobalQuotaSettingsImpl(String username, TableName tableName, String namespace, + String regionServer, QuotaProtos.Quotas quotas) { + this(username, tableName, namespace, regionServer, (quotas != null && quotas.hasThrottle() ? quotas.getThrottle() : null), (quotas != null && quotas.hasBypassGlobals() ? quotas.getBypassGlobals() : null), (quotas != null && quotas.hasSpace() ? quotas.getSpace() : null)); } - protected GlobalQuotaSettingsImpl( - String userName, TableName tableName, String namespace, QuotaProtos.Throttle throttleProto, - Boolean bypassGlobals, QuotaProtos.SpaceQuota spaceProto) { - super(userName, tableName, namespace); + protected GlobalQuotaSettingsImpl(String userName, TableName tableName, String namespace, + String regionServer, QuotaProtos.Throttle throttleProto, Boolean bypassGlobals, + QuotaProtos.SpaceQuota spaceProto) { + super(userName, tableName, namespace, regionServer); this.throttleProto = throttleProto; this.bypassGlobals = bypassGlobals; this.spaceProto = spaceProto; @@ -67,12 +68,12 @@ public class GlobalQuotaSettingsImpl extends GlobalQuotaSettings { // Very similar to QuotaSettingsFactory List settings = new ArrayList<>(); if (throttleProto != null) { - settings.addAll(QuotaSettingsFactory.fromThrottle( - getUserName(), getTableName(), getNamespace(), throttleProto)); + settings.addAll(QuotaSettingsFactory.fromThrottle(getUserName(), getTableName(), + getNamespace(), getRegionServer(), throttleProto)); } if (bypassGlobals != null && bypassGlobals.booleanValue()) { - settings.add(new QuotaGlobalsSettingsBypass( - getUserName(), getTableName(), getNamespace(), true)); + settings.add(new QuotaGlobalsSettingsBypass(getUserName(), getTableName(), getNamespace(), + getRegionServer(), true)); } if (spaceProto != null) { settings.add(QuotaSettingsFactory.fromSpace(getTableName(), getNamespace(), spaceProto)); @@ -207,7 +208,7 @@ public class GlobalQuotaSettingsImpl extends GlobalQuotaSettings { } return new GlobalQuotaSettingsImpl( - getUserName(), getTableName(), getNamespace(), + getUserName(), getTableName(), getNamespace(), getRegionServer(), (throttleBuilder == null ? null : throttleBuilder.build()), bypassGlobals, (spaceBuilder == null ? null : spaceBuilder.build())); } @@ -257,6 +258,9 @@ public class GlobalQuotaSettingsImpl extends GlobalQuotaSettings { builder.append(", SCOPE => "); builder.append(timedQuota.getScope().toString()); } + if (timedQuota.hasAllowExceed() && timedQuota.getAllowExceed()) { + builder.append(", ALLOW_EXCEED => true"); + } } builder.append( "} } "); } else { diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/quotas/MasterQuotaManager.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/quotas/MasterQuotaManager.java index bb3cff1..02ecfae 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/quotas/MasterQuotaManager.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/quotas/MasterQuotaManager.java @@ -82,6 +82,7 @@ public class MasterQuotaManager implements RegionStateListener { private NamedLock namespaceLocks; private NamedLock tableLocks; private NamedLock userLocks; + private NamedLock regionServerLocks; private boolean initialized = false; private NamespaceAuditor namespaceQuotaManager; private ConcurrentHashMap regionSizes; @@ -110,6 +111,7 @@ public class MasterQuotaManager implements RegionStateListener { namespaceLocks = new NamedLock<>(); tableLocks = new NamedLock<>(); userLocks = new NamedLock<>(); + regionServerLocks = new NamedLock<>(); regionSizes = new ConcurrentHashMap<>(); namespaceQuotaManager = new NamespaceAuditor(masterServices); @@ -162,9 +164,16 @@ public class MasterQuotaManager implements RegionStateListener { } finally { namespaceLocks.unlock(req.getNamespace()); } + } else if (req.hasRegionServer()) { + regionServerLocks.lock(req.getRegionServer()); + try { + setRegionServerQuota(req.getRegionServer(), req); + } finally { + regionServerLocks.unlock(req.getRegionServer()); + } } else { throw new DoNotRetryIOException( - new UnsupportedOperationException("a user, a table or a namespace must be specified")); + new UnsupportedOperationException("a user, a table, a namespace or region server must be specified")); } return SetQuotaResponse.newBuilder().build(); } @@ -174,8 +183,8 @@ public class MasterQuotaManager implements RegionStateListener { setQuota(req, new SetQuotaOperations() { @Override public GlobalQuotaSettingsImpl fetch() throws IOException { - return new GlobalQuotaSettingsImpl(req.getUserName(), null, null, QuotaUtil.getUserQuota( - masterServices.getConnection(), userName)); + return new GlobalQuotaSettingsImpl(req.getUserName(), null, null, null, + QuotaUtil.getUserQuota(masterServices.getConnection(), userName)); } @Override public void update(GlobalQuotaSettingsImpl quotaPojo) throws IOException { @@ -201,8 +210,8 @@ public class MasterQuotaManager implements RegionStateListener { setQuota(req, new SetQuotaOperations() { @Override public GlobalQuotaSettingsImpl fetch() throws IOException { - return new GlobalQuotaSettingsImpl(userName, table, null, QuotaUtil.getUserQuota( - masterServices.getConnection(), userName, table)); + return new GlobalQuotaSettingsImpl(userName, table, null, null, + QuotaUtil.getUserQuota(masterServices.getConnection(), userName, table)); } @Override public void update(GlobalQuotaSettingsImpl quotaPojo) throws IOException { @@ -229,8 +238,8 @@ public class MasterQuotaManager implements RegionStateListener { setQuota(req, new SetQuotaOperations() { @Override public GlobalQuotaSettingsImpl fetch() throws IOException { - return new GlobalQuotaSettingsImpl(userName, null, namespace, QuotaUtil.getUserQuota( - masterServices.getConnection(), userName, namespace)); + return new GlobalQuotaSettingsImpl(userName, null, namespace, null, + QuotaUtil.getUserQuota(masterServices.getConnection(), userName, namespace)); } @Override public void update(GlobalQuotaSettingsImpl quotaPojo) throws IOException { @@ -259,8 +268,8 @@ public class MasterQuotaManager implements RegionStateListener { setQuota(req, new SetQuotaOperations() { @Override public GlobalQuotaSettingsImpl fetch() throws IOException { - return new GlobalQuotaSettingsImpl(null, table, null, QuotaUtil.getTableQuota( - masterServices.getConnection(), table)); + return new GlobalQuotaSettingsImpl(null, table, null, null, + QuotaUtil.getTableQuota(masterServices.getConnection(), table)); } @Override public void update(GlobalQuotaSettingsImpl quotaPojo) throws IOException { @@ -286,8 +295,8 @@ public class MasterQuotaManager implements RegionStateListener { setQuota(req, new SetQuotaOperations() { @Override public GlobalQuotaSettingsImpl fetch() throws IOException { - return new GlobalQuotaSettingsImpl(null, null, namespace, QuotaUtil.getNamespaceQuota( - masterServices.getConnection(), namespace)); + return new GlobalQuotaSettingsImpl(null, null, namespace, null, + QuotaUtil.getNamespaceQuota(masterServices.getConnection(), namespace)); } @Override public void update(GlobalQuotaSettingsImpl quotaPojo) throws IOException { @@ -309,6 +318,38 @@ public class MasterQuotaManager implements RegionStateListener { }); } + public void setRegionServerQuota(final String regionServer, final SetQuotaRequest req) + throws IOException, InterruptedException { + setQuota(req, new SetQuotaOperations() { + @Override + public GlobalQuotaSettingsImpl fetch() throws IOException { + return new GlobalQuotaSettingsImpl(null, null, null, regionServer, + QuotaUtil.getRegionServerQuota(masterServices.getConnection(), regionServer)); + } + + @Override + public void update(GlobalQuotaSettingsImpl quotaPojo) throws IOException { + QuotaUtil.addRegionServerQuota(masterServices.getConnection(), regionServer, + ((GlobalQuotaSettingsImpl) quotaPojo).toQuotas()); + } + + @Override + public void delete() throws IOException { + QuotaUtil.deleteRegionServerQuota(masterServices.getConnection(), regionServer); + } + + @Override + public void preApply(GlobalQuotaSettingsImpl quotaPojo) throws IOException { + masterServices.getMasterCoprocessorHost().preSetRegionServerQuota(regionServer, quotaPojo); + } + + @Override + public void postApply(GlobalQuotaSettingsImpl quotaPojo) throws IOException { + masterServices.getMasterCoprocessorHost().postSetRegionServerQuota(regionServer, quotaPojo); + } + }); + } + public void setNamespaceQuota(NamespaceDescriptor desc) throws IOException { if (initialized) { this.namespaceQuotaManager.addNamespace(desc); diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/quotas/NoopQuotaLimiter.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/quotas/NoopQuotaLimiter.java index 71dd3c7..38508fa 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/quotas/NoopQuotaLimiter.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/quotas/NoopQuotaLimiter.java @@ -79,4 +79,9 @@ class NoopQuotaLimiter implements QuotaLimiter { public static QuotaLimiter get() { return instance; } + + @Override + public boolean isAllowExceed() { + return false; + } } diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/quotas/QuotaCache.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/quotas/QuotaCache.java index 0664cc5..7d589e8 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/quotas/QuotaCache.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/quotas/QuotaCache.java @@ -34,14 +34,14 @@ import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.ScheduledChore; import org.apache.hadoop.hbase.Stoppable; import org.apache.hadoop.hbase.TableName; -import org.apache.yetus.audience.InterfaceAudience; -import org.apache.yetus.audience.InterfaceStability; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.apache.hadoop.hbase.client.Get; import org.apache.hadoop.hbase.regionserver.RegionServerServices; import org.apache.hadoop.hbase.util.EnvironmentEdgeManager; import org.apache.hadoop.security.UserGroupInformation; +import org.apache.yetus.audience.InterfaceAudience; +import org.apache.yetus.audience.InterfaceStability; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Cache that keeps track of the quota settings for the users and tables that @@ -69,6 +69,7 @@ public class QuotaCache implements Stoppable { private final ConcurrentHashMap namespaceQuotaCache = new ConcurrentHashMap<>(); private final ConcurrentHashMap tableQuotaCache = new ConcurrentHashMap<>(); private final ConcurrentHashMap userQuotaCache = new ConcurrentHashMap<>(); + private final ConcurrentHashMap regionServerQuotaCache = new ConcurrentHashMap<>(); private final RegionServerServices rsServices; private QuotaRefresherChore refreshChore; @@ -147,6 +148,16 @@ public class QuotaCache implements Stoppable { } /** + * Returns the limiter associated to the specified region server. + * + * @param regionServer the region server to limit + * @return the limiter associated to the specified `region server + */ + public QuotaLimiter getRegionServerQuotaLimiter(final String regionServer) { + return getQuotaState(this.regionServerQuotaCache, regionServer).getGlobalLimiter(); + } + + /** * Returns the QuotaState requested. If the quota info is not in cache an empty one will be * returned and the quota request will be enqueued for the next cache refresh. */ @@ -171,6 +182,11 @@ public class QuotaCache implements Stoppable { } @VisibleForTesting + Map getRegionServerQuotaCache() { + return regionServerQuotaCache; + } + + @VisibleForTesting Map getTableQuotaCache() { return tableQuotaCache; } @@ -203,10 +219,12 @@ public class QuotaCache implements Stoppable { QuotaCache.this.namespaceQuotaCache.putIfAbsent(ns, new QuotaState()); } } + QuotaCache.this.regionServerQuotaCache.putIfAbsent(QuotaUtil.REGION_SERVER_QUOTA_KEY, new QuotaState()); fetchNamespaceQuotaState(); fetchTableQuotaState(); fetchUserQuotaState(); + fetchRegionServerQuotaState(); lastUpdate = EnvironmentEdgeManager.currentTime(); } @@ -257,6 +275,21 @@ public class QuotaCache implements Stoppable { }); } + private void fetchRegionServerQuotaState() { + fetch("regionServer", QuotaCache.this.regionServerQuotaCache, + new Fetcher() { + @Override + public Get makeGet(final Map.Entry entry) { + return QuotaUtil.makeGetForRegionServerQuotas(entry.getKey()); + } + + @Override + public Map fetchEntries(final List gets) throws IOException { + return QuotaUtil.fetchRegionServerQuotas(rsServices.getConnection(), gets); + } + }); + } + private void fetch(final String type, final ConcurrentHashMap quotasMap, final Fetcher fetcher) { long now = EnvironmentEdgeManager.currentTime(); diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/quotas/QuotaLimiter.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/quotas/QuotaLimiter.java index 9260ec2..244affd 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/quotas/QuotaLimiter.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/quotas/QuotaLimiter.java @@ -78,4 +78,7 @@ public interface QuotaLimiter { /** @return the number of bytes available to write to avoid exceeding the quota */ long getWriteAvailable(); + + /** @return True if allow exceed quota */ + boolean isAllowExceed(); } diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/quotas/QuotaUtil.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/quotas/QuotaUtil.java index f6b5d95..f3e93a2 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/quotas/QuotaUtil.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/quotas/QuotaUtil.java @@ -30,10 +30,6 @@ import org.apache.hadoop.hbase.HConstants; import org.apache.hadoop.hbase.HTableDescriptor; import org.apache.hadoop.hbase.KeyValueUtil; import org.apache.hadoop.hbase.TableName; -import org.apache.yetus.audience.InterfaceAudience; -import org.apache.yetus.audience.InterfaceStability; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.apache.hadoop.hbase.client.Connection; import org.apache.hadoop.hbase.client.Delete; import org.apache.hadoop.hbase.client.Get; @@ -41,10 +37,15 @@ import org.apache.hadoop.hbase.client.Mutation; import org.apache.hadoop.hbase.client.Put; import org.apache.hadoop.hbase.client.Result; import org.apache.hadoop.hbase.client.Table; -import org.apache.hadoop.hbase.shaded.protobuf.generated.QuotaProtos.Quotas; import org.apache.hadoop.hbase.regionserver.BloomType; import org.apache.hadoop.hbase.util.Bytes; import org.apache.hadoop.hbase.util.EnvironmentEdgeManager; +import org.apache.yetus.audience.InterfaceAudience; +import org.apache.yetus.audience.InterfaceStability; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.apache.hadoop.hbase.shaded.protobuf.generated.QuotaProtos.Quotas; /** * Helper class to interact with the quota table @@ -56,6 +57,7 @@ public class QuotaUtil extends QuotaTableUtil { public static final String QUOTA_CONF_KEY = "hbase.quota.enabled"; private static final boolean QUOTA_ENABLED_DEFAULT = false; + public static final String REGION_SERVER_QUOTA_KEY = "rs"; public static final String READ_CAPACITY_UNIT_CONF_KEY = "hbase.quota.read.capacity.unit"; // the default one read capacity unit is 1024 bytes (1KB) @@ -143,6 +145,16 @@ public class QuotaUtil extends QuotaTableUtil { getSettingsQualifierForUserNamespace(namespace)); } + public static void addRegionServerQuota(final Connection connection, final String regionServer, + final Quotas data) throws IOException { + addQuotas(connection, getRegionServerRowKey(regionServer), data); + } + + public static void deleteRegionServerQuota(final Connection connection, final String regionServer) + throws IOException { + deleteQuotas(connection, getRegionServerRowKey(regionServer)); + } + private static void addQuotas(final Connection connection, final byte[] rowKey, final Quotas data) throws IOException { addQuotas(connection, rowKey, QUOTA_QUALIFIER_SETTINGS, data); @@ -233,6 +245,17 @@ public class QuotaUtil extends QuotaTableUtil { }); } + public static Map fetchRegionServerQuotas(final Connection connection, + final List gets) throws IOException { + return fetchGlobalQuotas("regionServer", connection, gets, new KeyFromRow() { + @Override + public String getKeyFromRow(final byte[] row) { + assert isRegionServerRowKey(row); + return getRegionServerFromRowKey(row); + } + }); + } + public static Map fetchGlobalQuotas(final String type, final Connection connection, final List gets, final KeyFromRow kfr) throws IOException { diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/quotas/RateLimiter.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/quotas/RateLimiter.java index 852d8a6..144e3f1 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/quotas/RateLimiter.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/quotas/RateLimiter.java @@ -53,6 +53,7 @@ public abstract class RateLimiter { private long tunit = 1000; // Timeunit factor for translating to ms. private long limit = Long.MAX_VALUE; // The max value available resource units can be refilled to. private long avail = Long.MAX_VALUE; // Currently available resource units + private boolean allowExceed = false; /** * Refill the available units w.r.t the elapsed time. @@ -70,13 +71,23 @@ public abstract class RateLimiter { */ abstract long getWaitInterval(long limit, long available, long amount); - /** * Set the RateLimiter max available resources and refill period. * @param limit The max value available resource units can be refilled to. * @param timeUnit Timeunit factor for translating to ms. */ public synchronized void set(final long limit, final TimeUnit timeUnit) { + set(limit, timeUnit, false); + } + + /** + * Set the RateLimiter max available resources and refill period. + * @param limit The max value available resource units can be refilled to. + * @param timeUnit Timeunit factor for translating to ms. + * @param allowExceed If allow exceed quota, only used by region server quota + */ + public synchronized void set(final long limit, final TimeUnit timeUnit, + final boolean allowExceed) { switch (timeUnit) { case MILLISECONDS: tunit = 1; @@ -98,6 +109,7 @@ public abstract class RateLimiter { } this.limit = limit; this.avail = limit; + this.allowExceed = allowExceed; } @Override @@ -106,8 +118,9 @@ public abstract class RateLimiter { if (getLimit() == Long.MAX_VALUE) { return rateLimiter + "(Bypass)"; } - return rateLimiter + "(avail=" + getAvailable() + " limit=" + getLimit() + - " tunit=" + getTimeUnitInMillis() + ")"; + return new StringBuilder().append(rateLimiter).append("(avail=").append(getAvailable()) + .append(" limit=").append(getLimit()).append(" tunit=").append(getTimeUnitInMillis()) + .append(" allowExceed=").append(isAllowExceed()).append(")").toString(); } /** @@ -229,6 +242,10 @@ public abstract class RateLimiter { return (amount <= avail) ? 0 : getWaitInterval(getLimit(), avail, amount); } + public boolean isAllowExceed() { + return allowExceed; + } + // These two method are for strictly testing purpose only @VisibleForTesting public abstract void setNextRefillTime(long nextRefillTime); diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/quotas/RegionServerRpcQuotaManager.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/quotas/RegionServerRpcQuotaManager.java index 9b3d48a..9cf0d11 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/quotas/RegionServerRpcQuotaManager.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/quotas/RegionServerRpcQuotaManager.java @@ -22,19 +22,19 @@ import java.io.IOException; import java.util.List; import java.util.Optional; -import org.apache.yetus.audience.InterfaceAudience; -import org.apache.yetus.audience.InterfaceStability; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.apache.hadoop.hbase.TableName; import org.apache.hadoop.hbase.ipc.RpcScheduler; import org.apache.hadoop.hbase.ipc.RpcServer; -import org.apache.hadoop.hbase.shaded.protobuf.generated.ClientProtos; import org.apache.hadoop.hbase.regionserver.Region; import org.apache.hadoop.hbase.regionserver.RegionServerServices; import org.apache.hadoop.hbase.security.User; import org.apache.hadoop.security.UserGroupInformation; +import org.apache.yetus.audience.InterfaceAudience; +import org.apache.yetus.audience.InterfaceStability; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.apache.hbase.thirdparty.com.google.common.annotations.VisibleForTesting; +import org.apache.hadoop.hbase.shaded.protobuf.generated.ClientProtos; /** * Region Server Quota Manager. @@ -141,8 +141,19 @@ public class RegionServerRpcQuotaManager { userLimiter + " tableLimiter=" + tableLimiter + " nsLimiter=" + nsLimiter); } if (!useNoop) { - return new DefaultOperationQuota(this.rsServices.getConfiguration(), userLimiter, - tableLimiter, nsLimiter); + QuotaLimiter rsLimiter = + quotaCache.getRegionServerQuotaLimiter(QuotaUtil.REGION_SERVER_QUOTA_KEY); + if (!rsLimiter.isAllowExceed()) { + DefaultOperationQuota quota = + new DefaultOperationQuota(this.rsServices.getConfiguration(), userLimiter, + tableLimiter, nsLimiter, rsLimiter); + return quota; + } else { + AllowExceedOperationQuota quota = + new AllowExceedOperationQuota(this.rsServices.getConfiguration(), rsLimiter, + userLimiter, tableLimiter, nsLimiter); + return quota; + } } } } diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/quotas/TimeBasedLimiter.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/quotas/TimeBasedLimiter.java index 6b5349f..59c2dc9 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/quotas/TimeBasedLimiter.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/quotas/TimeBasedLimiter.java @@ -133,7 +133,8 @@ public class TimeBasedLimiter implements QuotaLimiter { } private static void setFromTimedQuota(final RateLimiter limiter, final TimedQuota timedQuota) { - limiter.set(timedQuota.getSoftLimit(), ProtobufUtil.toTimeUnit(timedQuota.getTimeUnit())); + limiter.set(timedQuota.getSoftLimit(), ProtobufUtil.toTimeUnit(timedQuota.getTimeUnit()), + timedQuota.hasAllowExceed() ? timedQuota.getAllowExceed() : false); } @Override @@ -272,4 +273,15 @@ public class TimeBasedLimiter implements QuotaLimiter { builder.append(')'); return builder.toString(); } + + public boolean isAllowExceed() { + if (!isBypass()) { + return reqsLimiter.isAllowExceed() | reqSizeLimiter.isAllowExceed() + | reqCapacityUnitLimiter.isAllowExceed() | readReqsLimiter.isAllowExceed() + | readSizeLimiter.isAllowExceed() | writeReqsLimiter.isAllowExceed() + | writeSizeLimiter.isAllowExceed() | readCapacityUnitLimiter.isAllowExceed() + | writeCapacityUnitLimiter.isAllowExceed(); + } + return false; + } } diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/access/AccessController.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/access/AccessController.java index bbf129d..f99531c 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/security/access/AccessController.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/security/access/AccessController.java @@ -2514,6 +2514,12 @@ public class AccessController implements MasterCoprocessor, RegionCoprocessor, } @Override + public void preSetRegionServerQuota(ObserverContext ctx, + final String regionServer, GlobalQuotaSettings quotas) throws IOException { + requirePermission(ctx, "setRegionServerQuota", Action.ADMIN); + } + + @Override public ReplicationEndpoint postCreateReplicationEndPoint( ObserverContext ctx, ReplicationEndpoint endpoint) { return endpoint; diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/quotas/TestGlobalQuotaSettingsImpl.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/quotas/TestGlobalQuotaSettingsImpl.java index bd8a94a..55938dc 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/quotas/TestGlobalQuotaSettingsImpl.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/quotas/TestGlobalQuotaSettingsImpl.java @@ -58,9 +58,9 @@ public class TestGlobalQuotaSettingsImpl { QuotaProtos.ThrottleRequest writeThrottle = QuotaProtos.ThrottleRequest.newBuilder() .setTimedQuota(writeQuota).setType(QuotaProtos.ThrottleType.WRITE_NUMBER).build(); - GlobalQuotaSettingsImpl settings = new GlobalQuotaSettingsImpl("joe", null, null, quota); + GlobalQuotaSettingsImpl settings = new GlobalQuotaSettingsImpl("joe", null, null, null, quota); GlobalQuotaSettingsImpl merged = settings.merge( - new ThrottleSettings("joe", null, null, writeThrottle)); + new ThrottleSettings("joe", null, null, null, writeThrottle)); QuotaProtos.Throttle mergedThrottle = merged.getThrottleProto(); // Verify the request throttle is in place @@ -80,7 +80,7 @@ public class TestGlobalQuotaSettingsImpl { QuotaProtos.Quotas quota = QuotaProtos.Quotas.newBuilder() .setSpace(SPACE_QUOTA).build(); - GlobalQuotaSettingsImpl settings = new GlobalQuotaSettingsImpl(null, tn, null, quota); + GlobalQuotaSettingsImpl settings = new GlobalQuotaSettingsImpl(null, tn, null, null, quota); // Switch the violation policy to DISABLE GlobalQuotaSettingsImpl merged = settings.merge( new SpaceLimitSettings(tn, SPACE_QUOTA.getSoftLimit(), SpaceViolationPolicy.DISABLE)); @@ -96,7 +96,7 @@ public class TestGlobalQuotaSettingsImpl { final String ns = "org1"; QuotaProtos.Quotas quota = QuotaProtos.Quotas.newBuilder() .setThrottle(THROTTLE).setSpace(SPACE_QUOTA).build(); - GlobalQuotaSettingsImpl settings = new GlobalQuotaSettingsImpl(null, null, ns, quota); + GlobalQuotaSettingsImpl settings = new GlobalQuotaSettingsImpl(null, null, ns, null, quota); QuotaProtos.TimedQuota writeQuota = REQUEST_THROTTLE.toBuilder() .setSoftLimit(500).build(); @@ -105,7 +105,7 @@ public class TestGlobalQuotaSettingsImpl { .setTimedQuota(writeQuota).setType(QuotaProtos.ThrottleType.WRITE_NUMBER).build(); GlobalQuotaSettingsImpl merged = settings.merge( - new ThrottleSettings(null, null, ns, writeThrottle)); + new ThrottleSettings(null, null, ns, null, writeThrottle)); GlobalQuotaSettingsImpl finalQuota = merged.merge(new SpaceLimitSettings( ns, SPACE_QUOTA.getSoftLimit(), SpaceViolationPolicy.NO_WRITES_COMPACTIONS)); diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/quotas/TestQuotaAdmin.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/quotas/TestQuotaAdmin.java index ef7e47d..e6af3e5 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/quotas/TestQuotaAdmin.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/quotas/TestQuotaAdmin.java @@ -470,7 +470,7 @@ public class TestQuotaAdmin { admin.setQuota(settings); // Verify the Quota in the table - verifyRecordPresentInQuotaTable(throttleType, 2L, TimeUnit.HOURS); + verifyRecordPresentInQuotaTable(throttleType, 2L, TimeUnit.HOURS, false); // Verify we can retrieve it via the QuotaRetriever API verifyFetchableViaAPI(admin, throttleType, 2L, TimeUnit.HOURS); @@ -495,7 +495,7 @@ public class TestQuotaAdmin { admin.setQuota(settings); // Verify the Quota in the table - verifyRecordPresentInQuotaTable(ThrottleType.REQUEST_SIZE, 2L, TimeUnit.HOURS); + verifyRecordPresentInQuotaTable(ThrottleType.REQUEST_SIZE, 2L, TimeUnit.HOURS, false); // Verify we can retrieve it via the QuotaRetriever API verifyFetchableViaAPI(admin, ThrottleType.REQUEST_SIZE, 2L, TimeUnit.HOURS); @@ -506,7 +506,7 @@ public class TestQuotaAdmin { admin.setQuota(newSettings); // Verify the new Quota in the table - verifyRecordPresentInQuotaTable(ThrottleType.REQUEST_SIZE, 3L, TimeUnit.DAYS); + verifyRecordPresentInQuotaTable(ThrottleType.REQUEST_SIZE, 3L, TimeUnit.DAYS, false); // Verify we can retrieve the new quota via the QuotaRetriever API verifyFetchableViaAPI(admin, ThrottleType.REQUEST_SIZE, 3L, TimeUnit.DAYS); @@ -524,6 +524,31 @@ public class TestQuotaAdmin { } @Test + public void testSetAndRemoveRegionServerQuota() throws Exception { + Admin admin = TEST_UTIL.getAdmin(); + String regionServer = QuotaUtil.REGION_SERVER_QUOTA_KEY; + + admin.setQuota(QuotaSettingsFactory.throttleRegionServer(regionServer, + ThrottleType.REQUEST_NUMBER, 10, TimeUnit.MINUTES, true)); + assertNumResults(1, new QuotaFilter().setRegionServerFilter(regionServer)); + // Verify the Quota in the table + verifyRecordPresentInQuotaTable(ThrottleType.REQUEST_NUMBER, 10, TimeUnit.MINUTES, true); + + admin.setQuota(QuotaSettingsFactory.throttleRegionServer(regionServer, + ThrottleType.REQUEST_NUMBER, 20, TimeUnit.MINUTES, false)); + assertNumResults(1, new QuotaFilter().setRegionServerFilter(regionServer)); + // Verify the Quota in the table + verifyRecordPresentInQuotaTable(ThrottleType.REQUEST_NUMBER, 20, TimeUnit.MINUTES, false); + + admin.setQuota(QuotaSettingsFactory.throttleRegionServer(regionServer, ThrottleType.READ_NUMBER, + 30, TimeUnit.MINUTES, false)); + assertNumResults(2, new QuotaFilter().setRegionServerFilter(regionServer)); + + admin.setQuota(QuotaSettingsFactory.unthrottleRegionServer(regionServer)); + assertNumResults(0, new QuotaFilter().setRegionServerFilter(regionServer)); + } + + @Test public void testRpcThrottleWhenStartup() throws IOException, InterruptedException { TEST_UTIL.getAdmin().switchRpcThrottle(false); assertFalse(TEST_UTIL.getAdmin().isRpcThrottleEnabled()); @@ -561,15 +586,15 @@ public class TestQuotaAdmin { rs.getRegionServer().getRegionServerRpcQuotaManager().isRpcThrottleEnabled())); } - private void verifyRecordPresentInQuotaTable(ThrottleType type, long limit, TimeUnit tu) - throws Exception { + private void verifyRecordPresentInQuotaTable(ThrottleType type, long limit, TimeUnit tu, + boolean allowExceed) throws Exception { // Verify the RPC Quotas in the table try (Table quotaTable = TEST_UTIL.getConnection().getTable(QuotaTableUtil.QUOTA_TABLE_NAME); ResultScanner scanner = quotaTable.getScanner(new Scan())) { Result r = Iterables.getOnlyElement(scanner); CellScanner cells = r.cellScanner(); assertTrue("Expected to find a cell", cells.advance()); - assertRPCQuota(type, limit, tu, cells.current()); + assertRPCQuota(type, limit, tu, allowExceed, cells.current()); } } @@ -596,10 +621,10 @@ public class TestQuotaAdmin { } } - private void assertRPCQuota(ThrottleType type, long limit, TimeUnit tu, Cell cell) - throws Exception { - Quotas q = QuotaTableUtil - .quotasFromData(cell.getValueArray(), cell.getValueOffset(), cell.getValueLength()); + private void assertRPCQuota(ThrottleType type, long limit, TimeUnit tu, boolean allowExceed, + Cell cell) throws Exception { + Quotas q = QuotaTableUtil.quotasFromData(cell.getValueArray(), cell.getValueOffset(), + cell.getValueLength()); assertTrue("Quota should have rpc quota defined", q.hasThrottle()); QuotaProtos.Throttle rpcQuota = q.getThrottle(); @@ -647,6 +672,7 @@ public class TestQuotaAdmin { assertEquals(t.getSoftLimit(), limit); assertEquals(t.getTimeUnit(), ProtobufUtil.toProtoTimeUnit(tu)); + assertEquals(allowExceed, t.getAllowExceed()); } private void assertRPCQuota(ThrottleType type, long limit, TimeUnit tu, diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/quotas/TestQuotaThrottle.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/quotas/TestQuotaThrottle.java index c069403..11e4d4e 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/quotas/TestQuotaThrottle.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/quotas/TestQuotaThrottle.java @@ -570,6 +570,50 @@ public class TestQuotaThrottle { triggerTableCacheRefresh(true, TABLE_NAMES[0]); } + @Test + public void testRegionServerThrottle() throws Exception { + final Admin admin = TEST_UTIL.getAdmin(); + admin.setQuota(QuotaSettingsFactory.throttleTable(TABLE_NAMES[0], ThrottleType.WRITE_NUMBER, 5, + TimeUnit.MINUTES)); + + // don't allow exceed region server quota and requests is limited by table quota + admin.setQuota(QuotaSettingsFactory.throttleRegionServer(QuotaUtil.REGION_SERVER_QUOTA_KEY, + ThrottleType.WRITE_NUMBER, 7, TimeUnit.MINUTES, false)); + triggerCacheRefresh(false, false, true, false, true, TABLE_NAMES[0]); + waitMinuteQuota(); + assertEquals(5, doPuts(10, tables[0])); + + // don't allow exceed region server quota and requests is limited by region server quota + admin.setQuota(QuotaSettingsFactory.throttleRegionServer(QuotaUtil.REGION_SERVER_QUOTA_KEY, + ThrottleType.WRITE_NUMBER, 4, TimeUnit.MINUTES, false)); + triggerCacheRefresh(false, false, false, false, true, TABLE_NAMES[0]); + waitMinuteQuota(); + assertEquals(4, doPuts(10, tables[0])); + + // unthrottle + admin.setQuota(QuotaSettingsFactory.unthrottleRegionServer(QuotaUtil.REGION_SERVER_QUOTA_KEY)); + admin.setQuota(QuotaSettingsFactory.unthrottleTable(TABLE_NAMES[0])); + triggerCacheRefresh(true, false, true, false, true, TABLE_NAMES[0]); + } + + @Test + public void testRegionServerThrottleAllowExceed() throws Exception { + final Admin admin = TEST_UTIL.getAdmin(); + admin.setQuota(QuotaSettingsFactory.throttleTable(TABLE_NAMES[0], ThrottleType.WRITE_NUMBER, 5, + TimeUnit.MINUTES)); + // allow exceed region server quota + admin.setQuota(QuotaSettingsFactory.throttleRegionServer(QuotaUtil.REGION_SERVER_QUOTA_KEY, + ThrottleType.WRITE_NUMBER, 20, TimeUnit.MINUTES, true)); + triggerCacheRefresh(false, false, true, false, true, TABLE_NAMES[0]); + waitMinuteQuota(); + assertEquals(10, doPuts(10, tables[0])); + + // unthrottle + admin.setQuota(QuotaSettingsFactory.unthrottleRegionServer(QuotaUtil.REGION_SERVER_QUOTA_KEY)); + admin.setQuota(QuotaSettingsFactory.unthrottleTable(TABLE_NAMES[0])); + triggerCacheRefresh(true, false, true, false, true, TABLE_NAMES[0]); + } + private int doPuts(int maxOps, final Table... tables) throws Exception { return doPuts(maxOps, -1, tables); } @@ -622,19 +666,19 @@ public class TestQuotaThrottle { } private void triggerUserCacheRefresh(boolean bypass, TableName... tables) throws Exception { - triggerCacheRefresh(bypass, true, false, false, tables); + triggerCacheRefresh(bypass, true, false, false, false, tables); } private void triggerTableCacheRefresh(boolean bypass, TableName... tables) throws Exception { - triggerCacheRefresh(bypass, false, true, false, tables); + triggerCacheRefresh(bypass, false, true, false, false, tables); } private void triggerNamespaceCacheRefresh(boolean bypass, TableName... tables) throws Exception { - triggerCacheRefresh(bypass, false, false, true, tables); + triggerCacheRefresh(bypass, false, false, true, false, tables); } private void triggerCacheRefresh(boolean bypass, boolean userLimiter, boolean tableLimiter, - boolean nsLimiter, final TableName... tables) throws Exception { + boolean nsLimiter, boolean rsLimiter, final TableName... tables) throws Exception { envEdge.incValue(2 * REFRESH_TIME); for (RegionServerThread rst: TEST_UTIL.getMiniHBaseCluster().getRegionServerThreads()) { RegionServerRpcQuotaManager quotaManager = rst.getRegionServer().getRegionServerRpcQuotaManager(); @@ -663,6 +707,9 @@ public class TestQuotaThrottle { if (nsLimiter) { isBypass &= quotaCache.getNamespaceLimiter(table.getNamespaceAsString()).isBypass(); } + if (rsLimiter) { + isBypass &= quotaCache.getRegionServerQuotaLimiter(QuotaUtil.REGION_SERVER_QUOTA_KEY).isBypass(); + } if (isBypass != bypass) { envEdge.incValue(100); isUpdated = false; @@ -675,6 +722,7 @@ public class TestQuotaThrottle { LOG.debug(Objects.toString(quotaCache.getNamespaceQuotaCache())); LOG.debug(Objects.toString(quotaCache.getTableQuotaCache())); LOG.debug(Objects.toString(quotaCache.getUserQuotaCache())); + LOG.debug(Objects.toString(quotaCache.getRegionServerQuotaCache())); } } diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestAccessController.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestAccessController.java index 2d37f30..9b8c8a6 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestAccessController.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/security/access/TestAccessController.java @@ -2700,6 +2700,15 @@ public class TestAccessController extends SecureTestUtil { } }; + AccessTestAction setRegionServerQuotaAction = new AccessTestAction() { + @Override + public Object run() throws Exception { + ACCESS_CONTROLLER.preSetRegionServerQuota(ObserverContextImpl.createAndPrepare(CP_ENV), + null, null); + return null; + } + }; + verifyAllowed(setUserQuotaAction, SUPERUSER, USER_ADMIN, USER_GROUP_ADMIN); verifyDenied(setUserQuotaAction, USER_CREATE, USER_RW, USER_RO, USER_NONE, USER_OWNER, USER_GROUP_READ, USER_GROUP_WRITE, USER_GROUP_CREATE); @@ -2718,6 +2727,10 @@ public class TestAccessController extends SecureTestUtil { verifyAllowed(setNamespaceQuotaAction, SUPERUSER, USER_ADMIN, USER_GROUP_ADMIN); verifyDenied(setNamespaceQuotaAction, USER_CREATE, USER_RW, USER_RO, USER_NONE, USER_OWNER, USER_GROUP_READ, USER_GROUP_WRITE, USER_GROUP_CREATE); + + verifyAllowed(setRegionServerQuotaAction, SUPERUSER, USER_ADMIN, USER_GROUP_ADMIN); + verifyDenied(setRegionServerQuotaAction, USER_CREATE, USER_RW, USER_RO, USER_NONE, USER_OWNER, + USER_GROUP_READ, USER_GROUP_WRITE, USER_GROUP_CREATE); } @Test diff --git a/hbase-shell/src/main/ruby/hbase/quotas.rb b/hbase-shell/src/main/ruby/hbase/quotas.rb index 38cb3e3..a4bfc0d 100644 --- a/hbase-shell/src/main/ruby/hbase/quotas.rb +++ b/hbase-shell/src/main/ruby/hbase/quotas.rb @@ -81,8 +81,12 @@ module Hbase namespace = args.delete(NAMESPACE) raise(ArgumentError, 'Unexpected arguments: ' + args.inspect) unless args.empty? settings = QuotaSettingsFactory.throttleNamespace(namespace, type, limit, time_unit) + elsif args.key?(REGIONSERVER) + allow_exceed = args.fetch(ALLOW_EXCEED, FALSE) + # NOTE: use 'rs' for all region servers, not support set single region server quota currently + settings = QuotaSettingsFactory.throttleRegionServer('rs', type, limit, time_unit, allow_exceed) else - raise 'One of USER, TABLE or NAMESPACE must be specified' + raise 'One of USER, TABLE, NAMESPACE or REGIONSERVER must be specified' end @admin.setQuota(settings) end @@ -111,8 +115,13 @@ module Hbase namespace = args.delete(NAMESPACE) raise(ArgumentError, 'Unexpected arguments: ' + args.inspect) unless args.empty? settings = QuotaSettingsFactory.unthrottleNamespace(namespace) + elsif args.key?(REGIONSERVER) + regionServer = args.delete(REGIONSERVER) + raise(ArgumentError, 'Unexpected arguments: ' + args.inspect) unless args.empty? + # NOTE: use 'rs' for all region servers, not support set single region server quota currently + settings = QuotaSettingsFactory.unthrottleRegionServer('rs') else - raise 'One of USER, TABLE or NAMESPACE must be specified' + raise 'One of USER, TABLE, NAMESPACE or REGIONSERVER must be specified' end @admin.setQuota(settings) end @@ -225,7 +234,8 @@ module Hbase owner = { USER => settings.getUserName, TABLE => settings.getTableName, - NAMESPACE => settings.getNamespace + NAMESPACE => settings.getNamespace, + REGIONSERVER => settings.getRegionServer }.delete_if { |_k, v| v.nil? }.map { |k, v| k.to_s + ' => ' + v.to_s } * ', ' yield owner, settings.to_s diff --git a/hbase-shell/src/main/ruby/hbase_constants.rb b/hbase-shell/src/main/ruby/hbase_constants.rb index 9871685..447a1e3 100644 --- a/hbase-shell/src/main/ruby/hbase_constants.rb +++ b/hbase-shell/src/main/ruby/hbase_constants.rb @@ -93,6 +93,7 @@ module HBaseConstants FORMATTER_CLASS = 'FORMATTER_CLASS'.freeze POLICY = 'POLICY'.freeze REGIONSERVER = 'REGIONSERVER'.freeze + ALLOW_EXCEED = 'ALLOW_EXCEED'.freeze # Load constants from hbase java API def self.promote_constants(constants) diff --git a/hbase-shell/src/main/ruby/shell/commands/set_quota.rb b/hbase-shell/src/main/ruby/shell/commands/set_quota.rb index 3a5c136..d271bef 100644 --- a/hbase-shell/src/main/ruby/shell/commands/set_quota.rb +++ b/hbase-shell/src/main/ruby/shell/commands/set_quota.rb @@ -22,7 +22,7 @@ module Shell class SetQuota < Command def help <<-EOF -Set a quota for a user, table, or namespace. +Set a quota for a user, table, namespace or region server. Syntax : set_quota TYPE => , TYPE => THROTTLE @@ -55,6 +55,10 @@ For example: hbase> set_quota TYPE => THROTTLE, USER => 'u1', LIMIT => NONE hbase> set_quota TYPE => THROTTLE, THROTTLE_TYPE => WRITE, USER => 'u1', LIMIT => NONE + hbase> set_quota TYPE => THROTTLE, REGIONSERVER => 'rs', LIMIT => '10req/sec', ALLOW_EXCEED => true + hbase> set_quota TYPE => THROTTLE, REGIONSERVER => 'rs', LIMIT => '10k/sec', ALLOW_EXCEED => false + hbase> set_quota TYPE => THROTTLE, REGIONSERVER => 'rs', LIMIT => NONE + hbase> set_quota USER => 'u1', GLOBAL_BYPASS => true TYPE => SPACE diff --git a/hbase-shell/src/test/ruby/hbase/quotas_test.rb b/hbase-shell/src/test/ruby/hbase/quotas_test.rb index 981001a..0b56988 100644 --- a/hbase-shell/src/test/ruby/hbase/quotas_test.rb +++ b/hbase-shell/src/test/ruby/hbase/quotas_test.rb @@ -170,5 +170,18 @@ module Hbase output = capture_stdout { command(:enable_rpc_throttle) } assert(output.include?('Previous rpc throttle state : false')) end + + define_test 'can set and remove region server quota' do + command(:set_quota, TYPE => THROTTLE, REGIONSERVER => 'rs', LIMIT => '1CU/sec') + output = capture_stdout{ command(:list_quotas) } + assert(output.include?('REGIONSERVER => rs')) + assert(output.include?('TYPE => THROTTLE')) + assert(output.include?('THROTTLE_TYPE => REQUEST_CAPACITY_UNIT')) + assert(output.include?('LIMIT => 1CU/sec')) + + command(:set_quota, TYPE => THROTTLE, REGIONSERVER => 'rs', LIMIT => NONE) + output = capture_stdout{ command(:list_quotas) } + assert(output.include?('0 row(s)')) + end end end -- 2.7.4