001/**
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018
019package org.apache.hadoop.hbase.quotas;
020
021import java.util.HashMap;
022import java.util.HashSet;
023import java.util.Map;
024import java.util.Set;
025
026import org.apache.hadoop.hbase.TableName;
027import org.apache.yetus.audience.InterfaceAudience;
028import org.apache.yetus.audience.InterfaceStability;
029import org.apache.hadoop.hbase.shaded.protobuf.generated.QuotaProtos.Quotas;
030import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
031
032/**
033 * In-Memory state of the user quotas
034 */
035@InterfaceAudience.Private
036@InterfaceStability.Evolving
037@edu.umd.cs.findbugs.annotations.SuppressWarnings(value="IS2_INCONSISTENT_SYNC",
038  justification="FindBugs seems confused; says bypassGlobals, namepaceLimiters, and " +
039    "tableLimiters are mostly synchronized...but to me it looks like they are totally synchronized")
040public class UserQuotaState extends QuotaState {
041  private Map<String, QuotaLimiter> namespaceLimiters = null;
042  private Map<TableName, QuotaLimiter> tableLimiters = null;
043  private boolean bypassGlobals = false;
044
045  public UserQuotaState() {
046    super();
047  }
048
049  public UserQuotaState(final long updateTs) {
050    super(updateTs);
051  }
052
053  @Override
054  public synchronized String toString() {
055    StringBuilder builder = new StringBuilder();
056    builder.append("UserQuotaState(ts=" + getLastUpdate());
057    if (bypassGlobals) builder.append(" bypass-globals");
058
059    if (isBypass()) {
060      builder.append(" bypass");
061    } else {
062      if (getGlobalLimiterWithoutUpdatingLastQuery() != NoopQuotaLimiter.get()) {
063        builder.append(" global-limiter");
064      }
065
066      if (tableLimiters != null && !tableLimiters.isEmpty()) {
067        builder.append(" [");
068        for (TableName table: tableLimiters.keySet()) {
069          builder.append(" " + table);
070        }
071        builder.append(" ]");
072      }
073
074      if (namespaceLimiters != null && !namespaceLimiters.isEmpty()) {
075        builder.append(" [");
076        for (String ns: namespaceLimiters.keySet()) {
077          builder.append(" " + ns);
078        }
079        builder.append(" ]");
080      }
081    }
082    builder.append(')');
083    return builder.toString();
084  }
085
086  /**
087   * @return true if there is no quota information associated to this object
088   */
089  @Override
090  public synchronized boolean isBypass() {
091    return !bypassGlobals &&
092        getGlobalLimiterWithoutUpdatingLastQuery() == NoopQuotaLimiter.get() &&
093        (tableLimiters == null || tableLimiters.isEmpty()) &&
094        (namespaceLimiters == null || namespaceLimiters.isEmpty());
095  }
096
097  public synchronized boolean hasBypassGlobals() {
098    return bypassGlobals;
099  }
100
101  @Override
102  public synchronized void setQuotas(final Quotas quotas) {
103    super.setQuotas(quotas);
104    bypassGlobals = quotas.getBypassGlobals();
105  }
106
107  /**
108   * Add the quota information of the specified table.
109   * (This operation is part of the QuotaState setup)
110   */
111  public synchronized void setQuotas(final TableName table, Quotas quotas) {
112    tableLimiters = setLimiter(tableLimiters, table, quotas);
113  }
114
115  /**
116   * Add the quota information of the specified namespace.
117   * (This operation is part of the QuotaState setup)
118   */
119  public void setQuotas(final String namespace, Quotas quotas) {
120    namespaceLimiters = setLimiter(namespaceLimiters, namespace, quotas);
121  }
122
123  private <K> Map<K, QuotaLimiter> setLimiter(Map<K, QuotaLimiter> limiters,
124      final K key, final Quotas quotas) {
125    if (limiters == null) {
126      limiters = new HashMap<>();
127    }
128
129    QuotaLimiter limiter = quotas.hasThrottle() ?
130      QuotaLimiterFactory.fromThrottle(quotas.getThrottle()) : null;
131    if (limiter != null && !limiter.isBypass()) {
132      limiters.put(key, limiter);
133    } else {
134      limiters.remove(key);
135    }
136    return limiters;
137  }
138
139  /**
140   * Perform an update of the quota state based on the other quota state object.
141   * (This operation is executed by the QuotaCache)
142   */
143  @Override
144  public synchronized void update(final QuotaState other) {
145    super.update(other);
146
147    if (other instanceof UserQuotaState) {
148      UserQuotaState uOther = (UserQuotaState)other;
149      tableLimiters = updateLimiters(tableLimiters, uOther.tableLimiters);
150      namespaceLimiters = updateLimiters(namespaceLimiters, uOther.namespaceLimiters);
151      bypassGlobals = uOther.bypassGlobals;
152    } else {
153      tableLimiters = null;
154      namespaceLimiters = null;
155      bypassGlobals = false;
156    }
157  }
158
159  private static <K> Map<K, QuotaLimiter> updateLimiters(final Map<K, QuotaLimiter> map,
160      final Map<K, QuotaLimiter> otherMap) {
161    if (map == null) {
162      return otherMap;
163    }
164
165    if (otherMap != null) {
166      // To Remove
167      Set<K> toRemove = new HashSet<>(map.keySet());
168      toRemove.removeAll(otherMap.keySet());
169      map.keySet().removeAll(toRemove);
170
171      // To Update/Add
172      for (final Map.Entry<K, QuotaLimiter> entry: otherMap.entrySet()) {
173        QuotaLimiter limiter = map.get(entry.getKey());
174        if (limiter == null) {
175          limiter = entry.getValue();
176        } else {
177          limiter = QuotaLimiterFactory.update(limiter, entry.getValue());
178        }
179        map.put(entry.getKey(), limiter);
180      }
181      return map;
182    }
183    return null;
184  }
185
186  /**
187   * Return the limiter for the specified table associated with this quota.
188   * If the table does not have its own quota limiter the global one will be returned.
189   * In case there is no quota limiter associated with this object a noop limiter will be returned.
190   *
191   * @return the quota limiter for the specified table
192   */
193  public synchronized QuotaLimiter getTableLimiter(final TableName table) {
194    lastQuery = EnvironmentEdgeManager.currentTime();
195    if (tableLimiters != null) {
196      QuotaLimiter limiter = tableLimiters.get(table);
197      if (limiter != null) return limiter;
198    }
199    if (namespaceLimiters != null) {
200      QuotaLimiter limiter = namespaceLimiters.get(table.getNamespaceAsString());
201      if (limiter != null) return limiter;
202    }
203    return getGlobalLimiterWithoutUpdatingLastQuery();
204  }
205}