View Javadoc

1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
3    * agreements. See the NOTICE file distributed with this work for additional information regarding
4    * copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 (the
5    * "License"); you may not use this file except in compliance with the License. You may obtain a
6    * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable
7    * law or agreed to in writing, software distributed under the License is distributed on an "AS IS"
8    * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
9    * for the specific language governing permissions and limitations under the License.
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.RegionStateListener;
24  import org.apache.hadoop.hbase.TableName;
25  import org.apache.hadoop.hbase.classification.InterfaceAudience;
26  import org.apache.hadoop.hbase.classification.InterfaceStability;
27  import org.apache.hadoop.hbase.master.MasterServices;
28  import org.apache.hadoop.hbase.namespace.NamespaceAuditor;
29  import org.apache.hadoop.hbase.protobuf.ProtobufUtil;
30  import org.apache.hadoop.hbase.protobuf.generated.MasterProtos.SetQuotaRequest;
31  import org.apache.hadoop.hbase.protobuf.generated.MasterProtos.SetQuotaResponse;
32  import org.apache.hadoop.hbase.protobuf.generated.QuotaProtos.Quotas;
33  import org.apache.hadoop.hbase.protobuf.generated.QuotaProtos.Throttle;
34  import org.apache.hadoop.hbase.protobuf.generated.QuotaProtos.ThrottleRequest;
35  import org.apache.hadoop.hbase.protobuf.generated.QuotaProtos.TimedQuota;
36  import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
37  
38  /**
39   * Master Quota Manager. It is responsible for initialize the quota table on the first-run and
40   * provide the admin operations to interact with the quota table. TODO: FUTURE: The master will be
41   * responsible to notify each RS of quota changes and it will do the "quota aggregation" when the
42   * QuotaScope is CLUSTER.
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 initialized = 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      // If the user doesn't want the quota support skip all the initializations.
62      if (!QuotaUtil.isQuotaEnabled(masterServices.getConfiguration())) {
63        LOG.info("Quota support disabled");
64        return;
65      }
66  
67      // Create the quota table if missing
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      initialized = true;
82    }
83  
84    public void stop() {
85    }
86  
87    public boolean isQuotaInitialized() {
88      return initialized && namespaceQuotaManager.isInitialized();
89    }
90  
91    /*
92     * ========================================================================== Admin operations to
93     * manage the quota table
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 (initialized) {
287       this.namespaceQuotaManager.addNamespace(desc);
288     }
289   }
290 
291   public void removeNamespaceQuota(String namespace) throws IOException {
292     if (initialized) {
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     // Apply quota changes
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     // Submit new changes
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 (initialized) {
326       namespaceQuotaManager.checkQuotaToCreateTable(tName, regions);
327     }
328   }
329   
330   public void checkAndUpdateNamespaceRegionQuota(TableName tName, int regions) throws IOException {
331     if (initialized) {
332       namespaceQuotaManager.checkQuotaToUpdateRegion(tName, regions);
333     }
334   }
335 
336   /**
337    * @return cached region count, or -1 if quota manager is disabled or table status not found
338   */
339   public int getRegionCountOfTable(TableName tName) throws IOException {
340     if (initialized) {
341       return namespaceQuotaManager.getRegionCountOfTable(tName);
342     }
343     return -1;
344   }
345 
346   @Override
347   public void onRegionMerged(HRegionInfo hri) throws IOException {
348     if (initialized) {
349       namespaceQuotaManager.updateQuotaForRegionMerge(hri);
350     }
351   }
352 
353   @Override
354   public void onRegionSplit(HRegionInfo hri) throws IOException {
355     if (initialized) {
356       namespaceQuotaManager.checkQuotaToSplitRegion(hri);
357     }
358   }
359 
360   /**
361    * Remove table from namespace quota.
362    * @param tName - The table name to update quota usage.
363    * @throws IOException Signals that an I/O exception has occurred.
364    */
365   public void removeTableFromNamespaceQuota(TableName tName) throws IOException {
366     if (initialized) {
367       namespaceQuotaManager.removeFromNamespaceUsage(tName);
368     }
369   }
370 
371   public NamespaceAuditor getNamespaceQuotaManager() {
372     return this.namespaceQuotaManager;
373   }
374 
375   private static interface SetQuotaOperations {
376     Quotas fetch() throws IOException;
377 
378     void delete() throws IOException;
379 
380     void update(final Quotas quotas) throws IOException;
381 
382     void preApply(final Quotas quotas) throws IOException;
383 
384     void postApply(final Quotas quotas) throws IOException;
385   }
386 
387   /*
388    * ========================================================================== Helpers to apply
389    * changes to the quotas
390    */
391   private void applyThrottle(final Quotas.Builder quotas, final ThrottleRequest req)
392       throws IOException {
393     Throttle.Builder throttle;
394 
395     if (req.hasType() && (req.hasTimedQuota() || quotas.hasThrottle())) {
396       // Validate timed quota if present
397       if (req.hasTimedQuota()) {
398         validateTimedQuota(req.getTimedQuota());
399       }
400 
401       // apply the new settings
402       throttle = quotas.hasThrottle() ? quotas.getThrottle().toBuilder() : Throttle.newBuilder();
403 
404       switch (req.getType()) {
405         case REQUEST_NUMBER:
406           if (req.hasTimedQuota()) {
407             throttle.setReqNum(req.getTimedQuota());
408           } else {
409             throttle.clearReqNum();
410           }
411           break;
412         case REQUEST_SIZE:
413           if (req.hasTimedQuota()) {
414             throttle.setReqSize(req.getTimedQuota());
415           } else {
416             throttle.clearReqSize();
417           }
418           break;
419         case WRITE_NUMBER:
420           if (req.hasTimedQuota()) {
421             throttle.setWriteNum(req.getTimedQuota());
422           } else {
423             throttle.clearWriteNum();
424           }
425           break;
426         case WRITE_SIZE:
427           if (req.hasTimedQuota()) {
428             throttle.setWriteSize(req.getTimedQuota());
429           } else {
430             throttle.clearWriteSize();
431           }
432           break;
433         case READ_NUMBER:
434           if (req.hasTimedQuota()) {
435             throttle.setReadNum(req.getTimedQuota());
436           } else {
437             throttle.clearReqNum();
438           }
439           break;
440         case READ_SIZE:
441           if (req.hasTimedQuota()) {
442             throttle.setReadSize(req.getTimedQuota());
443           } else {
444             throttle.clearReadSize();
445           }
446           break;
447         case REQUEST_CAPACITY_UNIT:
448           if (req.hasTimedQuota()) {
449             throttle.setReqCapacityUnit(req.getTimedQuota());
450           } else {
451             throttle.clearReqCapacityUnit();
452           }
453           break;
454         case READ_CAPACITY_UNIT:
455           if (req.hasTimedQuota()) {
456             throttle.setReadCapacityUnit(req.getTimedQuota());
457           } else {
458             throttle.clearReadCapacityUnit();
459           }
460           break;
461         case WRITE_CAPACITY_UNIT:
462           if (req.hasTimedQuota()) {
463             throttle.setWriteCapacityUnit(req.getTimedQuota());
464           } else {
465             throttle.clearWriteCapacityUnit();
466           }
467           break;
468         default:
469           throw new RuntimeException("Invalid throttle type: " + req.getType());
470       }
471       quotas.setThrottle(throttle.build());
472     } else {
473       quotas.clearThrottle();
474     }
475   }
476 
477   private void applyBypassGlobals(final Quotas.Builder quotas, boolean bypassGlobals) {
478     if (bypassGlobals) {
479       quotas.setBypassGlobals(bypassGlobals);
480     } else {
481       quotas.clearBypassGlobals();
482     }
483   }
484 
485   private void validateTimedQuota(final TimedQuota timedQuota) throws IOException {
486     if (timedQuota.getSoftLimit() < 1) {
487       throw new DoNotRetryIOException(new UnsupportedOperationException(
488           "The throttle limit must be greater then 0, got " + timedQuota.getSoftLimit()));
489     }
490   }
491 
492   /*
493    * ========================================================================== Helpers
494    */
495 
496   private void checkQuotaSupport() throws IOException {
497     if (!QuotaUtil.isQuotaEnabled(masterServices.getConfiguration())) {
498       throw new DoNotRetryIOException(new UnsupportedOperationException("quota support disabled"));
499     }
500     if (!initialized) {
501       long maxWaitTime = masterServices.getConfiguration().getLong(
502         "hbase.master.wait.for.quota.manager.init", 30000); // default is 30 seconds.
503       long startTime = EnvironmentEdgeManager.currentTime();
504       do {
505         try {
506           Thread.sleep(100);
507         } catch (InterruptedException e) {
508           LOG.warn("Interrupted while waiting for Quota Manager to be initialized.");
509           break;
510         }
511       } while (!initialized && (EnvironmentEdgeManager.currentTime() - startTime) < maxWaitTime);
512       if (!initialized) {
513         throw new IOException("Quota manager is uninitialized, please retry later.");
514       }
515     }
516   }
517 
518   private void createQuotaTable() throws IOException {
519     masterServices.createSystemTable(QuotaUtil.QUOTA_TABLE_DESC);
520   }
521 
522   private static class NamedLock<T> {
523     private final HashSet<T> locks = new HashSet<T>();
524 
525     public void lock(final T name) throws InterruptedException {
526       synchronized (locks) {
527         while (locks.contains(name)) {
528           locks.wait();
529         }
530         locks.add(name);
531       }
532     }
533 
534     public void unlock(final T name) {
535       synchronized (locks) {
536         locks.remove(name);
537         locks.notifyAll();
538       }
539     }
540   }
541 
542   @Override
543   public void onRegionSplitReverted(HRegionInfo hri) throws IOException {
544     if (initialized) {
545       this.namespaceQuotaManager.removeRegionFromNamespaceUsage(hri);
546     }
547   }
548 }