1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.hadoop.hbase.quotas;
20
21 import java.io.IOException;
22 import java.util.HashSet;
23
24 import org.apache.commons.logging.Log;
25 import org.apache.commons.logging.LogFactory;
26 import org.apache.hadoop.hbase.DoNotRetryIOException;
27 import org.apache.hadoop.hbase.HRegionInfo;
28 import org.apache.hadoop.hbase.MetaTableAccessor;
29 import org.apache.hadoop.hbase.NamespaceDescriptor;
30 import org.apache.hadoop.hbase.TableName;
31 import org.apache.hadoop.hbase.classification.InterfaceAudience;
32 import org.apache.hadoop.hbase.classification.InterfaceStability;
33 import org.apache.hadoop.hbase.master.MasterServices;
34 import org.apache.hadoop.hbase.master.procedure.CreateTableProcedure;
35 import org.apache.hadoop.hbase.namespace.NamespaceAuditor;
36 import org.apache.hadoop.hbase.protobuf.ProtobufUtil;
37 import org.apache.hadoop.hbase.protobuf.generated.MasterProtos.SetQuotaRequest;
38 import org.apache.hadoop.hbase.protobuf.generated.MasterProtos.SetQuotaResponse;
39 import org.apache.hadoop.hbase.protobuf.generated.QuotaProtos.Quotas;
40 import org.apache.hadoop.hbase.protobuf.generated.QuotaProtos.Throttle;
41 import org.apache.hadoop.hbase.protobuf.generated.QuotaProtos.ThrottleRequest;
42 import org.apache.hadoop.hbase.protobuf.generated.QuotaProtos.TimedQuota;
43
44
45
46
47
48
49
50
51
52 @InterfaceAudience.Private
53 @InterfaceStability.Evolving
54 public class MasterQuotaManager implements RegionStateListener {
55 private static final Log LOG = LogFactory.getLog(MasterQuotaManager.class);
56
57 private final MasterServices masterServices;
58 private NamedLock<String> namespaceLocks;
59 private NamedLock<TableName> tableLocks;
60 private NamedLock<String> userLocks;
61 private boolean enabled = false;
62 private NamespaceAuditor namespaceQuotaManager;
63
64 public MasterQuotaManager(final MasterServices masterServices) {
65 this.masterServices = masterServices;
66 }
67
68 public void start() throws IOException {
69
70 if (!QuotaUtil.isQuotaEnabled(masterServices.getConfiguration())) {
71 LOG.info("Quota support disabled");
72 return;
73 }
74
75
76 if (!MetaTableAccessor.tableExists(masterServices.getConnection(),
77 QuotaUtil.QUOTA_TABLE_NAME)) {
78 LOG.info("Quota table not found. Creating...");
79 createQuotaTable();
80 }
81
82 LOG.info("Initializing quota support");
83 namespaceLocks = new NamedLock<String>();
84 tableLocks = new NamedLock<TableName>();
85 userLocks = new NamedLock<String>();
86
87 namespaceQuotaManager = new NamespaceAuditor(masterServices);
88 namespaceQuotaManager.start();
89 enabled = true;
90 }
91
92 public void stop() {
93 }
94
95 public boolean isQuotaEnabled() {
96 return enabled && namespaceQuotaManager.isInitialized();
97 }
98
99
100
101
102 public SetQuotaResponse setQuota(final SetQuotaRequest req)
103 throws IOException, InterruptedException {
104 checkQuotaSupport();
105
106 if (req.hasUserName()) {
107 userLocks.lock(req.getUserName());
108 try {
109 if (req.hasTableName()) {
110 setUserQuota(req.getUserName(), ProtobufUtil.toTableName(req.getTableName()), req);
111 } else if (req.hasNamespace()) {
112 setUserQuota(req.getUserName(), req.getNamespace(), req);
113 } else {
114 setUserQuota(req.getUserName(), req);
115 }
116 } finally {
117 userLocks.unlock(req.getUserName());
118 }
119 } else if (req.hasTableName()) {
120 TableName table = ProtobufUtil.toTableName(req.getTableName());
121 tableLocks.lock(table);
122 try {
123 setTableQuota(table, req);
124 } finally {
125 tableLocks.unlock(table);
126 }
127 } else if (req.hasNamespace()) {
128 namespaceLocks.lock(req.getNamespace());
129 try {
130 setNamespaceQuota(req.getNamespace(), req);
131 } finally {
132 namespaceLocks.unlock(req.getNamespace());
133 }
134 } else {
135 throw new DoNotRetryIOException(
136 new UnsupportedOperationException("a user, a table or a namespace must be specified"));
137 }
138 return SetQuotaResponse.newBuilder().build();
139 }
140
141 public void setUserQuota(final String userName, final SetQuotaRequest req)
142 throws IOException, InterruptedException {
143 setQuota(req, new SetQuotaOperations() {
144 @Override
145 public Quotas fetch() throws IOException {
146 return QuotaUtil.getUserQuota(masterServices.getConnection(), userName);
147 }
148 @Override
149 public void update(final Quotas quotas) throws IOException {
150 QuotaUtil.addUserQuota(masterServices.getConnection(), userName, quotas);
151 }
152 @Override
153 public void delete() throws IOException {
154 QuotaUtil.deleteUserQuota(masterServices.getConnection(), userName);
155 }
156 @Override
157 public void preApply(final Quotas quotas) throws IOException {
158 masterServices.getMasterCoprocessorHost().preSetUserQuota(userName, quotas);
159 }
160 @Override
161 public void postApply(final Quotas quotas) throws IOException {
162 masterServices.getMasterCoprocessorHost().postSetUserQuota(userName, quotas);
163 }
164 });
165 }
166
167 public void setUserQuota(final String userName, final TableName table,
168 final SetQuotaRequest req) throws IOException, InterruptedException {
169 setQuota(req, new SetQuotaOperations() {
170 @Override
171 public Quotas fetch() throws IOException {
172 return QuotaUtil.getUserQuota(masterServices.getConnection(), userName, table);
173 }
174 @Override
175 public void update(final Quotas quotas) throws IOException {
176 QuotaUtil.addUserQuota(masterServices.getConnection(), userName, table, quotas);
177 }
178 @Override
179 public void delete() throws IOException {
180 QuotaUtil.deleteUserQuota(masterServices.getConnection(), userName, table);
181 }
182 @Override
183 public void preApply(final Quotas quotas) throws IOException {
184 masterServices.getMasterCoprocessorHost().preSetUserQuota(userName, table, quotas);
185 }
186 @Override
187 public void postApply(final Quotas quotas) throws IOException {
188 masterServices.getMasterCoprocessorHost().postSetUserQuota(userName, table, quotas);
189 }
190 });
191 }
192
193 public void setUserQuota(final String userName, final String namespace,
194 final SetQuotaRequest req) throws IOException, InterruptedException {
195 setQuota(req, new SetQuotaOperations() {
196 @Override
197 public Quotas fetch() throws IOException {
198 return QuotaUtil.getUserQuota(masterServices.getConnection(), userName, namespace);
199 }
200 @Override
201 public void update(final Quotas quotas) throws IOException {
202 QuotaUtil.addUserQuota(masterServices.getConnection(), userName, namespace, quotas);
203 }
204 @Override
205 public void delete() throws IOException {
206 QuotaUtil.deleteUserQuota(masterServices.getConnection(), userName, namespace);
207 }
208 @Override
209 public void preApply(final Quotas quotas) throws IOException {
210 masterServices.getMasterCoprocessorHost().preSetUserQuota(userName, namespace, quotas);
211 }
212 @Override
213 public void postApply(final Quotas quotas) throws IOException {
214 masterServices.getMasterCoprocessorHost().postSetUserQuota(userName, namespace, quotas);
215 }
216 });
217 }
218
219 public void setTableQuota(final TableName table, final SetQuotaRequest req)
220 throws IOException, InterruptedException {
221 setQuota(req, new SetQuotaOperations() {
222 @Override
223 public Quotas fetch() throws IOException {
224 return QuotaUtil.getTableQuota(masterServices.getConnection(), table);
225 }
226 @Override
227 public void update(final Quotas quotas) throws IOException {
228 QuotaUtil.addTableQuota(masterServices.getConnection(), table, quotas);
229 }
230 @Override
231 public void delete() throws IOException {
232 QuotaUtil.deleteTableQuota(masterServices.getConnection(), table);
233 }
234 @Override
235 public void preApply(final Quotas quotas) throws IOException {
236 masterServices.getMasterCoprocessorHost().preSetTableQuota(table, quotas);
237 }
238 @Override
239 public void postApply(final Quotas quotas) throws IOException {
240 masterServices.getMasterCoprocessorHost().postSetTableQuota(table, quotas);
241 }
242 });
243 }
244
245 public void setNamespaceQuota(final String namespace, final SetQuotaRequest req)
246 throws IOException, InterruptedException {
247 setQuota(req, new SetQuotaOperations() {
248 @Override
249 public Quotas fetch() throws IOException {
250 return QuotaUtil.getNamespaceQuota(masterServices.getConnection(), namespace);
251 }
252 @Override
253 public void update(final Quotas quotas) throws IOException {
254 QuotaUtil.addNamespaceQuota(masterServices.getConnection(), namespace, quotas);
255 }
256 @Override
257 public void delete() throws IOException {
258 QuotaUtil.deleteNamespaceQuota(masterServices.getConnection(), namespace);
259 }
260 @Override
261 public void preApply(final Quotas quotas) throws IOException {
262 masterServices.getMasterCoprocessorHost().preSetNamespaceQuota(namespace, quotas);
263 }
264 @Override
265 public void postApply(final Quotas quotas) throws IOException {
266 masterServices.getMasterCoprocessorHost().postSetNamespaceQuota(namespace, quotas);
267 }
268 });
269 }
270
271 public void setNamespaceQuota(NamespaceDescriptor desc) throws IOException {
272 if (enabled) {
273 this.namespaceQuotaManager.addNamespace(desc);
274 }
275 }
276
277 public void removeNamespaceQuota(String namespace) throws IOException {
278 if (enabled) {
279 this.namespaceQuotaManager.deleteNamespace(namespace);
280 }
281 }
282
283 private void setQuota(final SetQuotaRequest req, final SetQuotaOperations quotaOps)
284 throws IOException, InterruptedException {
285 if (req.hasRemoveAll() && req.getRemoveAll() == true) {
286 quotaOps.preApply(null);
287 quotaOps.delete();
288 quotaOps.postApply(null);
289 return;
290 }
291
292
293 Quotas quotas = quotaOps.fetch();
294 quotaOps.preApply(quotas);
295
296 Quotas.Builder builder = (quotas != null) ? quotas.toBuilder() : Quotas.newBuilder();
297 if (req.hasThrottle()) applyThrottle(builder, req.getThrottle());
298 if (req.hasBypassGlobals()) applyBypassGlobals(builder, req.getBypassGlobals());
299
300
301 quotas = builder.build();
302 if (QuotaUtil.isEmptyQuota(quotas)) {
303 quotaOps.delete();
304 } else {
305 quotaOps.update(quotas);
306 }
307 quotaOps.postApply(quotas);
308 }
309
310 public void checkNamespaceTableAndRegionQuota(TableName tName, int regions) throws IOException {
311 if (enabled) {
312 namespaceQuotaManager.checkQuotaToCreateTable(tName, regions);
313 }
314 }
315
316 public void checkAndUpdateNamespaceRegionQuota(TableName tName, int regions) throws IOException {
317 if (enabled) {
318 namespaceQuotaManager.checkQuotaToUpdateRegion(tName, regions);
319 }
320 }
321
322 public void onRegionMerged(HRegionInfo hri) throws IOException {
323 if (enabled) {
324 namespaceQuotaManager.updateQuotaForRegionMerge(hri);
325 }
326 }
327
328 public void onRegionSplit(HRegionInfo hri) throws IOException {
329 if (enabled) {
330 namespaceQuotaManager.checkQuotaToSplitRegion(hri);
331 }
332 }
333
334
335
336
337
338
339
340 public void removeTableFromNamespaceQuota(TableName tName) throws IOException {
341 if (enabled) {
342 namespaceQuotaManager.removeFromNamespaceUsage(tName);
343 }
344 }
345
346 public NamespaceAuditor getNamespaceQuotaManager() {
347 return this.namespaceQuotaManager;
348 }
349
350 private static interface SetQuotaOperations {
351 Quotas fetch() throws IOException;
352 void delete() throws IOException;
353 void update(final Quotas quotas) throws IOException;
354 void preApply(final Quotas quotas) throws IOException;
355 void postApply(final Quotas quotas) throws IOException;
356 }
357
358
359
360
361 private void applyThrottle(final Quotas.Builder quotas, final ThrottleRequest req)
362 throws IOException {
363 Throttle.Builder throttle;
364
365 if (req.hasType() && (req.hasTimedQuota() || quotas.hasThrottle())) {
366
367 if (req.hasTimedQuota()) validateTimedQuota(req.getTimedQuota());
368
369
370 throttle = quotas.hasThrottle() ? quotas.getThrottle().toBuilder() : Throttle.newBuilder();
371
372 switch (req.getType()) {
373 case REQUEST_NUMBER:
374 if (req.hasTimedQuota()) {
375 throttle.setReqNum(req.getTimedQuota());
376 } else {
377 throttle.clearReqNum();
378 }
379 break;
380 case REQUEST_SIZE:
381 if (req.hasTimedQuota()) {
382 throttle.setReqSize(req.getTimedQuota());
383 } else {
384 throttle.clearReqSize();
385 }
386 break;
387 case WRITE_NUMBER:
388 if (req.hasTimedQuota()) {
389 throttle.setWriteNum(req.getTimedQuota());
390 } else {
391 throttle.clearWriteNum();
392 }
393 break;
394 case WRITE_SIZE:
395 if (req.hasTimedQuota()) {
396 throttle.setWriteSize(req.getTimedQuota());
397 } else {
398 throttle.clearWriteSize();
399 }
400 break;
401 case READ_NUMBER:
402 if (req.hasTimedQuota()) {
403 throttle.setReadNum(req.getTimedQuota());
404 } else {
405 throttle.clearReqNum();
406 }
407 break;
408 case READ_SIZE:
409 if (req.hasTimedQuota()) {
410 throttle.setReadSize(req.getTimedQuota());
411 } else {
412 throttle.clearReadSize();
413 }
414 break;
415 }
416 quotas.setThrottle(throttle.build());
417 } else {
418 quotas.clearThrottle();
419 }
420 }
421
422 private void applyBypassGlobals(final Quotas.Builder quotas, boolean bypassGlobals) {
423 if (bypassGlobals) {
424 quotas.setBypassGlobals(bypassGlobals);
425 } else {
426 quotas.clearBypassGlobals();
427 }
428 }
429
430 private void validateTimedQuota(final TimedQuota timedQuota) throws IOException {
431 if (timedQuota.getSoftLimit() < 1) {
432 throw new DoNotRetryIOException(new UnsupportedOperationException(
433 "The throttle limit must be greater then 0, got " + timedQuota.getSoftLimit()));
434 }
435 }
436
437
438
439
440
441 private void checkQuotaSupport() throws IOException {
442 if (!enabled) {
443 throw new DoNotRetryIOException(
444 new UnsupportedOperationException("quota support disabled"));
445 }
446 }
447
448 private void createQuotaTable() throws IOException {
449 HRegionInfo newRegions[] = new HRegionInfo[] {
450 new HRegionInfo(QuotaUtil.QUOTA_TABLE_NAME)
451 };
452
453 masterServices.getMasterProcedureExecutor()
454 .submitProcedure(new CreateTableProcedure(
455 masterServices.getMasterProcedureExecutor().getEnvironment(),
456 QuotaUtil.QUOTA_TABLE_DESC,
457 newRegions));
458 }
459
460 private static class NamedLock<T> {
461 private HashSet<T> locks = new HashSet<T>();
462
463 public void lock(final T name) throws InterruptedException {
464 synchronized (locks) {
465 while (locks.contains(name)) {
466 locks.wait();
467 }
468 locks.add(name);
469 }
470 }
471
472 public void unlock(final T name) {
473 synchronized (locks) {
474 locks.remove(name);
475 locks.notifyAll();
476 }
477 }
478 }
479
480 @Override
481 public void onRegionSplitReverted(HRegionInfo hri) throws IOException {
482 if (enabled) {
483 this.namespaceQuotaManager.removeRegionFromNamespaceUsage(hri);
484 }
485 }
486 }
487