From 508dfe9da908dce84601ca83f934fc8020b8d3de Mon Sep 17 00:00:00 2001 From: meiyi Date: Thu, 16 Aug 2018 16:10:20 +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 | 27 ++++- .../hadoop/hbase/quotas/QuotaSettingsFactory.java | 113 ++++++++++++++------- .../apache/hadoop/hbase/quotas/QuotaTableUtil.java | 62 ++++++++++- .../hadoop/hbase/quotas/SpaceLimitSettings.java | 10 +- .../hadoop/hbase/quotas/ThrottleSettings.java | 20 ++-- .../hadoop/hbase/shaded/protobuf/ProtobufUtil.java | 22 +++- .../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 | 65 ++++++++++++ .../hadoop/hbase/quotas/DefaultOperationQuota.java | 13 ++- .../hadoop/hbase/quotas/GlobalQuotaSettings.java | 5 +- .../hbase/quotas/GlobalQuotaSettingsImpl.java | 27 ++--- .../hadoop/hbase/quotas/MasterQuotaManager.java | 63 ++++++++++-- .../hadoop/hbase/quotas/NoopQuotaLimiter.java | 5 + .../org/apache/hadoop/hbase/quotas/QuotaCache.java | 27 +++++ .../apache/hadoop/hbase/quotas/QuotaLimiter.java | 3 + .../org/apache/hadoop/hbase/quotas/QuotaUtil.java | 22 ++++ .../apache/hadoop/hbase/quotas/RateLimiter.java | 27 ++++- .../hbase/quotas/RegionServerRpcQuotaManager.java | 15 ++- .../hadoop/hbase/quotas/TimeBasedLimiter.java | 12 ++- .../hbase/security/access/AccessController.java | 6 ++ .../hbase/quotas/TestGlobalQuotaSettingsImpl.java | 10 +- .../apache/hadoop/hbase/quotas/TestQuotaAdmin.java | 15 +++ .../hadoop/hbase/quotas/TestQuotaThrottle.java | 30 +++++- .../security/access/TestAccessController.java | 13 +++ hbase-shell/src/main/ruby/hbase/quotas.rb | 16 ++- hbase-shell/src/main/ruby/hbase_constants.rb | 1 + 32 files changed, 606 insertions(+), 142 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..6d62421 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..85b113b 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 @@ -34,12 +34,14 @@ 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 2a20c51..5a04714 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 @@ -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,32 +123,33 @@ 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())); } return settings; } @@ -183,7 +191,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); } /** @@ -198,7 +206,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); } /** @@ -213,7 +221,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); } /** @@ -223,7 +231,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); } /** @@ -234,7 +242,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); } /** @@ -245,7 +253,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); } /** @@ -259,7 +267,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); } /** @@ -269,7 +277,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); } /** @@ -283,7 +291,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); } /** @@ -293,12 +301,43 @@ 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) { @@ -307,7 +346,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()); } /* ========================================================================== @@ -322,7 +361,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..504d194 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 @@ -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 02bd6e4..13bba85 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 e424d8a..a71c394 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 @@ -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; } @@ -105,6 +105,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"); } @@ -140,7 +143,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; @@ -153,12 +157,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 24d2ab7..e68d3ee 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 @@ -2510,11 +2510,23 @@ 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 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 c2ab180..bd68e17 100644 --- a/hbase-protocol-shaded/src/main/protobuf/Master.proto +++ b/hbase-protocol-shaded/src/main/protobuf/Master.proto @@ -570,6 +570,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 cd4c7df..2b4af3a 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 573ac7a..70e145a 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 @@ -1076,6 +1076,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 019c64f..964774e 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 @@ -1235,6 +1235,16 @@ 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 postSetTableQuota( final TableName table, final GlobalQuotaSettings quotas) throws IOException { execOperation(coprocEnvironments.isEmpty() ? null : new MasterObserverOperation() { @@ -1264,6 +1274,16 @@ public class MasterCoprocessorHost } }); } + + 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 { 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..239a8b7 --- /dev/null +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/quotas/AllowExceedOperationQuota.java @@ -0,0 +1,65 @@ +/** + * 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.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(QuotaLimiter regionServerLimiter, + final QuotaLimiter... limiters) { + super(limiters); + this.regionServerLimiter = regionServerLimiter; + } + + @Override + public void checkQuota(int numWrites, int numReads, int numScans) throws RpcThrottlingException { + try { + super.checkQuota(numWrites, numReads, numScans); + } catch (RpcThrottlingException e) { + LOG.debug("Read/Write requests num exceeds quota: writes:{} reads:{} scan:{}, " + + "try use region server quota", + numWrites, numReads, numScans); + } + if (regionServerLimiter.isBypass()) return; + + readAvailable = Math.max(readAvailable, regionServerLimiter.getReadAvailable()); + writeAvailable = Math.max(writeAvailable, regionServerLimiter.getWriteAvailable()); + regionServerLimiter.checkQuota(numWrites, writeConsumed, numReads + numScans, readConsumed); + regionServerLimiter.grabQuota(numWrites, writeConsumed, numReads + numScans, readConsumed); + } + + @Override + public void close() { + super.close(); + // Adjust the quota consumed for the specified operation + long writeDiff = operationSize[OperationType.MUTATE.ordinal()] - writeConsumed; + long readDiff = operationSize[OperationType.GET.ordinal()] + + operationSize[OperationType.SCAN.ordinal()] - readConsumed; + if (writeDiff != 0) regionServerLimiter.consumeWrite(writeDiff); + if (readDiff != 0) regionServerLimiter.consumeRead(readDiff); + } +} 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 1265a42..750e565 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 @@ -34,11 +34,11 @@ public class DefaultOperationQuota implements OperationQuota { private static final Logger LOG = LoggerFactory.getLogger(DefaultOperationQuota.class); private final List limiters; - private long writeAvailable = 0; - private long readAvailable = 0; - private long writeConsumed = 0; - private long readConsumed = 0; - private final long[] operationSize; + protected long writeAvailable = 0; + protected long readAvailable = 0; + protected long writeConsumed = 0; + protected long readConsumed = 0; + protected final long[] operationSize; public DefaultOperationQuota(final QuotaLimiter... limiters) { this(Arrays.asList(limiters)); @@ -68,10 +68,9 @@ public class DefaultOperationQuota implements OperationQuota { readAvailable = Long.MAX_VALUE; for (final QuotaLimiter limiter: limiters) { if (limiter.isBypass()) continue; - - limiter.checkQuota(numWrites, writeConsumed, numReads + numScans, readConsumed); readAvailable = Math.min(readAvailable, limiter.getReadAvailable()); writeAvailable = Math.min(writeAvailable, limiter.getWriteAvailable()); + limiter.checkQuota(numWrites, writeConsumed, numReads + numScans, readConsumed); } for (final QuotaLimiter limiter: limiters) { 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 107523b..77cdf88 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 @@ -34,8 +34,9 @@ import org.apache.yetus.audience.InterfaceStability; @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 3119691..581b948 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 @@ -45,18 +45,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 +67,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)); @@ -197,7 +197,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())); } @@ -242,6 +242,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 bdeab80..fd038bd 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 @@ -76,6 +76,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; @@ -102,6 +103,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); @@ -151,9 +153,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(); } @@ -163,8 +172,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 { @@ -190,8 +199,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 { @@ -218,8 +227,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 { @@ -248,8 +257,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 { @@ -275,8 +284,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 { @@ -298,6 +307,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 3cca955..aae6ace 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 @@ -78,4 +78,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..97c007c 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 @@ -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,15 @@ public class QuotaCache implements Stoppable { } /** + * Returns the limiter associated to the region server. + * @param regionServer the region server to limit + * @return the limiter associated to the 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. */ @@ -203,10 +213,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 +269,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 7cb29b3..a6dbfa1 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 @@ -72,4 +72,7 @@ public interface QuotaLimiter { /** @return the number of bytes available to write to avoid exceeding the quota */ long getWriteAvailable(); + + /** @return 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 6bc3ce9..84227dd 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 @@ -56,6 +56,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"; /** Table descriptor for Quota internal table */ public static final HTableDescriptor QUOTA_TABLE_DESC = @@ -136,6 +137,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); @@ -226,6 +237,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..2118737 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 to 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,13 @@ public abstract class RateLimiter { if (getLimit() == Long.MAX_VALUE) { return rateLimiter + "(Bypass)"; } - return rateLimiter + "(avail=" + getAvailable() + " limit=" + getLimit() + - " tunit=" + getTimeUnitInMillis() + ")"; + StringBuilder sb = new StringBuilder(); + sb.append(rateLimiter).append("(avail=").append(getAvailable()).append(" limit=") + .append(getLimit()).append(" tunit=").append(getTimeUnitInMillis()); + if (isAllowExceed()) { + sb.append(" allowExceed=").append(isAllowExceed()); + } + return sb.append(")").toString(); } /** @@ -229,6 +246,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 7c21f45..4d3b732 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 @@ -102,7 +102,8 @@ public class RegionServerRpcQuotaManager { LOG.trace("get quota for ugi=" + ugi + " table=" + table + " userLimiter=" + userLimiter); } if (!useNoop) { - return new DefaultOperationQuota(userLimiter); + DefaultOperationQuota quota = new DefaultOperationQuota(userLimiter); + return quota; } } else { QuotaLimiter nsLimiter = quotaCache.getNamespaceLimiter(table.getNamespaceAsString()); @@ -113,7 +114,17 @@ public class RegionServerRpcQuotaManager { userLimiter + " tableLimiter=" + tableLimiter + " nsLimiter=" + nsLimiter); } if (!useNoop) { - return new DefaultOperationQuota(userLimiter, tableLimiter, nsLimiter); + QuotaLimiter rsLimiter = + quotaCache.getRegionServerQuotaLimiter(QuotaUtil.REGION_SERVER_QUOTA_KEY); + if (!rsLimiter.isAllowExceed()) { + DefaultOperationQuota quota = + new DefaultOperationQuota(userLimiter, tableLimiter, nsLimiter, rsLimiter); + return quota; + } else { + AllowExceedOperationQuota quota = + new AllowExceedOperationQuota(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 02dffcf..d4a513c 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 @@ -106,7 +106,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 @@ -198,4 +199,13 @@ public class TimeBasedLimiter implements QuotaLimiter { builder.append(')'); return builder.toString(); } + + public boolean isAllowExceed() { + if (!isBypass()) { + return reqsLimiter.isAllowExceed() | reqSizeLimiter.isAllowExceed() + | readReqsLimiter.isAllowExceed() | readSizeLimiter.isAllowExceed() + | writeReqsLimiter.isAllowExceed() | writeSizeLimiter.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 1100500..21216f8 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 @@ -2485,6 +2485,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 b84dc83..6dedf4f 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 @@ -515,6 +515,21 @@ 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)); + admin.setQuota(QuotaSettingsFactory.throttleRegionServer(regionServer, + ThrottleType.REQUEST_NUMBER, 10, TimeUnit.MINUTES, false)); + assertNumResults(1, new QuotaFilter().setRegionServerFilter(regionServer)); + + admin.setQuota(QuotaSettingsFactory.unthrottleRegionServer(regionServer)); + assertNumResults(0, new QuotaFilter().setRegionServerFilter(regionServer)); + } + private void verifyRecordPresentInQuotaTable(ThrottleType type, long limit, TimeUnit tu) throws Exception { // Verify the RPC Quotas in the table 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 59ba322..f6f5a76 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 @@ -28,6 +28,7 @@ import org.apache.hadoop.hbase.TableName; import org.apache.hadoop.hbase.client.Admin; import org.apache.hadoop.hbase.client.Get; import org.apache.hadoop.hbase.client.Put; +import org.apache.hadoop.hbase.client.RetriesExhaustedException; import org.apache.hadoop.hbase.client.Table; import org.apache.hadoop.hbase.security.User; import org.apache.hadoop.hbase.testclassification.MediumTests; @@ -509,6 +510,20 @@ public class TestQuotaThrottle { assertEquals(30, doGets(30, tables[1])); } + @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)); + admin.setQuota(QuotaSettingsFactory.throttleRegionServer(QuotaUtil.REGION_SERVER_QUOTA_KEY, + ThrottleType.WRITE_NUMBER, 9, TimeUnit.MINUTES, true)); + triggerCacheRefresh(false, false, true, false, true, TABLE_NAMES[0]); + assertEquals(9, doPuts(10, tables[0])); + 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 { int count = 0; try { @@ -522,6 +537,8 @@ public class TestQuotaThrottle { } } catch (RpcThrottlingException e) { LOG.error("put failed after nRetries=" + count, e); + } catch (RetriesExhaustedException e) { + LOG.error("put failed after nRetries=" + count, e); } return count; } @@ -538,24 +555,26 @@ public class TestQuotaThrottle { } } catch (RpcThrottlingException e) { LOG.error("get failed after nRetries=" + count, e); + } catch (RetriesExhaustedException e) { + LOG.error("put failed after nRetries=" + count, e); } return count; } 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(); @@ -584,6 +603,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; 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 163c2ec..66a88c3 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 @@ -2656,6 +2656,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); @@ -2674,6 +2683,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 1ea8d28..fa757af 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) + exceed = args.fetch(ALLOW_EXCEED, FALSE) + # use 'rs' for all region servers, not support set single region server quota + settings = QuotaSettingsFactory.throttleRegionServer('rs', type, limit, time_unit, 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? + # use 'rs' for all region servers, not support set single region server quota + 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 2870dfb..1882302 100644 --- a/hbase-shell/src/main/ruby/hbase_constants.rb +++ b/hbase-shell/src/main/ruby/hbase_constants.rb @@ -90,6 +90,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) -- 2.7.4