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.yetus.audience.InterfaceAudience;
026import org.apache.yetus.audience.InterfaceStability;
027
028import org.apache.hadoop.hbase.shaded.protobuf.generated.QuotaProtos.Quotas;
029
030/**
031 * In-Memory state of the user quotas
032 */
033@InterfaceAudience.Private
034@InterfaceStability.Evolving
035@edu.umd.cs.findbugs.annotations.SuppressWarnings(value = "IS2_INCONSISTENT_SYNC",
036    justification = "FindBugs seems confused; says bypassGlobals, namepaceLimiters, and "
037      + "tableLimiters are mostly synchronized..."
038      + "but to me it looks like they are totally synchronized")
039public class UserQuotaState extends QuotaState {
040  private Map<String, QuotaLimiter> namespaceLimiters = null;
041  private Map<TableName, QuotaLimiter> tableLimiters = null;
042  private boolean bypassGlobals = false;
043
044  @Override
045  public synchronized String toString() {
046    StringBuilder builder = new StringBuilder();
047    builder.append("UserQuotaState(");
048    if (bypassGlobals) {
049      builder.append("bypass-globals");
050    }
051
052    if (isBypass()) {
053      builder.append(" bypass");
054    } else {
055      if (getGlobalLimiter() != NoopQuotaLimiter.get()) {
056        builder.append(" global-limiter");
057      }
058
059      if (tableLimiters != null && !tableLimiters.isEmpty()) {
060        builder.append(" [");
061        for (TableName table : tableLimiters.keySet()) {
062          builder.append(" " + table);
063        }
064        builder.append(" ]");
065      }
066
067      if (namespaceLimiters != null && !namespaceLimiters.isEmpty()) {
068        builder.append(" [");
069        for (String ns : namespaceLimiters.keySet()) {
070          builder.append(" " + ns);
071        }
072        builder.append(" ]");
073      }
074    }
075    builder.append(')');
076    return builder.toString();
077  }
078
079  /** Returns true if there is no quota information associated to this object */
080  @Override
081  public synchronized boolean isBypass() {
082    return !bypassGlobals && getGlobalLimiter() == NoopQuotaLimiter.get()
083      && (tableLimiters == null || tableLimiters.isEmpty())
084      && (namespaceLimiters == null || namespaceLimiters.isEmpty());
085  }
086
087  public synchronized boolean hasBypassGlobals() {
088    return bypassGlobals;
089  }
090
091  @Override
092  public synchronized void setQuotas(final Quotas quotas) {
093    super.setQuotas(quotas);
094    bypassGlobals = quotas.getBypassGlobals();
095  }
096
097  /**
098   * Add the quota information of the specified table. (This operation is part of the QuotaState
099   * setup)
100   */
101  public synchronized void setQuotas(final TableName table, Quotas quotas) {
102    tableLimiters = setLimiter(tableLimiters, table, quotas);
103  }
104
105  /**
106   * Add the quota information of the specified namespace. (This operation is part of the QuotaState
107   * setup)
108   */
109  public void setQuotas(final String namespace, Quotas quotas) {
110    namespaceLimiters = setLimiter(namespaceLimiters, namespace, quotas);
111  }
112
113  public boolean hasTableLimiters() {
114    return tableLimiters != null && !tableLimiters.isEmpty();
115  }
116
117  private <K> Map<K, QuotaLimiter> setLimiter(Map<K, QuotaLimiter> limiters, final K key,
118    final Quotas quotas) {
119    if (limiters == null) {
120      limiters = new HashMap<>();
121    }
122
123    QuotaLimiter limiter =
124      quotas.hasThrottle() ? QuotaLimiterFactory.fromThrottle(quotas.getThrottle()) : null;
125    if (limiter != null && !limiter.isBypass()) {
126      limiters.put(key, limiter);
127    } else {
128      limiters.remove(key);
129    }
130    return limiters;
131  }
132
133  /**
134   * Perform an update of the quota state based on the other quota state object. (This operation is
135   * executed by the QuotaCache)
136   */
137  @Override
138  public synchronized void update(final QuotaState other) {
139    super.update(other);
140
141    if (other instanceof UserQuotaState) {
142      UserQuotaState uOther = (UserQuotaState) other;
143      tableLimiters = updateLimiters(tableLimiters, uOther.tableLimiters);
144      namespaceLimiters = updateLimiters(namespaceLimiters, uOther.namespaceLimiters);
145      bypassGlobals = uOther.bypassGlobals;
146    } else {
147      tableLimiters = null;
148      namespaceLimiters = null;
149      bypassGlobals = false;
150    }
151  }
152
153  private static <K> Map<K, QuotaLimiter> updateLimiters(final Map<K, QuotaLimiter> map,
154    final Map<K, QuotaLimiter> otherMap) {
155    if (map == null) {
156      return otherMap;
157    }
158
159    if (otherMap != null) {
160      // To Remove
161      Set<K> toRemove = new HashSet<>(map.keySet());
162      toRemove.removeAll(otherMap.keySet());
163      map.keySet().removeAll(toRemove);
164
165      // To Update/Add
166      for (final Map.Entry<K, QuotaLimiter> entry : otherMap.entrySet()) {
167        QuotaLimiter limiter = map.get(entry.getKey());
168        if (limiter == null) {
169          limiter = entry.getValue();
170        } else {
171          limiter = QuotaLimiterFactory.update(limiter, entry.getValue());
172        }
173        map.put(entry.getKey(), limiter);
174      }
175      return map;
176    }
177    return null;
178  }
179
180  /**
181   * Return the limiter for the specified table associated with this quota. If the table does not
182   * have its own quota limiter the global one will be returned. In case there is no quota limiter
183   * associated with this object a noop limiter will be returned.
184   * @return the quota limiter for the specified table
185   */
186  public synchronized QuotaLimiter getTableLimiter(final TableName table) {
187    if (tableLimiters != null) {
188      QuotaLimiter limiter = tableLimiters.get(table);
189      if (limiter != null) return limiter;
190    }
191    if (namespaceLimiters != null) {
192      QuotaLimiter limiter = namespaceLimiters.get(table.getNamespaceAsString());
193      if (limiter != null) return limiter;
194    }
195    return getGlobalLimiter();
196  }
197}