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