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
34 GLOBAL_BYPASS = 'GLOBAL_BYPASS'.freeze
35 THROTTLE_TYPE = 'THROTTLE_TYPE'.freeze
36 THROTTLE = 'THROTTLE'.freeze
37 REQUEST = 'REQUEST'.freeze
38 WRITE = 'WRITE'.freeze
40 SCOPE = 'SCOPE'.freeze
41 CLUSTER = 'CLUSTER'.freeze
42 MACHINE = 'MACHINE'.freeze
43 # Space quota constants
44 SPACE = 'SPACE'.freeze
45 NO_INSERTS = 'NO_INSERTS'.freeze
46 NO_WRITES = 'NO_WRITES'.freeze
47 NO_WRITES_COMPACTIONS = 'NO_WRITES_COMPACTIONS'.freeze
48 DISABLE = 'DISABLE'.freeze
52 # rubocop:disable Metrics/ClassLength
63 raise(ArgumentError, 'Arguments should be a Hash') unless args.is_a?(Hash)
64 type = args.fetch(THROTTLE_TYPE, REQUEST)
65 args.delete(THROTTLE_TYPE)
66 type, limit, time_unit = _parse_limit(args.delete(LIMIT), ThrottleType, type)
67 scope = _parse_scope(args.fetch(SCOPE, MACHINE))
70 user = args.delete(USER)
72 table = TableName.valueOf(args.delete(TABLE))
73 raise(ArgumentError, 'Unexpected arguments: ' + args.inspect) unless args.empty?
74 settings = QuotaSettingsFactory.throttleUser(user, table, type, limit, time_unit, scope)
75 elsif args.key?(NAMESPACE)
76 namespace = args.delete(NAMESPACE)
77 raise(ArgumentError, 'Unexpected arguments: ' + args.inspect) unless args.empty?
78 settings = QuotaSettingsFactory.throttleUser(user, namespace, type, limit, time_unit, scope)
80 raise(ArgumentError, 'Unexpected arguments: ' + args.inspect) unless args.empty?
81 settings = QuotaSettingsFactory.throttleUser(user, type, limit, time_unit, scope)
83 elsif args.key?(TABLE)
84 table = TableName.valueOf(args.delete(TABLE))
85 raise(ArgumentError, 'Unexpected arguments: ' + args.inspect) unless args.empty?
86 settings = QuotaSettingsFactory.throttleTable(table, type, limit, time_unit, scope)
87 elsif args.key?(NAMESPACE)
88 namespace = args.delete(NAMESPACE)
89 raise(ArgumentError, 'Unexpected arguments: ' + args.inspect) unless args.empty?
90 settings = QuotaSettingsFactory.throttleNamespace(namespace, type, limit, time_unit, scope)
91 elsif args.key?(REGIONSERVER)
92 # TODO: Setting specified region server quota isn't supported currently and using 'all' for all RS
93 if scope == QuotaScope.valueOf(CLUSTER)
94 raise(ArgumentError, 'Invalid region server throttle scope, must be MACHINE')
96 settings = QuotaSettingsFactory.throttleRegionServer('all', type, limit, time_unit)
98 raise 'One of USER, TABLE, NAMESPACE or REGIONSERVER must be specified'
100 @admin.setQuota(settings)
104 raise(ArgumentError, 'Arguments should be a Hash') unless args.is_a?(Hash)
106 user = args.delete(USER)
108 table = TableName.valueOf(args.delete(TABLE))
109 raise(ArgumentError, 'Unexpected arguments: ' + args.inspect) unless args.empty?
110 settings = QuotaSettingsFactory.unthrottleUser(user, table)
111 elsif args.key?(NAMESPACE)
112 namespace = args.delete(NAMESPACE)
113 raise(ArgumentError, 'Unexpected arguments: ' + args.inspect) unless args.empty?
114 settings = QuotaSettingsFactory.unthrottleUser(user, namespace)
116 raise(ArgumentError, 'Unexpected arguments: ' + args.inspect) unless args.empty?
117 settings = QuotaSettingsFactory.unthrottleUser(user)
119 elsif args.key?(TABLE)
120 table = TableName.valueOf(args.delete(TABLE))
121 raise(ArgumentError, 'Unexpected arguments: ' + args.inspect) unless args.empty?
122 settings = QuotaSettingsFactory.unthrottleTable(table)
123 elsif args.key?(NAMESPACE)
124 namespace = args.delete(NAMESPACE)
125 raise(ArgumentError, 'Unexpected arguments: ' + args.inspect) unless args.empty?
126 settings = QuotaSettingsFactory.unthrottleNamespace(namespace)
127 elsif args.key?(REGIONSERVER)
128 regionServer = args.delete(REGIONSERVER)
129 raise(ArgumentError, 'Unexpected arguments: ' + args.inspect) unless args.empty?
130 # TODO: Setting specified region server quota isn't supported currently and using 'all' for all RS
131 settings = QuotaSettingsFactory.unthrottleRegionServer('all')
133 raise 'One of USER, TABLE, NAMESPACE or REGIONSERVER must be specified'
135 @admin.setQuota(settings)
138 # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
139 # rubocop:disable Metrics/MethodLength, Metrics/PerceivedComplexity
140 def limit_space(args)
141 raise(ArgumentError, 'Argument should be a Hash') unless !args.nil? && args.is_a?(Hash)
142 # Let the user provide a raw number
143 limit = if args[LIMIT].is_a?(Numeric)
146 # Parse a string a 1K, 2G, etc.
147 _parse_size(args[LIMIT])
150 raise(ArgumentError, 'Invalid space limit, must be greater than 0')
153 # Extract the policy, failing if something bogus was provided
154 policy = SpaceViolationPolicy.valueOf(args[POLICY])
155 # Create a table or namespace quota
157 if args.key?(NAMESPACE)
158 raise(ArgumentError, 'Only one of TABLE or NAMESPACE can be specified.')
160 settings = QuotaSettingsFactory.limitTableSpace(TableName.valueOf(args.delete(TABLE)), limit, policy)
161 elsif args.key?(NAMESPACE)
163 raise(ArgumentError, 'Only one of TABLE or NAMESPACE can be specified.')
165 settings = QuotaSettingsFactory.limitNamespaceSpace(args.delete(NAMESPACE), limit, policy)
167 raise(ArgumentError, 'One of TABLE or NAMESPACE must be specified.')
170 @admin.setQuota(settings)
172 # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity
173 # rubocop:enable Metrics/MethodLength, Metrics/PerceivedComplexity
175 def remove_space_limit(args)
176 raise(ArgumentError, 'Argument should be a Hash') unless !args.nil? && args.is_a?(Hash)
178 if args.key?(NAMESPACE)
179 raise(ArgumentError, 'Only one of TABLE or NAMESPACE can be specified.')
181 table = TableName.valueOf(args.delete(TABLE))
182 settings = QuotaSettingsFactory.removeTableSpaceLimit(table)
183 elsif args.key?(NAMESPACE)
185 raise(ArgumentError, 'Only one of TABLE or NAMESPACE can be specified.')
187 settings = QuotaSettingsFactory.removeNamespaceSpaceLimit(args.delete(NAMESPACE))
189 raise(ArgumentError, 'One of TABLE or NAMESPACE must be specified.')
191 @admin.setQuota(settings)
194 def get_master_table_sizes
195 @admin.getSpaceQuotaTableSizes
198 def get_quota_snapshots(regionserver = nil)
199 # Ask a regionserver if we were given one
200 return get_rs_quota_snapshots(regionserver) if regionserver
201 # Otherwise, read from the quota table
202 get_quota_snapshots_from_table
205 def get_quota_snapshots_from_table
206 # Reads the snapshots from the hbase:quota table
207 QuotaTableUtil.getSnapshots(@admin.getConnection)
210 def get_rs_quota_snapshots(rs)
211 # Reads the snapshots from a specific regionserver
212 @admin.getRegionServerSpaceQuotaSnapshots(ServerName.valueOf(rs))
215 def set_global_bypass(bypass, args)
216 raise(ArgumentError, 'Arguments should be a Hash') unless args.is_a?(Hash)
219 user = args.delete(USER)
220 raise(ArgumentError, 'Unexpected arguments: ' + args.inspect) unless args.empty?
221 settings = QuotaSettingsFactory.bypassGlobals(user, bypass)
223 raise 'Expected USER'
225 @admin.setQuota(settings)
228 def list_quotas(args = {})
229 raise(ArgumentError, 'Arguments should be a Hash') unless args.is_a?(Hash)
231 limit = args.delete('LIMIT') || -1
234 filter = QuotaFilter.new
235 filter.setUserFilter(args.delete(USER)) if args.key?(USER)
236 filter.setTableFilter(args.delete(TABLE)) if args.key?(TABLE)
237 filter.setNamespaceFilter(args.delete(NAMESPACE)) if args.key?(NAMESPACE)
238 raise(ArgumentError, 'Unexpected arguments: ' + args.inspect) unless args.empty?
241 quotas = @admin.getQuota(filter)
242 iter = quotas.iterator
246 break if limit > 0 && count >= limit
250 USER => settings.getUserName,
251 TABLE => settings.getTableName,
252 NAMESPACE => settings.getNamespace,
253 REGIONSERVER => settings.getRegionServer
254 }.delete_if { |_k, v| v.nil? }.map { |k, v| k.to_s + ' => ' + v.to_s } * ', '
256 yield owner, settings.to_s
263 def list_snapshot_sizes
264 QuotaTableUtil.getObservedSnapshotSizes(@admin.getConnection)
267 def switch_rpc_throttle(enabled)
268 @admin.switchRpcThrottle(java.lang.Boolean.valueOf(enabled))
271 def switch_exceed_throttle_quota(enabled)
272 @admin.exceedThrottleQuotaSwitch(java.lang.Boolean.valueOf(enabled))
275 def _parse_size(str_limit)
276 str_limit = str_limit.downcase
277 match = /^(\d+)([bkmgtp%]?)$/.match(str_limit)
282 return _size_from_str(match[1].to_i, match[2])
285 raise(ArgumentError, 'Invalid size limit syntax')
289 # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
290 def _parse_limit(str_limit, type_cls, type)
291 str_limit = str_limit.downcase
292 match = /^(\d+)(req|cu|[bkmgtp])\/(sec|min|hour|day)$/.match(str_limit)
294 limit = match[1].to_i
296 type = type_cls.valueOf(type + '_NUMBER')
297 elsif match[2] == 'cu'
298 type = type_cls.valueOf(type + '_CAPACITY_UNIT')
300 limit = _size_from_str(limit, match[2])
301 type = type_cls.valueOf(type + '_SIZE')
305 raise(ArgumentError, 'Invalid throttle limit, must be greater than 0')
309 when 'sec' then time_unit = TimeUnit::SECONDS
310 when 'min' then time_unit = TimeUnit::MINUTES
311 when 'hour' then time_unit = TimeUnit::HOURS
312 when 'day' then time_unit = TimeUnit::DAYS
315 return type, limit, time_unit
317 raise(ArgumentError, 'Invalid throttle limit syntax')
320 # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
322 def _size_from_str(value, suffix)
324 when 'k' then value <<= 10
325 when 'm' then value <<= 20
326 when 'g' then value <<= 30
327 when 't' then value <<= 40
328 when 'p' then value <<= 50
333 def _parse_scope(scope_str)
334 scope_str = scope_str.upcase
335 return QuotaScope.valueOf(scope_str) if [CLUSTER, MACHINE].include?(scope_str)
336 unless raise(ArgumentError, 'Invalid throttle scope, must be either CLUSTER or MACHINE')
340 # rubocop:enable Metrics/ClassLength