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