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