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.util.HashMap;
15  import java.util.HashSet;
16  import java.util.Map;
17  import java.util.Set;
18  
19  import org.apache.hadoop.hbase.TableName;
20  import org.apache.hadoop.hbase.classification.InterfaceAudience;
21  import org.apache.hadoop.hbase.classification.InterfaceStability;
22  import org.apache.hadoop.hbase.protobuf.generated.QuotaProtos.Quotas;
23  import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
24  
25  /**
26   * In-Memory state of the user quotas
27   */
28  @InterfaceAudience.Private
29  @InterfaceStability.Evolving
30  @edu.umd.cs.findbugs.annotations.SuppressWarnings(value="IS2_INCONSISTENT_SYNC",
31    justification="FindBugs seems confused; says bypassGlobals, namepaceLimiters, and " +
32      "tableLimiters are mostly synchronized...but to me it looks like they are totally synchronized")
33  public class UserQuotaState extends QuotaState {
34    private Map<String, QuotaLimiter> namespaceLimiters = null;
35    private Map<TableName, QuotaLimiter> tableLimiters = null;
36    private boolean bypassGlobals = false;
37  
38    public UserQuotaState() {
39      super();
40    }
41  
42    public UserQuotaState(final long updateTs) {
43      super(updateTs);
44    }
45  
46    @Override
47    public synchronized String toString() {
48      StringBuilder builder = new StringBuilder();
49      builder.append("UserQuotaState(ts=" + getLastUpdate());
50      if (bypassGlobals) builder.append(" bypass-globals");
51  
52      if (isBypass()) {
53        builder.append(" bypass");
54      } else {
55        if (getGlobalLimiterWithoutUpdatingLastQuery() != NoopQuotaLimiter.get()) {
56          builder.append(" global-limiter");
57        }
58  
59        if (tableLimiters != null && !tableLimiters.isEmpty()) {
60          builder.append(" [");
61          for (TableName table : tableLimiters.keySet()) {
62            builder.append(" " + table);
63          }
64          builder.append(" ]");
65        }
66  
67        if (namespaceLimiters != null && !namespaceLimiters.isEmpty()) {
68          builder.append(" [");
69          for (String ns : namespaceLimiters.keySet()) {
70            builder.append(" " + ns);
71          }
72          builder.append(" ]");
73        }
74      }
75      builder.append(')');
76      return builder.toString();
77    }
78  
79    /**
80     * @return true if there is no quota information associated to this object
81     */
82    @Override
83    public synchronized boolean isBypass() {
84      return !bypassGlobals && getGlobalLimiterWithoutUpdatingLastQuery() == NoopQuotaLimiter.get()
85          && (tableLimiters == null || tableLimiters.isEmpty())
86          && (namespaceLimiters == null || namespaceLimiters.isEmpty());
87    }
88  
89    public synchronized boolean hasBypassGlobals() {
90      return bypassGlobals;
91    }
92  
93    @Override
94    public synchronized void setQuotas(final Quotas quotas) {
95      super.setQuotas(quotas);
96      bypassGlobals = quotas.getBypassGlobals();
97    }
98  
99    /**
100    * Add the quota information of the specified table. (This operation is part of the QuotaState
101    * setup)
102    */
103   public synchronized void setQuotas(final TableName table, Quotas quotas) {
104     tableLimiters = setLimiter(tableLimiters, table, quotas);
105   }
106 
107   /**
108    * Add the quota information of the specified namespace. (This operation is part of the QuotaState
109    * setup)
110    */
111   public synchronized void setQuotas(final String namespace, Quotas quotas) {
112     namespaceLimiters = setLimiter(namespaceLimiters, namespace, quotas);
113   }
114 
115   private <K> Map<K, QuotaLimiter> setLimiter(Map<K, QuotaLimiter> limiters, final K key,
116       final Quotas quotas) {
117     if (limiters == null) {
118       limiters = new HashMap<K, QuotaLimiter>();
119     }
120 
121     QuotaLimiter limiter =
122         quotas.hasThrottle() ? QuotaLimiterFactory.fromThrottle(quotas.getThrottle()) : null;
123     if (limiter != null && !limiter.isBypass()) {
124       limiters.put(key, limiter);
125     } else {
126       limiters.remove(key);
127     }
128     return limiters;
129   }
130 
131   /**
132    * Perform an update of the quota state based on the other quota state object. (This operation is
133    * executed by the QuotaCache)
134    */
135   @Override
136   public synchronized void update(final QuotaState other) {
137     super.update(other);
138 
139     if (other instanceof UserQuotaState) {
140       UserQuotaState uOther = (UserQuotaState) other;
141       tableLimiters = updateLimiters(tableLimiters, uOther.tableLimiters);
142       namespaceLimiters = updateLimiters(namespaceLimiters, uOther.namespaceLimiters);
143       bypassGlobals = uOther.bypassGlobals;
144     } else {
145       tableLimiters = null;
146       namespaceLimiters = null;
147       bypassGlobals = false;
148     }
149   }
150 
151   private static <K> Map<K, QuotaLimiter> updateLimiters(final Map<K, QuotaLimiter> map,
152       final Map<K, QuotaLimiter> otherMap) {
153     if (map == null) {
154       return otherMap;
155     }
156 
157     if (otherMap != null) {
158       // To Remove
159       Set<K> toRemove = new HashSet<K>(map.keySet());
160       toRemove.removeAll(otherMap.keySet());
161       map.keySet().removeAll(toRemove);
162 
163       // To Update/Add
164       for (final Map.Entry<K, QuotaLimiter> entry : otherMap.entrySet()) {
165         QuotaLimiter limiter = map.get(entry.getKey());
166         if (limiter == null) {
167           limiter = entry.getValue();
168         } else {
169           limiter = QuotaLimiterFactory.update(limiter, entry.getValue());
170         }
171         map.put(entry.getKey(), limiter);
172       }
173       return map;
174     }
175     return null;
176   }
177 
178   /**
179    * Return the limiter for the specified table associated with this quota. If the table does not
180    * have its own quota limiter the global one will be returned. In case there is no quota limiter
181    * associated with this object a noop limiter will be returned.
182    * @return the quota limiter for the specified table
183    */
184   public synchronized QuotaLimiter getTableLimiter(final TableName table) {
185     setLastQuery(EnvironmentEdgeManager.currentTime());
186     if (tableLimiters != null) {
187       QuotaLimiter limiter = tableLimiters.get(table);
188       if (limiter != null) return limiter;
189     }
190     if (namespaceLimiters != null) {
191       QuotaLimiter limiter = namespaceLimiters.get(table.getNamespaceAsString());
192       if (limiter != null) return limiter;
193     }
194     return getGlobalLimiterWithoutUpdatingLastQuery();
195   }
196 }