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 public void onRegionMerged(HRegionInfo hri) throws IOException {
337 if (enabled) {
338 namespaceQuotaManager.updateQuotaForRegionMerge(hri);
339 }
340 }
341
342 public void onRegionSplit(HRegionInfo hri) throws IOException {
343 if (enabled) {
344 namespaceQuotaManager.checkQuotaToSplitRegion(hri);
345 }
346 }
347
348
349
350
351
352
353 public void removeTableFromNamespaceQuota(TableName tName) throws IOException {
354 if (enabled) {
355 namespaceQuotaManager.removeFromNamespaceUsage(tName);
356 }
357 }
358
359 public NamespaceAuditor getNamespaceQuotaManager() {
360 return this.namespaceQuotaManager;
361 }
362
363 private static interface SetQuotaOperations {
364 Quotas fetch() throws IOException;
365
366 void delete() throws IOException;
367
368 void update(final Quotas quotas) throws IOException;
369
370 void preApply(final Quotas quotas) throws IOException;
371
372 void postApply(final Quotas quotas) throws IOException;
373 }
374
375
376
377
378
379 private void applyThrottle(final Quotas.Builder quotas, final ThrottleRequest req)
380 throws IOException {
381 Throttle.Builder throttle;
382
383 if (req.hasType() && (req.hasTimedQuota() || quotas.hasThrottle())) {
384
385 if (req.hasTimedQuota()) {
386 validateTimedQuota(req.getTimedQuota());
387 }
388
389
390 throttle = quotas.hasThrottle() ? quotas.getThrottle().toBuilder() : Throttle.newBuilder();
391
392 switch (req.getType()) {
393 case REQUEST_NUMBER:
394 if (req.hasTimedQuota()) {
395 throttle.setReqNum(req.getTimedQuota());
396 } else {
397 throttle.clearReqNum();
398 }
399 break;
400 case REQUEST_SIZE:
401 if (req.hasTimedQuota()) {
402 throttle.setReqSize(req.getTimedQuota());
403 } else {
404 throttle.clearReqSize();
405 }
406 break;
407 case WRITE_NUMBER:
408 if (req.hasTimedQuota()) {
409 throttle.setWriteNum(req.getTimedQuota());
410 } else {
411 throttle.clearWriteNum();
412 }
413 break;
414 case WRITE_SIZE:
415 if (req.hasTimedQuota()) {
416 throttle.setWriteSize(req.getTimedQuota());
417 } else {
418 throttle.clearWriteSize();
419 }
420 break;
421 case READ_NUMBER:
422 if (req.hasTimedQuota()) {
423 throttle.setReadNum(req.getTimedQuota());
424 } else {
425 throttle.clearReqNum();
426 }
427 break;
428 case READ_SIZE:
429 if (req.hasTimedQuota()) {
430 throttle.setReadSize(req.getTimedQuota());
431 } else {
432 throttle.clearReadSize();
433 }
434 break;
435 default:
436 throw new RuntimeException("Invalid throttle type: " + req.getType());
437 }
438 quotas.setThrottle(throttle.build());
439 } else {
440 quotas.clearThrottle();
441 }
442 }
443
444 private void applyBypassGlobals(final Quotas.Builder quotas, boolean bypassGlobals) {
445 if (bypassGlobals) {
446 quotas.setBypassGlobals(bypassGlobals);
447 } else {
448 quotas.clearBypassGlobals();
449 }
450 }
451
452 private void validateTimedQuota(final TimedQuota timedQuota) throws IOException {
453 if (timedQuota.getSoftLimit() < 1) {
454 throw new DoNotRetryIOException(new UnsupportedOperationException(
455 "The throttle limit must be greater then 0, got " + timedQuota.getSoftLimit()));
456 }
457 }
458
459
460
461
462
463 private void checkQuotaSupport() throws IOException {
464 if (!enabled) {
465 throw new DoNotRetryIOException(new UnsupportedOperationException("quota support disabled"));
466 }
467 }
468
469 private void createQuotaTable() throws IOException {
470 HRegionInfo[] newRegions = new HRegionInfo[] { new HRegionInfo(QuotaUtil.QUOTA_TABLE_NAME) };
471
472 if (masterServices.isMasterProcedureExecutorEnabled()) {
473 masterServices.getMasterProcedureExecutor()
474 .submitProcedure(new CreateTableProcedure(
475 masterServices.getMasterProcedureExecutor().getEnvironment(),
476 QuotaUtil.QUOTA_TABLE_DESC,
477 newRegions));
478 } else {
479 masterServices.getExecutorService().submit(
480 new CreateTableHandler(masterServices, masterServices.getMasterFileSystem(),
481 QuotaUtil.QUOTA_TABLE_DESC, masterServices.getConfiguration(), newRegions,
482 masterServices).prepare());
483 }
484 }
485
486 private static class NamedLock<T> {
487 private HashSet<T> locks = new HashSet<T>();
488
489 public void lock(final T name) throws InterruptedException {
490 synchronized (locks) {
491 while (locks.contains(name)) {
492 locks.wait();
493 }
494 locks.add(name);
495 }
496 }
497
498 public void unlock(final T name) {
499 synchronized (locks) {
500 locks.remove(name);
501 locks.notifyAll();
502 }
503 }
504 }
505
506 @Override
507 public void onRegionSplitReverted(HRegionInfo hri) throws IOException {
508 if (enabled) {
509 this.namespaceQuotaManager.removeRegionFromNamespaceUsage(hri);
510 }
511 }
512 }