3 # Licensed to the Apache Software Foundation (ASF) under one
4 # or more contributor license agreements. See the NOTICE file
5 # distributed with this work for additional information
6 # regarding copyright ownership. The ASF licenses this file
7 # to you under the Apache License, Version 2.0 (the
8 # "License"); you may not use this file except in compliance
9 # with the License. You may obtain a copy of the License at
11 # http://www.apache.org/licenses/LICENSE-2.0
13 # Unless required by applicable law or agreed to in writing, software
14 # distributed under the License is distributed on an "AS IS" BASIS,
15 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 # See the License for the specific language governing permissions and
17 # limitations under the License.
21 java_import java.util.concurrent.TimeUnit
22 java_import org.apache.hadoop.hbase.TableName
23 java_import org.apache.hadoop.hbase.ServerName
24 java_import org.apache.hadoop.hbase.quotas.ThrottleType
25 java_import org.apache.hadoop.hbase.quotas.QuotaFilter
26 java_import org.apache.hadoop.hbase.quotas.QuotaRetriever
27 java_import org.apache.hadoop.hbase.quotas.QuotaScope
28 java_import org.apache.hadoop.hbase.quotas.QuotaSettingsFactory
29 java_import org.apache.hadoop.hbase.quotas.QuotaTableUtil
30 java_import org.apache.hadoop.hbase.quotas.SpaceViolationPolicy
32 module HBaseQuotasConstants
33 QUOTA_TABLE_NAME = QuotaTableUtil::QUOTA_TABLE_NAME
35 GLOBAL_BYPASS = 'GLOBAL_BYPASS'.freeze
36 THROTTLE_TYPE = 'THROTTLE_TYPE'.freeze
37 THROTTLE = 'THROTTLE'.freeze
38 REQUEST = 'REQUEST'.freeze
39 WRITE = 'WRITE'.freeze
41 SCOPE = 'SCOPE'.freeze
42 CLUSTER = 'CLUSTER'.freeze
43 MACHINE = 'MACHINE'.freeze
44 # Space quota constants
45 SPACE = 'SPACE'.freeze
46 NO_INSERTS = 'NO_INSERTS'.freeze
47 NO_WRITES = 'NO_WRITES'.freeze
48 NO_WRITES_COMPACTIONS = 'NO_WRITES_COMPACTIONS'.freeze
49 DISABLE = 'DISABLE'.freeze
50 READ_NUMBER = 'READ_NUMBER'.freeze
51 READ_SIZE = 'READ_SIZE'.freeze
52 WRITE_NUMBER = 'WRITE_NUMBER'.freeze
53 WRITE_SIZE = 'WRITE_SIZE'.freeze
54 REQUEST_NUMBER = 'REQUEST_NUMBER'.freeze
55 REQUEST_SIZE = 'REQUEST_SIZE'.freeze
56 REQUEST_CAPACITY_UNIT = 'REQUEST_CAPACITY_UNIT'.freeze
57 WRITE_CAPACITY_UNIT = 'WRITE_CAPACITY_UNIT'.freeze
58 READ_CAPACITY_UNIT = 'READ_CAPACITY_UNIT'.freeze
62 # rubocop:disable Metrics/ClassLength
73 raise(ArgumentError, 'Arguments should be a Hash') unless args.is_a?(Hash)
74 type = args.fetch(THROTTLE_TYPE, REQUEST)
75 args.delete(THROTTLE_TYPE)
76 type, limit, time_unit = _parse_limit(args.delete(LIMIT), ThrottleType, type)
77 scope = _parse_scope(args.fetch(SCOPE, MACHINE))
80 user = args.delete(USER)
82 table = TableName.valueOf(args.delete(TABLE))
83 raise(ArgumentError, 'Unexpected arguments: ' + args.inspect) unless args.empty?
84 settings = QuotaSettingsFactory.throttleUser(user, table, type, limit, time_unit, scope)
85 elsif args.key?(NAMESPACE)
86 namespace = args.delete(NAMESPACE)
87 raise(ArgumentError, 'Unexpected arguments: ' + args.inspect) unless args.empty?
88 settings = QuotaSettingsFactory.throttleUser(user, namespace, type, limit, time_unit, scope)
90 raise(ArgumentError, 'Unexpected arguments: ' + args.inspect) unless args.empty?
91 settings = QuotaSettingsFactory.throttleUser(user, type, limit, time_unit, scope)
93 elsif args.key?(TABLE)
94 table = TableName.valueOf(args.delete(TABLE))
95 raise(ArgumentError, 'Unexpected arguments: ' + args.inspect) unless args.empty?
96 settings = QuotaSettingsFactory.throttleTable(table, type, limit, time_unit, scope)
97 elsif args.key?(NAMESPACE)
98 namespace = args.delete(NAMESPACE)
99 raise(ArgumentError, 'Unexpected arguments: ' + args.inspect) unless args.empty?
100 settings = QuotaSettingsFactory.throttleNamespace(namespace, type, limit, time_unit, scope)
101 elsif args.key?(REGIONSERVER)
102 # TODO: Setting specified region server quota isn't supported currently and using 'all' for all RS
103 if scope == QuotaScope.valueOf(CLUSTER)
104 raise(ArgumentError, 'Invalid region server throttle scope, must be MACHINE')
106 settings = QuotaSettingsFactory.throttleRegionServer('all', type, limit, time_unit)
108 raise 'One of USER, TABLE, NAMESPACE or REGIONSERVER must be specified'
110 @admin.setQuota(settings)
114 raise(ArgumentError, 'Arguments should be a Hash') unless args.is_a?(Hash)
116 if args.key?(USER) then settings = unthrottle_user_table_namespace(args)
117 elsif args.key?(TABLE) then settings = unthrottle_table(args)
118 elsif args.key?(NAMESPACE) then settings = unthrottle_namespace(args)
119 elsif args.key?(REGIONSERVER)
120 settings = unthrottle_regionserver(args)
122 raise 'One of USER, TABLE, NAMESPACE or REGIONSERVER must be specified'
124 @admin.setQuota(settings)
127 def _parse_throttle_type(type_cls, throttle_type)
128 type_cls.valueOf(throttle_type)
131 def get_throttle_type(args)
132 throttle_type_str = args.delete(THROTTLE_TYPE)
133 throttle_type = _parse_throttle_type(ThrottleType, throttle_type_str)
137 def unthrottle_user_table_namespace(args)
138 user = args.delete(USER)
139 settings = if args.key?(TABLE)
140 unthrottle_user_table(args, user)
141 elsif args.key?(NAMESPACE)
142 unthrottle_user_namespace(args, user)
144 unthrottle_user(args, user)
150 return if args.empty?
153 'Unexpected arguments: ' + args.inspect)
156 def unthrottle_user_table(args, user)
157 table = TableName.valueOf(args.delete(TABLE))
158 if args.key?(THROTTLE_TYPE)
159 settings = QuotaSettingsFactory
160 .unthrottleUserByThrottleType(user,
161 table, get_throttle_type(args))
164 settings = QuotaSettingsFactory.unthrottleUser(user, table)
169 def unthrottle_user_namespace(args, user)
170 namespace = args.delete(NAMESPACE)
171 if args.key?(THROTTLE_TYPE)
172 throttle_type = get_throttle_type(args)
173 settings = QuotaSettingsFactory
174 .unthrottleUserByThrottleType(user, namespace, throttle_type)
177 settings = QuotaSettingsFactory.unthrottleUser(user, namespace)
182 def unthrottle_user(args, user)
183 if args.key?(THROTTLE_TYPE)
184 throttle_type = get_throttle_type(args)
185 settings = QuotaSettingsFactory
186 .unthrottleUserByThrottleType(user, throttle_type)
189 settings = QuotaSettingsFactory.unthrottleUser(user)
194 def unthrottle_table(args)
195 table = TableName.valueOf(args.delete(TABLE))
196 if args.key?(THROTTLE_TYPE)
197 throttle_type = get_throttle_type(args)
198 settings = QuotaSettingsFactory
199 .unthrottleTableByThrottleType(table, throttle_type)
202 settings = QuotaSettingsFactory.unthrottleTable(table)
207 def unthrottle_namespace(args)
208 namespace = args.delete(NAMESPACE)
209 if args.key?(THROTTLE_TYPE)
210 throttle_type = get_throttle_type(args)
211 settings = QuotaSettingsFactory
212 .unthrottleNamespaceByThrottleType(namespace, throttle_type)
215 settings = QuotaSettingsFactory.unthrottleNamespace(namespace)
220 def unthrottle_regionserver(args)
221 _region_server = args.delete(REGIONSERVER)
222 if args.key?(THROTTLE_TYPE)
223 throttle_type = get_throttle_type(args)
224 settings = QuotaSettingsFactory
225 .unthrottleRegionServerByThrottleType('all', throttle_type)
228 settings = QuotaSettingsFactory.unthrottleRegionServer('all')
233 # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
234 # rubocop:disable Metrics/MethodLength, Metrics/PerceivedComplexity
235 def limit_space(args)
236 raise(ArgumentError, 'Argument should be a Hash') unless !args.nil? && args.is_a?(Hash)
237 # Let the user provide a raw number
238 limit = if args[LIMIT].is_a?(Numeric)
241 # Parse a string a 1K, 2G, etc.
242 _parse_size(args[LIMIT])
245 raise(ArgumentError, 'Invalid space limit, must be greater than 0')
248 # Extract the policy, failing if something bogus was provided
249 policy = SpaceViolationPolicy.valueOf(args[POLICY])
250 # Create a table or namespace quota
252 if args.key?(NAMESPACE)
253 raise(ArgumentError, 'Only one of TABLE or NAMESPACE can be specified.')
255 settings = QuotaSettingsFactory.limitTableSpace(TableName.valueOf(args.delete(TABLE)), limit, policy)
256 elsif args.key?(NAMESPACE)
258 raise(ArgumentError, 'Only one of TABLE or NAMESPACE can be specified.')
260 settings = QuotaSettingsFactory.limitNamespaceSpace(args.delete(NAMESPACE), limit, policy)
262 raise(ArgumentError, 'One of TABLE or NAMESPACE must be specified.')
265 @admin.setQuota(settings)
267 # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity
268 # rubocop:enable Metrics/MethodLength, Metrics/PerceivedComplexity
270 def remove_space_limit(args)
271 raise(ArgumentError, 'Argument should be a Hash') unless !args.nil? && args.is_a?(Hash)
273 if args.key?(NAMESPACE)
274 raise(ArgumentError, 'Only one of TABLE or NAMESPACE can be specified.')
276 table = TableName.valueOf(args.delete(TABLE))
277 settings = QuotaSettingsFactory.removeTableSpaceLimit(table)
278 elsif args.key?(NAMESPACE)
280 raise(ArgumentError, 'Only one of TABLE or NAMESPACE can be specified.')
282 settings = QuotaSettingsFactory.removeNamespaceSpaceLimit(args.delete(NAMESPACE))
284 raise(ArgumentError, 'One of TABLE or NAMESPACE must be specified.')
286 @admin.setQuota(settings)
289 def get_master_table_sizes
290 @admin.getSpaceQuotaTableSizes
293 def get_quota_snapshots(regionserver = nil)
294 # Ask a regionserver if we were given one
295 return get_rs_quota_snapshots(regionserver) if regionserver
296 # Otherwise, read from the quota table
297 get_quota_snapshots_from_table
300 def get_quota_snapshots_from_table
301 # Reads the snapshots from the hbase:quota table
302 QuotaTableUtil.getSnapshots(@admin.getConnection)
305 def get_rs_quota_snapshots(rs)
306 # Reads the snapshots from a specific regionserver
307 @admin.getRegionServerSpaceQuotaSnapshots(ServerName.valueOf(rs))
310 def set_global_bypass(bypass, args)
311 raise(ArgumentError, 'Arguments should be a Hash') unless args.is_a?(Hash)
314 user = args.delete(USER)
315 raise(ArgumentError, 'Unexpected arguments: ' + args.inspect) unless args.empty?
316 settings = QuotaSettingsFactory.bypassGlobals(user, bypass)
318 raise 'Expected USER'
320 @admin.setQuota(settings)
323 def list_quotas(args = {})
324 raise(ArgumentError, 'Arguments should be a Hash') unless args.is_a?(Hash)
326 limit = args.delete('LIMIT') || -1
329 filter = QuotaFilter.new
330 filter.setUserFilter(args.delete(USER)) if args.key?(USER)
331 filter.setTableFilter(args.delete(TABLE)) if args.key?(TABLE)
332 filter.setNamespaceFilter(args.delete(NAMESPACE)) if args.key?(NAMESPACE)
333 raise(ArgumentError, 'Unexpected arguments: ' + args.inspect) unless args.empty?
336 quotas = @admin.getQuota(filter)
337 iter = quotas.iterator
341 break if limit > 0 && count >= limit
345 USER => settings.getUserName,
346 TABLE => settings.getTableName,
347 NAMESPACE => settings.getNamespace,
348 REGIONSERVER => settings.getRegionServer
349 }.delete_if { |_k, v| v.nil? }.map { |k, v| k.to_s + ' => ' + v.to_s } * ', '
351 yield owner, settings.to_s
358 def list_snapshot_sizes
359 QuotaTableUtil.getObservedSnapshotSizes(@admin.getConnection)
362 def switch_rpc_throttle(enabled)
363 @admin.switchRpcThrottle(java.lang.Boolean.valueOf(enabled))
366 def switch_exceed_throttle_quota(enabled)
367 @admin.exceedThrottleQuotaSwitch(java.lang.Boolean.valueOf(enabled))
370 def _parse_size(str_limit)
371 str_limit = str_limit.downcase
372 match = /^(\d+)([bkmgtp%]?)$/.match(str_limit)
377 return _size_from_str(match[1].to_i, match[2])
380 raise(ArgumentError, 'Invalid size limit syntax')
384 # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
385 def _parse_limit(str_limit, type_cls, type)
386 str_limit = str_limit.downcase
387 match = /^(\d+)(req|cu|[bkmgtp])\/(sec|min|hour|day)$/.match(str_limit)
389 limit = match[1].to_i
391 type = type_cls.valueOf(type + '_NUMBER')
392 elsif match[2] == 'cu'
393 type = type_cls.valueOf(type + '_CAPACITY_UNIT')
395 limit = _size_from_str(limit, match[2])
396 type = type_cls.valueOf(type + '_SIZE')
400 raise(ArgumentError, 'Invalid throttle limit, must be greater than 0')
404 when 'sec' then time_unit = TimeUnit::SECONDS
405 when 'min' then time_unit = TimeUnit::MINUTES
406 when 'hour' then time_unit = TimeUnit::HOURS
407 when 'day' then time_unit = TimeUnit::DAYS
410 return type, limit, time_unit
412 raise(ArgumentError, 'Invalid throttle limit syntax')
415 # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
417 def _size_from_str(value, suffix)
419 when 'k' then value <<= 10
420 when 'm' then value <<= 20
421 when 'g' then value <<= 30
422 when 't' then value <<= 40
423 when 'p' then value <<= 50
428 def _parse_scope(scope_str)
429 scope_str = scope_str.upcase
430 return QuotaScope.valueOf(scope_str) if [CLUSTER, MACHINE].include?(scope_str)
431 unless raise(ArgumentError, 'Invalid throttle scope, must be either CLUSTER or MACHINE')
435 # rubocop:enable Metrics/ClassLength