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  public boolean hasTableLimiters() {
121    return tableLimiters != null && !tableLimiters.isEmpty();
122  }
123
124  private <K> Map<K, QuotaLimiter> setLimiter(Map<K, QuotaLimiter> limiters, final K key,
125    final Quotas quotas) {
126    if (limiters == null) {
127      limiters = new HashMap<>();
128    }
129
130    QuotaLimiter limiter =
131      quotas.hasThrottle() ? QuotaLimiterFactory.fromThrottle(quotas.getThrottle()) : null;
132    if (limiter != null && !limiter.isBypass()) {
133      limiters.put(key, limiter);
134    } else {
135      limiters.remove(key);
136    }
137    return limiters;
138  }
139
140  /**
141   * Perform an update of the quota state based on the other quota state object. (This operation is
142   * executed by the QuotaCache)
143   */
144  @Override
145  public synchronized void update(final QuotaState other) {
146    super.update(other);
147
148    if (other instanceof UserQuotaState) {
149      UserQuotaState uOther = (UserQuotaState) other;
150      tableLimiters = updateLimiters(tableLimiters, uOther.tableLimiters);
151      namespaceLimiters = updateLimiters(namespaceLimiters, uOther.namespaceLimiters);
152      bypassGlobals = uOther.bypassGlobals;
153    } else {
154      tableLimiters = null;
155      namespaceLimiters = null;
156      bypassGlobals = false;
157    }
158  }
159
160  private static <K> Map<K, QuotaLimiter> updateLimiters(final Map<K, QuotaLimiter> map,
161    final Map<K, QuotaLimiter> otherMap) {
162    if (map == null) {
163      return otherMap;
164    }
165
166    if (otherMap != null) {
167      // To Remove
168      Set<K> toRemove = new HashSet<>(map.keySet());
169      toRemove.removeAll(otherMap.keySet());
170      map.keySet().removeAll(toRemove);
171
172      // To Update/Add
173      for (final Map.Entry<K, QuotaLimiter> entry : otherMap.entrySet()) {
174        QuotaLimiter limiter = map.get(entry.getKey());
175        if (limiter == null) {
176          limiter = entry.getValue();
177        } else {
178          limiter = QuotaLimiterFactory.update(limiter, entry.getValue());
179        }
180        map.put(entry.getKey(), limiter);
181      }
182      return map;
183    }
184    return null;
185  }
186
187  /**
188   * Return the limiter for the specified table associated with this quota. If the table does not
189   * have its own quota limiter the global one will be returned. In case there is no quota limiter
190   * associated with this object a noop limiter will be returned.
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}