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 */
018package org.apache.hadoop.hbase.quotas;
019
020import java.util.HashMap;
021import java.util.HashSet;
022import java.util.Map;
023import java.util.Set;
024import org.apache.hadoop.hbase.TableName;
025import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
026import org.apache.yetus.audience.InterfaceAudience;
027import org.apache.yetus.audience.InterfaceStability;
028
029import org.apache.hadoop.hbase.shaded.protobuf.generated.QuotaProtos.Quotas;
030
031/**
032 * In-Memory state of the user quotas
033 */
034@InterfaceAudience.Private
035@InterfaceStability.Evolving
036@edu.umd.cs.findbugs.annotations.SuppressWarnings(value = "IS2_INCONSISTENT_SYNC",
037    justification = "FindBugs seems confused; says bypassGlobals, namepaceLimiters, and "
038      + "tableLimiters are mostly synchronized..."
039      + "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  /** Returns true if there is no quota information associated to this object */
087  @Override
088  public synchronized boolean isBypass() {
089    return !bypassGlobals && getGlobalLimiterWithoutUpdatingLastQuery() == NoopQuotaLimiter.get()
090      && (tableLimiters == null || tableLimiters.isEmpty())
091      && (namespaceLimiters == null || namespaceLimiters.isEmpty());
092  }
093
094  public synchronized boolean hasBypassGlobals() {
095    return bypassGlobals;
096  }
097
098  @Override
099  public synchronized void setQuotas(final Quotas quotas) {
100    super.setQuotas(quotas);
101    bypassGlobals = quotas.getBypassGlobals();
102  }
103
104  /**
105   * Add the quota information of the specified table. (This operation is part of the QuotaState
106   * setup)
107   */
108  public synchronized void setQuotas(final TableName table, Quotas quotas) {
109    tableLimiters = setLimiter(tableLimiters, table, quotas);
110  }
111
112  /**
113   * Add the quota information of the specified namespace. (This operation is part of the QuotaState
114   * setup)
115   */
116  public void setQuotas(final String namespace, Quotas quotas) {
117    namespaceLimiters = setLimiter(namespaceLimiters, namespace, quotas);
118  }
119
120  private <K> Map<K, QuotaLimiter> setLimiter(Map<K, QuotaLimiter> limiters, final K key,
121    final Quotas quotas) {
122    if (limiters == null) {
123      limiters = new HashMap<>();
124    }
125
126    QuotaLimiter limiter =
127      quotas.hasThrottle() ? QuotaLimiterFactory.fromThrottle(quotas.getThrottle()) : null;
128    if (limiter != null && !limiter.isBypass()) {
129      limiters.put(key, limiter);
130    } else {
131      limiters.remove(key);
132    }
133    return limiters;
134  }
135
136  /**
137   * Perform an update of the quota state based on the other quota state object. (This operation is
138   * executed by the QuotaCache)
139   */
140  @Override
141  public synchronized void update(final QuotaState other) {
142    super.update(other);
143
144    if (other instanceof UserQuotaState) {
145      UserQuotaState uOther = (UserQuotaState) other;
146      tableLimiters = updateLimiters(tableLimiters, uOther.tableLimiters);
147      namespaceLimiters = updateLimiters(namespaceLimiters, uOther.namespaceLimiters);
148      bypassGlobals = uOther.bypassGlobals;
149    } else {
150      tableLimiters = null;
151      namespaceLimiters = null;
152      bypassGlobals = false;
153    }
154  }
155
156  private static <K> Map<K, QuotaLimiter> updateLimiters(final Map<K, QuotaLimiter> map,
157    final Map<K, QuotaLimiter> otherMap) {
158    if (map == null) {
159      return otherMap;
160    }
161
162    if (otherMap != null) {
163      // To Remove
164      Set<K> toRemove = new HashSet<>(map.keySet());
165      toRemove.removeAll(otherMap.keySet());
166      map.keySet().removeAll(toRemove);
167
168      // To Update/Add
169      for (final Map.Entry<K, QuotaLimiter> entry : otherMap.entrySet()) {
170        QuotaLimiter limiter = map.get(entry.getKey());
171        if (limiter == null) {
172          limiter = entry.getValue();
173        } else {
174          limiter = QuotaLimiterFactory.update(limiter, entry.getValue());
175        }
176        map.put(entry.getKey(), limiter);
177      }
178      return map;
179    }
180    return null;
181  }
182
183  /**
184   * Return the limiter for the specified table associated with this quota. If the table does not
185   * have its own quota limiter the global one will be returned. In case there is no quota limiter
186   * associated with this object a noop limiter will be returned.
187   * @return the quota limiter for the specified table
188   */
189  public synchronized QuotaLimiter getTableLimiter(final TableName table) {
190    lastQuery = EnvironmentEdgeManager.currentTime();
191    if (tableLimiters != null) {
192      QuotaLimiter limiter = tableLimiters.get(table);
193      if (limiter != null) return limiter;
194    }
195    if (namespaceLimiters != null) {
196      QuotaLimiter limiter = namespaceLimiters.get(table.getNamespaceAsString());
197      if (limiter != null) return limiter;
198    }
199    return getGlobalLimiterWithoutUpdatingLastQuery();
200  }
201}