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.io.IOException;
022import java.util.HashMap;
023import java.util.List;
024import java.util.Map;
025
026import org.apache.hadoop.conf.Configuration;
027import org.apache.hadoop.hbase.Cell;
028import org.apache.hadoop.hbase.HColumnDescriptor;
029import org.apache.hadoop.hbase.HConstants;
030import org.apache.hadoop.hbase.HTableDescriptor;
031import org.apache.hadoop.hbase.KeyValueUtil;
032import org.apache.hadoop.hbase.TableName;
033import org.apache.yetus.audience.InterfaceAudience;
034import org.apache.yetus.audience.InterfaceStability;
035import org.slf4j.Logger;
036import org.slf4j.LoggerFactory;
037import org.apache.hadoop.hbase.client.Connection;
038import org.apache.hadoop.hbase.client.Delete;
039import org.apache.hadoop.hbase.client.Get;
040import org.apache.hadoop.hbase.client.Mutation;
041import org.apache.hadoop.hbase.client.Put;
042import org.apache.hadoop.hbase.client.Result;
043import org.apache.hadoop.hbase.client.Table;
044import org.apache.hadoop.hbase.shaded.protobuf.generated.QuotaProtos.Quotas;
045import org.apache.hadoop.hbase.regionserver.BloomType;
046import org.apache.hadoop.hbase.util.Bytes;
047import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
048
049/**
050 * Helper class to interact with the quota table
051 */
052@InterfaceAudience.Private
053@InterfaceStability.Evolving
054public class QuotaUtil extends QuotaTableUtil {
055  private static final Logger LOG = LoggerFactory.getLogger(QuotaUtil.class);
056
057  public static final String QUOTA_CONF_KEY = "hbase.quota.enabled";
058  private static final boolean QUOTA_ENABLED_DEFAULT = false;
059
060  /** Table descriptor for Quota internal table */
061  public static final HTableDescriptor QUOTA_TABLE_DESC =
062    new HTableDescriptor(QUOTA_TABLE_NAME);
063  static {
064    QUOTA_TABLE_DESC.addFamily(
065      new HColumnDescriptor(QUOTA_FAMILY_INFO)
066        .setScope(HConstants.REPLICATION_SCOPE_LOCAL)
067        .setBloomFilterType(BloomType.ROW)
068        .setMaxVersions(1)
069    );
070    QUOTA_TABLE_DESC.addFamily(
071      new HColumnDescriptor(QUOTA_FAMILY_USAGE)
072        .setScope(HConstants.REPLICATION_SCOPE_LOCAL)
073        .setBloomFilterType(BloomType.ROW)
074        .setMaxVersions(1)
075    );
076  }
077
078  /** Returns true if the support for quota is enabled */
079  public static boolean isQuotaEnabled(final Configuration conf) {
080    return conf.getBoolean(QUOTA_CONF_KEY, QUOTA_ENABLED_DEFAULT);
081  }
082
083  /* =========================================================================
084   *  Quota "settings" helpers
085   */
086  public static void addTableQuota(final Connection connection, final TableName table,
087      final Quotas data) throws IOException {
088    addQuotas(connection, getTableRowKey(table), data);
089  }
090
091  public static void deleteTableQuota(final Connection connection, final TableName table)
092      throws IOException {
093    deleteQuotas(connection, getTableRowKey(table));
094  }
095
096  public static void addNamespaceQuota(final Connection connection, final String namespace,
097      final Quotas data) throws IOException {
098    addQuotas(connection, getNamespaceRowKey(namespace), data);
099  }
100
101  public static void deleteNamespaceQuota(final Connection connection, final String namespace)
102      throws IOException {
103    deleteQuotas(connection, getNamespaceRowKey(namespace));
104  }
105
106  public static void addUserQuota(final Connection connection, final String user,
107      final Quotas data) throws IOException {
108    addQuotas(connection, getUserRowKey(user), data);
109  }
110
111  public static void addUserQuota(final Connection connection, final String user,
112      final TableName table, final Quotas data) throws IOException {
113    addQuotas(connection, getUserRowKey(user), getSettingsQualifierForUserTable(table), data);
114  }
115
116  public static void addUserQuota(final Connection connection, final String user,
117      final String namespace, final Quotas data) throws IOException {
118    addQuotas(connection, getUserRowKey(user),
119        getSettingsQualifierForUserNamespace(namespace), data);
120  }
121
122  public static void deleteUserQuota(final Connection connection, final String user)
123      throws IOException {
124    deleteQuotas(connection, getUserRowKey(user));
125  }
126
127  public static void deleteUserQuota(final Connection connection, final String user,
128      final TableName table) throws IOException {
129    deleteQuotas(connection, getUserRowKey(user),
130        getSettingsQualifierForUserTable(table));
131  }
132
133  public static void deleteUserQuota(final Connection connection, final String user,
134      final String namespace) throws IOException {
135    deleteQuotas(connection, getUserRowKey(user),
136        getSettingsQualifierForUserNamespace(namespace));
137  }
138
139  private static void addQuotas(final Connection connection, final byte[] rowKey,
140      final Quotas data) throws IOException {
141    addQuotas(connection, rowKey, QUOTA_QUALIFIER_SETTINGS, data);
142  }
143
144  private static void addQuotas(final Connection connection, final byte[] rowKey,
145      final byte[] qualifier, final Quotas data) throws IOException {
146    Put put = new Put(rowKey);
147    put.addColumn(QUOTA_FAMILY_INFO, qualifier, quotasToData(data));
148    doPut(connection, put);
149  }
150
151  private static void deleteQuotas(final Connection connection, final byte[] rowKey)
152      throws IOException {
153    deleteQuotas(connection, rowKey, null);
154  }
155
156  private static void deleteQuotas(final Connection connection, final byte[] rowKey,
157      final byte[] qualifier) throws IOException {
158    Delete delete = new Delete(rowKey);
159    if (qualifier != null) {
160      delete.addColumns(QUOTA_FAMILY_INFO, qualifier);
161    }
162    doDelete(connection, delete);
163  }
164
165  public static Map<String, UserQuotaState> fetchUserQuotas(final Connection connection,
166      final List<Get> gets) throws IOException {
167    long nowTs = EnvironmentEdgeManager.currentTime();
168    Result[] results = doGet(connection, gets);
169
170    Map<String, UserQuotaState> userQuotas = new HashMap<>(results.length);
171    for (int i = 0; i < results.length; ++i) {
172      byte[] key = gets.get(i).getRow();
173      assert isUserRowKey(key);
174      String user = getUserFromRowKey(key);
175
176      final UserQuotaState quotaInfo = new UserQuotaState(nowTs);
177      userQuotas.put(user, quotaInfo);
178
179      if (results[i].isEmpty()) continue;
180      assert Bytes.equals(key, results[i].getRow());
181
182      try {
183        parseUserResult(user, results[i], new UserQuotasVisitor() {
184          @Override
185          public void visitUserQuotas(String userName, String namespace, Quotas quotas) {
186            quotaInfo.setQuotas(namespace, quotas);
187          }
188
189          @Override
190          public void visitUserQuotas(String userName, TableName table, Quotas quotas) {
191            quotaInfo.setQuotas(table, quotas);
192          }
193
194          @Override
195          public void visitUserQuotas(String userName, Quotas quotas) {
196            quotaInfo.setQuotas(quotas);
197          }
198        });
199      } catch (IOException e) {
200        LOG.error("Unable to parse user '" + user + "' quotas", e);
201        userQuotas.remove(user);
202      }
203    }
204    return userQuotas;
205  }
206
207  public static Map<TableName, QuotaState> fetchTableQuotas(final Connection connection,
208      final List<Get> gets) throws IOException {
209    return fetchGlobalQuotas("table", connection, gets, new KeyFromRow<TableName>() {
210      @Override
211      public TableName getKeyFromRow(final byte[] row) {
212        assert isTableRowKey(row);
213        return getTableFromRowKey(row);
214      }
215    });
216  }
217
218  public static Map<String, QuotaState> fetchNamespaceQuotas(final Connection connection,
219      final List<Get> gets) throws IOException {
220    return fetchGlobalQuotas("namespace", connection, gets, new KeyFromRow<String>() {
221      @Override
222      public String getKeyFromRow(final byte[] row) {
223        assert isNamespaceRowKey(row);
224        return getNamespaceFromRowKey(row);
225      }
226    });
227  }
228
229  public static <K> Map<K, QuotaState> fetchGlobalQuotas(final String type,
230      final Connection connection, final List<Get> gets, final KeyFromRow<K> kfr)
231  throws IOException {
232    long nowTs = EnvironmentEdgeManager.currentTime();
233    Result[] results = doGet(connection, gets);
234
235    Map<K, QuotaState> globalQuotas = new HashMap<>(results.length);
236    for (int i = 0; i < results.length; ++i) {
237      byte[] row = gets.get(i).getRow();
238      K key = kfr.getKeyFromRow(row);
239
240      QuotaState quotaInfo = new QuotaState(nowTs);
241      globalQuotas.put(key, quotaInfo);
242
243      if (results[i].isEmpty()) continue;
244      assert Bytes.equals(row, results[i].getRow());
245
246      byte[] data = results[i].getValue(QUOTA_FAMILY_INFO, QUOTA_QUALIFIER_SETTINGS);
247      if (data == null) continue;
248
249      try {
250        Quotas quotas = quotasFromData(data);
251        quotaInfo.setQuotas(quotas);
252      } catch (IOException e) {
253        LOG.error("Unable to parse " + type + " '" + key + "' quotas", e);
254        globalQuotas.remove(key);
255      }
256    }
257    return globalQuotas;
258  }
259
260  private static interface KeyFromRow<T> {
261    T getKeyFromRow(final byte[] row);
262  }
263
264  /* =========================================================================
265   *  HTable helpers
266   */
267  private static void doPut(final Connection connection, final Put put)
268  throws IOException {
269    try (Table table = connection.getTable(QuotaUtil.QUOTA_TABLE_NAME)) {
270      table.put(put);
271    }
272  }
273
274  private static void doDelete(final Connection connection, final Delete delete)
275  throws IOException {
276    try (Table table = connection.getTable(QuotaUtil.QUOTA_TABLE_NAME)) {
277      table.delete(delete);
278    }
279  }
280
281  /* =========================================================================
282   *  Data Size Helpers
283   */
284  public static long calculateMutationSize(final Mutation mutation) {
285    long size = 0;
286    for (Map.Entry<byte [], List<Cell>> entry : mutation.getFamilyCellMap().entrySet()) {
287      for (Cell cell : entry.getValue()) {
288        size += KeyValueUtil.length(cell);
289      }
290    }
291    return size;
292  }
293
294  public static long calculateResultSize(final Result result) {
295    long size = 0;
296    for (Cell cell : result.rawCells()) {
297      size += KeyValueUtil.length(cell);
298    }
299    return size;
300  }
301
302  public static long calculateResultSize(final List<Result> results) {
303    long size = 0;
304    for (Result result: results) {
305      for (Cell cell : result.rawCells()) {
306        size += KeyValueUtil.length(cell);
307      }
308    }
309    return size;
310  }
311}