View Javadoc

1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
3    * agreements. See the NOTICE file distributed with this work for additional information regarding
4    * copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 (the
5    * "License"); you may not use this file except in compliance with the License. You may obtain a
6    * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable
7    * law or agreed to in writing, software distributed under the License is distributed on an "AS IS"
8    * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
9    * for the specific language governing permissions and limitations under the License.
10   */
11  
12  package org.apache.hadoop.hbase.quotas;
13  
14  import java.io.IOException;
15  import java.util.HashMap;
16  import java.util.List;
17  import java.util.Map;
18  
19  import org.apache.commons.logging.Log;
20  import org.apache.commons.logging.LogFactory;
21  import org.apache.hadoop.conf.Configuration;
22  import org.apache.hadoop.hbase.Cell;
23  import org.apache.hadoop.hbase.HColumnDescriptor;
24  import org.apache.hadoop.hbase.HConstants;
25  import org.apache.hadoop.hbase.HTableDescriptor;
26  import org.apache.hadoop.hbase.KeyValueUtil;
27  import org.apache.hadoop.hbase.TableName;
28  import org.apache.hadoop.hbase.classification.InterfaceAudience;
29  import org.apache.hadoop.hbase.classification.InterfaceStability;
30  import org.apache.hadoop.hbase.client.Connection;
31  import org.apache.hadoop.hbase.client.Delete;
32  import org.apache.hadoop.hbase.client.Get;
33  import org.apache.hadoop.hbase.client.Mutation;
34  import org.apache.hadoop.hbase.client.Put;
35  import org.apache.hadoop.hbase.client.Result;
36  import org.apache.hadoop.hbase.client.Table;
37  import org.apache.hadoop.hbase.protobuf.generated.QuotaProtos.Quotas;
38  import org.apache.hadoop.hbase.regionserver.BloomType;
39  import org.apache.hadoop.hbase.util.Bytes;
40  import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
41  
42  /**
43   * Helper class to interact with the quota table
44   */
45  @InterfaceAudience.Private
46  @InterfaceStability.Evolving
47  public class QuotaUtil extends QuotaTableUtil {
48    private static final Log LOG = LogFactory.getLog(QuotaUtil.class);
49  
50    public static final String QUOTA_CONF_KEY = "hbase.quota.enabled";
51    private static final boolean QUOTA_ENABLED_DEFAULT = false;
52  
53    public static final String QUOTA_RETRYABLE_THROTTING_EXCEPTION_CONF_KEY =
54        "hbase.quota.retryable.throttlingexception";
55    public static final boolean QUOTA_RETRYABLE_THROTTING_EXCEPTION_DEFAULT = false;
56  
57    public static final String READ_CAPACITY_UNIT_CONF_KEY = "hbase.quota.read.capacity.unit";
58    // the default one read capacity unit is 1024 bytes (1KB)
59    public static final long DEFAULT_READ_CAPACITY_UNIT = 1024;
60    public static final String WRITE_CAPACITY_UNIT_CONF_KEY = "hbase.quota.write.capacity.unit";
61    // the default one write capacity unit is 1024 bytes (1KB)
62    public static final long DEFAULT_WRITE_CAPACITY_UNIT = 1024;
63  
64    /** Table descriptor for Quota internal table */
65    public static final HTableDescriptor QUOTA_TABLE_DESC = new HTableDescriptor(QUOTA_TABLE_NAME);
66    static {
67      QUOTA_TABLE_DESC.addFamily(new HColumnDescriptor(QUOTA_FAMILY_INFO)
68          .setScope(HConstants.REPLICATION_SCOPE_LOCAL).setBloomFilterType(BloomType.ROW)
69          .setMaxVersions(1));
70      QUOTA_TABLE_DESC.addFamily(new HColumnDescriptor(QUOTA_FAMILY_USAGE)
71          .setScope(HConstants.REPLICATION_SCOPE_LOCAL).setBloomFilterType(BloomType.ROW)
72          .setMaxVersions(1));
73    }
74  
75    /** Returns true if the support for quota is enabled */
76    public static boolean isQuotaEnabled(final Configuration conf) {
77      return conf.getBoolean(QUOTA_CONF_KEY, QUOTA_ENABLED_DEFAULT);
78    }
79  
80    /*
81     * ========================================================================= Quota "settings"
82     * helpers
83     */
84    public static void addTableQuota(final Connection connection, final TableName table,
85        final Quotas data) throws IOException {
86      addQuotas(connection, getTableRowKey(table), data);
87    }
88  
89    public static void deleteTableQuota(final Connection connection, final TableName table)
90        throws IOException {
91      deleteQuotas(connection, getTableRowKey(table));
92    }
93  
94    public static void addNamespaceQuota(final Connection connection, final String namespace,
95        final Quotas data) throws IOException {
96      addQuotas(connection, getNamespaceRowKey(namespace), data);
97    }
98  
99    public static void deleteNamespaceQuota(final Connection connection, final String namespace)
100       throws IOException {
101     deleteQuotas(connection, getNamespaceRowKey(namespace));
102   }
103 
104   public static void
105       addUserQuota(final Connection connection, final String user, final Quotas data)
106           throws IOException {
107     addQuotas(connection, getUserRowKey(user), data);
108   }
109 
110   public static void addUserQuota(final Connection connection, final String user,
111       final TableName table, final Quotas data) throws IOException {
112     addQuotas(connection, getUserRowKey(user), getSettingsQualifierForUserTable(table), data);
113   }
114 
115   public static void addUserQuota(final Connection connection, final String user,
116       final String namespace, final Quotas data) throws IOException {
117     addQuotas(connection, getUserRowKey(user), getSettingsQualifierForUserNamespace(namespace),
118       data);
119   }
120 
121   public static void deleteUserQuota(final Connection connection, final String user)
122       throws IOException {
123     deleteQuotas(connection, getUserRowKey(user));
124   }
125 
126   public static void deleteUserQuota(final Connection connection, final String user,
127       final TableName table) throws IOException {
128     deleteQuotas(connection, getUserRowKey(user), getSettingsQualifierForUserTable(table));
129   }
130 
131   public static void deleteUserQuota(final Connection connection, final String user,
132       final String namespace) throws IOException {
133     deleteQuotas(connection, getUserRowKey(user), getSettingsQualifierForUserNamespace(namespace));
134   }
135 
136   private static void
137       addQuotas(final Connection connection, final byte[] rowKey, final Quotas data)
138           throws IOException {
139     addQuotas(connection, rowKey, QUOTA_QUALIFIER_SETTINGS, data);
140   }
141 
142   private static void addQuotas(final Connection connection, final byte[] rowKey,
143       final byte[] qualifier, final Quotas data) throws IOException {
144     Put put = new Put(rowKey);
145     put.addColumn(QUOTA_FAMILY_INFO, qualifier, quotasToData(data));
146     doPut(connection, put);
147   }
148 
149   private static void deleteQuotas(final Connection connection, final byte[] rowKey)
150       throws IOException {
151     deleteQuotas(connection, rowKey, null);
152   }
153 
154   private static void deleteQuotas(final Connection connection, final byte[] rowKey,
155       final byte[] qualifier) throws IOException {
156     Delete delete = new Delete(rowKey);
157     if (qualifier != null) {
158       delete.addColumns(QUOTA_FAMILY_INFO, qualifier);
159     }
160     doDelete(connection, delete);
161   }
162 
163   public static Map<String, UserQuotaState> fetchUserQuotas(final Connection connection,
164       final List<Get> gets) throws IOException {
165     long nowTs = EnvironmentEdgeManager.currentTime();
166     Result[] results = doGet(connection, gets);
167 
168     Map<String, UserQuotaState> userQuotas = new HashMap<String, UserQuotaState>(results.length);
169     for (int i = 0; i < results.length; ++i) {
170       byte[] key = gets.get(i).getRow();
171       assert isUserRowKey(key);
172       String user = getUserFromRowKey(key);
173 
174       final UserQuotaState quotaInfo = new UserQuotaState(nowTs);
175       userQuotas.put(user, quotaInfo);
176 
177       if (results[i].isEmpty()) continue;
178       assert Bytes.equals(key, results[i].getRow());
179 
180       try {
181         parseUserResult(user, results[i], new UserQuotasVisitor() {
182           @Override
183           public void visitUserQuotas(String userName, String namespace, Quotas quotas) {
184             quotaInfo.setQuotas(namespace, quotas);
185           }
186 
187           @Override
188           public void visitUserQuotas(String userName, TableName table, Quotas quotas) {
189             quotaInfo.setQuotas(table, quotas);
190           }
191 
192           @Override
193           public void visitUserQuotas(String userName, Quotas quotas) {
194             quotaInfo.setQuotas(quotas);
195           }
196         });
197       } catch (IOException e) {
198         LOG.error("Unable to parse user '" + user + "' quotas", e);
199         userQuotas.remove(user);
200       }
201     }
202     return userQuotas;
203   }
204 
205   public static Map<TableName, QuotaState> fetchTableQuotas(final Connection connection,
206       final List<Get> gets) throws IOException {
207     return fetchGlobalQuotas("table", connection, gets, new KeyFromRow<TableName>() {
208       @Override
209       public TableName getKeyFromRow(final byte[] row) {
210         assert isTableRowKey(row);
211         return getTableFromRowKey(row);
212       }
213     });
214   }
215 
216   public static Map<String, QuotaState> fetchNamespaceQuotas(final Connection connection,
217       final List<Get> gets) throws IOException {
218     return fetchGlobalQuotas("namespace", connection, gets, new KeyFromRow<String>() {
219       @Override
220       public String getKeyFromRow(final byte[] row) {
221         assert isNamespaceRowKey(row);
222         return getNamespaceFromRowKey(row);
223       }
224     });
225   }
226 
227   public static <K> Map<K, QuotaState> fetchGlobalQuotas(final String type,
228       final Connection connection, final List<Get> gets, final KeyFromRow<K> kfr)
229       throws IOException {
230     long nowTs = EnvironmentEdgeManager.currentTime();
231     Result[] results = doGet(connection, gets);
232 
233     Map<K, QuotaState> globalQuotas = new HashMap<K, QuotaState>(results.length);
234     for (int i = 0; i < results.length; ++i) {
235       byte[] row = gets.get(i).getRow();
236       K key = kfr.getKeyFromRow(row);
237 
238       QuotaState quotaInfo = new QuotaState(nowTs);
239       globalQuotas.put(key, quotaInfo);
240 
241       if (results[i].isEmpty()) continue;
242       assert Bytes.equals(row, results[i].getRow());
243 
244       byte[] data = results[i].getValue(QUOTA_FAMILY_INFO, QUOTA_QUALIFIER_SETTINGS);
245       if (data == null) continue;
246 
247       try {
248         Quotas quotas = quotasFromData(data);
249         quotaInfo.setQuotas(quotas);
250       } catch (IOException e) {
251         LOG.error("Unable to parse " + type + " '" + key + "' quotas", e);
252         globalQuotas.remove(key);
253       }
254     }
255     return globalQuotas;
256   }
257 
258   private static interface KeyFromRow<T> {
259     T getKeyFromRow(final byte[] row);
260   }
261 
262   /*
263    * ========================================================================= HTable helpers
264    */
265   private static void doPut(final Connection connection, final Put put) throws IOException {
266     try (Table table = connection.getTable(QuotaUtil.QUOTA_TABLE_NAME)) {
267       table.put(put);
268     }
269   }
270 
271   private static void doDelete(final Connection connection, final Delete delete) 
272       throws IOException {
273     try (Table table = connection.getTable(QuotaUtil.QUOTA_TABLE_NAME)) {
274       table.delete(delete);
275     }
276   }
277 
278   /*
279    * ========================================================================= Data Size Helpers
280    */
281   public static long calculateMutationSize(final Mutation mutation) {
282     long size = 0;
283     for (Map.Entry<byte[], List<Cell>> entry : mutation.getFamilyCellMap().entrySet()) {
284       for (Cell cell : entry.getValue()) {
285         size += KeyValueUtil.length(cell);
286       }
287     }
288     return size;
289   }
290 
291   public static long calculateResultSize(final Result result) {
292     long size = 0;
293     for (Cell cell : result.rawCells()) {
294       size += KeyValueUtil.length(cell);
295     }
296     return size;
297   }
298 
299   public static long calculateResultSize(final List<Result> results) {
300     long size = 0;
301     for (Result result : results) {
302       for (Cell cell : result.rawCells()) {
303         size += KeyValueUtil.length(cell);
304       }
305     }
306     return size;
307   }
308 }