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.ByteArrayInputStream;
15  import java.io.ByteArrayOutputStream;
16  import java.io.IOException;
17  import java.util.HashMap;
18  import java.util.List;
19  import java.util.Map;
20  import java.util.Objects;
21  import java.util.regex.Pattern;
22  
23  import org.apache.commons.logging.Log;
24  import org.apache.commons.logging.LogFactory;
25  import org.apache.hadoop.hbase.Cell;
26  import org.apache.hadoop.hbase.NamespaceDescriptor;
27  import org.apache.hadoop.hbase.ServerName;
28  import org.apache.hadoop.hbase.TableName;
29  import org.apache.hadoop.hbase.classification.InterfaceAudience;
30  import org.apache.hadoop.hbase.classification.InterfaceStability;
31  import org.apache.hadoop.hbase.client.ClusterConnection;
32  import org.apache.hadoop.hbase.client.Connection;
33  import org.apache.hadoop.hbase.client.Get;
34  import org.apache.hadoop.hbase.client.Put;
35  import org.apache.hadoop.hbase.client.QuotaStatusCalls;
36  import org.apache.hadoop.hbase.client.Result;
37  import org.apache.hadoop.hbase.client.ResultScanner;
38  import org.apache.hadoop.hbase.client.Scan;
39  import org.apache.hadoop.hbase.client.Table;
40  import org.apache.hadoop.hbase.filter.CompareFilter;
41  import org.apache.hadoop.hbase.filter.Filter;
42  import org.apache.hadoop.hbase.filter.FilterList;
43  import org.apache.hadoop.hbase.filter.QualifierFilter;
44  import org.apache.hadoop.hbase.filter.RegexStringComparator;
45  import org.apache.hadoop.hbase.filter.RowFilter;
46  import org.apache.hadoop.hbase.protobuf.ProtobufUtil;
47  import org.apache.hadoop.hbase.protobuf.generated.TableProtos;
48  import org.apache.hadoop.hbase.protobuf.generated.HBaseProtos;
49  import org.apache.hadoop.hbase.protobuf.generated.QuotaProtos;
50  import org.apache.hadoop.hbase.protobuf.generated.QuotaProtos.GetQuotaStatesResponse;
51  import org.apache.hadoop.hbase.protobuf.generated.QuotaProtos.GetSpaceQuotaRegionSizesResponse;
52  import org.apache.hadoop.hbase.protobuf.generated.QuotaProtos.GetSpaceQuotaSnapshotsResponse;
53  import org.apache.hadoop.hbase.protobuf.generated.QuotaProtos.GetSpaceQuotaSnapshotsResponse.TableQuotaSnapshot;
54  import org.apache.hadoop.hbase.protobuf.generated.QuotaProtos.GetSpaceQuotaRegionSizesResponse.RegionSizes;
55  import org.apache.hadoop.hbase.protobuf.generated.QuotaProtos.Quotas;
56  import org.apache.hadoop.hbase.protobuf.generated.QuotaProtos.SpaceQuota;
57  import org.apache.hadoop.hbase.ipc.RpcControllerFactory;
58  import org.apache.hadoop.hbase.util.Bytes;
59  import org.apache.hadoop.hbase.util.Strings;
60  
61  import com.google.protobuf.ByteString;
62  import com.google.protobuf.HBaseZeroCopyByteString;
63  import com.google.protobuf.InvalidProtocolBufferException;
64  
65  /**
66   * Helper class to interact with the quota table.
67   * 
68   * <pre>
69   *     ROW-KEY      FAM/QUAL        DATA
70   *   n.&lt;namespace&gt; q:s         &lt;global-quotas&gt;
71   *   t.&lt;namespace&gt; u:p        &lt;namespace-quota policy&gt;
72   *   t.&lt;table&gt;     q:s         &lt;global-quotas&gt;
73   *   t.&lt;table&gt;     u:p        &lt;table-quota policy&gt;
74   *   u.&lt;user&gt;      q:s         &lt;global-quotas&gt;
75   *   u.&lt;user&gt;      q:s.&lt;table&gt; &lt;table-quotas&gt;
76   *   u.&lt;user&gt;      q:s.&lt;ns&gt;:   &lt;namespace-quotas&gt;
77   * </pre>
78   */
79  @InterfaceAudience.Private
80  @InterfaceStability.Evolving
81  public class QuotaTableUtil {
82    private static final Log LOG = LogFactory.getLog(QuotaTableUtil.class);
83  
84    /** System table for quotas */
85    public static final TableName QUOTA_TABLE_NAME = TableName.valueOf(
86      NamespaceDescriptor.SYSTEM_NAMESPACE_NAME_STR, "quota");
87  
88    protected static final byte[] QUOTA_FAMILY_INFO = Bytes.toBytes("q");
89    protected static final byte[] QUOTA_FAMILY_USAGE = Bytes.toBytes("u");
90    protected static final byte[] QUOTA_QUALIFIER_SETTINGS = Bytes.toBytes("s");
91    protected static final byte[] QUOTA_QUALIFIER_SETTINGS_PREFIX = Bytes.toBytes("s.");
92    protected static final byte[] QUOTA_QUALIFIER_POLICY = Bytes.toBytes("p");
93    protected static final String QUOTA_POLICY_COLUMN =
94      Bytes.toString(QUOTA_FAMILY_USAGE) + ":" + Bytes.toString(QUOTA_QUALIFIER_POLICY);
95    protected static final byte[] QUOTA_USER_ROW_KEY_PREFIX = Bytes.toBytes("u.");
96    protected static final byte[] QUOTA_TABLE_ROW_KEY_PREFIX = Bytes.toBytes("t.");
97    protected static final byte[] QUOTA_NAMESPACE_ROW_KEY_PREFIX = Bytes.toBytes("n.");
98  
99    /*
100    * ========================================================================= Quota "settings"
101    * helpers
102    */
103   public static Quotas getTableQuota(final Connection connection, final TableName table)
104       throws IOException {
105     return getQuotas(connection, getTableRowKey(table));
106   }
107 
108   public static Quotas getNamespaceQuota(final Connection connection, final String namespace)
109       throws IOException {
110     return getQuotas(connection, getNamespaceRowKey(namespace));
111   }
112 
113   public static Quotas getUserQuota(final Connection connection, final String user)
114       throws IOException {
115     return getQuotas(connection, getUserRowKey(user));
116   }
117 
118   public static Quotas getUserQuota(final Connection connection, final String user,
119       final TableName table) throws IOException {
120     return getQuotas(connection, getUserRowKey(user), getSettingsQualifierForUserTable(table));
121   }
122 
123   public static Quotas getUserQuota(final Connection connection, final String user,
124       final String namespace) throws IOException {
125     return getQuotas(connection, getUserRowKey(user),
126       getSettingsQualifierForUserNamespace(namespace));
127   }
128 
129   private static Quotas getQuotas(final Connection connection, final byte[] rowKey)
130       throws IOException {
131     return getQuotas(connection, rowKey, QUOTA_QUALIFIER_SETTINGS);
132   }
133 
134   private static Quotas getQuotas(final Connection connection, final byte[] rowKey,
135       final byte[] qualifier) throws IOException {
136     Get get = new Get(rowKey);
137     get.addColumn(QUOTA_FAMILY_INFO, qualifier);
138     Result result = doGet(connection, get);
139     if (result.isEmpty()) {
140       return null;
141     }
142     return quotasFromData(result.getValue(QUOTA_FAMILY_INFO, qualifier));
143   }
144 
145   public static Get makeGetForTableQuotas(final TableName table) {
146     Get get = new Get(getTableRowKey(table));
147     get.addFamily(QUOTA_FAMILY_INFO);
148     return get;
149   }
150 
151   public static Get makeGetForNamespaceQuotas(final String namespace) {
152     Get get = new Get(getNamespaceRowKey(namespace));
153     get.addFamily(QUOTA_FAMILY_INFO);
154     return get;
155   }
156 
157   public static Get makeGetForUserQuotas(final String user, final Iterable<TableName> tables,
158       final Iterable<String> namespaces) {
159     Get get = new Get(getUserRowKey(user));
160     get.addColumn(QUOTA_FAMILY_INFO, QUOTA_QUALIFIER_SETTINGS);
161     for (final TableName table : tables) {
162       get.addColumn(QUOTA_FAMILY_INFO, getSettingsQualifierForUserTable(table));
163     }
164     for (final String ns : namespaces) {
165       get.addColumn(QUOTA_FAMILY_INFO, getSettingsQualifierForUserNamespace(ns));
166     }
167     return get;
168   }
169 
170   public static Scan makeScan(final QuotaFilter filter) {
171     Scan scan = new Scan();
172     scan.addFamily(QUOTA_FAMILY_INFO);
173     if (filter != null && !filter.isNull()) {
174       scan.setFilter(makeFilter(filter));
175     }
176     return scan;
177   }
178 
179   /**
180    * converts quotafilter to serializeable filterlists.
181    */
182   public static Filter makeFilter(final QuotaFilter filter) {
183     FilterList filterList = new FilterList(FilterList.Operator.MUST_PASS_ALL);
184     if (!Strings.isEmpty(filter.getUserFilter())) {
185       FilterList userFilters = new FilterList(FilterList.Operator.MUST_PASS_ONE);
186       boolean hasFilter = false;
187 
188       if (!Strings.isEmpty(filter.getNamespaceFilter())) {
189         FilterList nsFilters = new FilterList(FilterList.Operator.MUST_PASS_ALL);
190         nsFilters.addFilter(new RowFilter(CompareFilter.CompareOp.EQUAL, new RegexStringComparator(
191             getUserRowKeyRegex(filter.getUserFilter()), 0)));
192         nsFilters.addFilter(new QualifierFilter(CompareFilter.CompareOp.EQUAL,
193             new RegexStringComparator(getSettingsQualifierRegexForUserNamespace(filter
194                 .getNamespaceFilter()), 0)));
195         userFilters.addFilter(nsFilters);
196         hasFilter = true;
197       }
198       if (!Strings.isEmpty(filter.getTableFilter())) {
199         FilterList tableFilters = new FilterList(FilterList.Operator.MUST_PASS_ALL);
200         tableFilters.addFilter(new RowFilter(CompareFilter.CompareOp.EQUAL,
201             new RegexStringComparator(getUserRowKeyRegex(filter.getUserFilter()), 0)));
202         tableFilters.addFilter(new QualifierFilter(CompareFilter.CompareOp.EQUAL,
203             new RegexStringComparator(
204                 getSettingsQualifierRegexForUserTable(filter.getTableFilter()), 0)));
205         userFilters.addFilter(tableFilters);
206         hasFilter = true;
207       }
208       if (!hasFilter) {
209         userFilters.addFilter(new RowFilter(CompareFilter.CompareOp.EQUAL,
210             new RegexStringComparator(getUserRowKeyRegex(filter.getUserFilter()), 0)));
211       }
212 
213       filterList.addFilter(userFilters);
214     } else if (!Strings.isEmpty(filter.getTableFilter())) {
215       filterList.addFilter(new RowFilter(CompareFilter.CompareOp.EQUAL, new RegexStringComparator(
216           getTableRowKeyRegex(filter.getTableFilter()), 0)));
217     } else if (!Strings.isEmpty(filter.getNamespaceFilter())) {
218       filterList.addFilter(new RowFilter(CompareFilter.CompareOp.EQUAL, new RegexStringComparator(
219           getNamespaceRowKeyRegex(filter.getNamespaceFilter()), 0)));
220     }
221     return filterList;
222   }
223 
224   /**
225    * Creates a {@link Scan} which returns only quota snapshots from the quota table.
226    */
227   public static Scan makeQuotaSnapshotScan() {
228     Scan s = new Scan();
229     // Limit to "u:v" column
230     s.addColumn(QUOTA_FAMILY_USAGE, QUOTA_QUALIFIER_POLICY);
231     // Limit rowspace to the "t:" prefix
232     s.setRowPrefixFilter(QUOTA_TABLE_ROW_KEY_PREFIX);
233     return s;
234   }
235 
236   /**
237    * Fetches all {@link SpaceQuotaSnapshot} objects from the {@code hbase:quota} table.
238    *
239    * @param conn The HBase connection
240    * @return A map of table names and their computed snapshot.
241    */
242   public static Map<TableName,SpaceQuotaSnapshot> getSnapshots(Connection conn) throws IOException {
243     Map<TableName,SpaceQuotaSnapshot> snapshots = new HashMap<>();
244     try (Table quotaTable = conn.getTable(QUOTA_TABLE_NAME);
245         ResultScanner rs = quotaTable.getScanner(makeQuotaSnapshotScan())) {
246       for (Result r : rs) {
247         extractQuotaSnapshot(r, snapshots);
248       }
249     }
250     return snapshots;
251   }
252 
253   /**
254    * Extracts the {@link SpaceViolationPolicy} and {@link TableName} from the provided
255    * {@link Result} and adds them to the given {@link Map}. If the result does not contain
256    * the expected information or the serialized policy in the value is invalid, this method
257    * will throw an {@link IllegalArgumentException}.
258    *
259    * @param result A row from the quota table.
260    * @param snapshots A map of snapshots to add the result of this method into.
261    */
262   public static void extractQuotaSnapshot(
263       Result result, Map<TableName,SpaceQuotaSnapshot> snapshots) {
264     byte[] row = Objects.requireNonNull(result).getRow();
265     if (row == null) {
266       throw new IllegalArgumentException("Provided result had a null row");
267     }
268     final TableName targetTableName = getTableFromRowKey(row);
269     Cell c = result.getColumnLatestCell(QUOTA_FAMILY_USAGE, QUOTA_QUALIFIER_POLICY);
270     if (c == null) {
271       throw new IllegalArgumentException("Result did not contain the expected column "
272           + QUOTA_POLICY_COLUMN + ", " + result.toString());
273     }
274     ByteString buffer = HBaseZeroCopyByteString.wrap(
275         c.getValueArray(), c.getValueOffset(), c.getValueLength());
276     try {
277       QuotaProtos.SpaceQuotaSnapshot snapshot = QuotaProtos.SpaceQuotaSnapshot.parseFrom(buffer);
278       snapshots.put(targetTableName, SpaceQuotaSnapshot.toSpaceQuotaSnapshot(snapshot));
279     } catch (InvalidProtocolBufferException e) {
280       throw new IllegalArgumentException(
281           "Result did not contain a valid SpaceQuota protocol buffer message", e);
282     }
283   }
284 
285   public static interface UserQuotasVisitor {
286     void visitUserQuotas(final String userName, final Quotas quotas) throws IOException;
287 
288     void visitUserQuotas(final String userName, final TableName table, final Quotas quotas)
289         throws IOException;
290 
291     void visitUserQuotas(final String userName, final String namespace, final Quotas quotas)
292         throws IOException;
293   }
294 
295   public static interface TableQuotasVisitor {
296     void visitTableQuotas(final TableName tableName, final Quotas quotas) throws IOException;
297   }
298 
299   public static interface NamespaceQuotasVisitor {
300     void visitNamespaceQuotas(final String namespace, final Quotas quotas) throws IOException;
301   }
302 
303   public static interface QuotasVisitor extends UserQuotasVisitor, TableQuotasVisitor,
304       NamespaceQuotasVisitor {
305   }
306 
307   public static void parseResult(final Result result, final QuotasVisitor visitor)
308       throws IOException {
309     byte[] row = result.getRow();
310     if (isNamespaceRowKey(row)) {
311       parseNamespaceResult(result, visitor);
312     } else if (isTableRowKey(row)) {
313       parseTableResult(result, visitor);
314     } else if (isUserRowKey(row)) {
315       parseUserResult(result, visitor);
316     } else {
317       LOG.warn("unexpected row-key: " + Bytes.toString(row));
318     }
319   }
320 
321   public static void
322       parseNamespaceResult(final Result result, final NamespaceQuotasVisitor visitor)
323           throws IOException {
324     String namespace = getNamespaceFromRowKey(result.getRow());
325     parseNamespaceResult(namespace, result, visitor);
326   }
327 
328   protected static void parseNamespaceResult(final String namespace, final Result result,
329       final NamespaceQuotasVisitor visitor) throws IOException {
330     byte[] data = result.getValue(QUOTA_FAMILY_INFO, QUOTA_QUALIFIER_SETTINGS);
331     if (data != null) {
332       Quotas quotas = quotasFromData(data);
333       visitor.visitNamespaceQuotas(namespace, quotas);
334     }
335   }
336 
337   public static void parseTableResult(final Result result, final TableQuotasVisitor visitor)
338       throws IOException {
339     TableName table = getTableFromRowKey(result.getRow());
340     parseTableResult(table, result, visitor);
341   }
342 
343   protected static void parseTableResult(final TableName table, final Result result,
344       final TableQuotasVisitor visitor) throws IOException {
345     byte[] data = result.getValue(QUOTA_FAMILY_INFO, QUOTA_QUALIFIER_SETTINGS);
346     if (data != null) {
347       Quotas quotas = quotasFromData(data);
348       visitor.visitTableQuotas(table, quotas);
349     }
350   }
351 
352   public static void parseUserResult(final Result result, final UserQuotasVisitor visitor)
353       throws IOException {
354     String userName = getUserFromRowKey(result.getRow());
355     parseUserResult(userName, result, visitor);
356   }
357 
358   protected static void parseUserResult(final String userName, final Result result,
359       final UserQuotasVisitor visitor) throws IOException {
360     Map<byte[], byte[]> familyMap = result.getFamilyMap(QUOTA_FAMILY_INFO);
361     if (familyMap == null || familyMap.isEmpty()) return;
362 
363     for (Map.Entry<byte[], byte[]> entry : familyMap.entrySet()) {
364       Quotas quotas = quotasFromData(entry.getValue());
365       if (Bytes.startsWith(entry.getKey(), QUOTA_QUALIFIER_SETTINGS_PREFIX)) {
366         String name = Bytes.toString(entry.getKey(), QUOTA_QUALIFIER_SETTINGS_PREFIX.length);
367         if (name.charAt(name.length() - 1) == TableName.NAMESPACE_DELIM) {
368           String namespace = name.substring(0, name.length() - 1);
369           visitor.visitUserQuotas(userName, namespace, quotas);
370         } else {
371           TableName table = TableName.valueOf(name);
372           visitor.visitUserQuotas(userName, table, quotas);
373         }
374       } else if (Bytes.equals(entry.getKey(), QUOTA_QUALIFIER_SETTINGS)) {
375         visitor.visitUserQuotas(userName, quotas);
376       }
377     }
378   }
379 
380   /**
381    * Creates a {@link Put} to store the given {@code snapshot} for the given {@code tableName} in
382    * the quota table.
383    */
384   public static Put putSpaceSnapshot(TableName tableName, SpaceQuotaSnapshot snapshot) {
385     Put p = new Put(getTableRowKey(tableName));
386     p.addColumn(QUOTA_FAMILY_USAGE, QUOTA_QUALIFIER_POLICY, SpaceQuotaSnapshot.toProtoSnapshot(snapshot).toByteArray());
387     return p;
388   }
389 
390 
391   /* =========================================================================
392    *  Space quota status RPC helpers
393    */
394   /**
395    * Fetches the table sizes on the filesystem as tracked by the HBase Master.
396    */
397   public static Map<TableName,Long> getMasterReportedTableSizes(
398       Connection conn) throws IOException {
399     if (!(conn instanceof ClusterConnection)) {
400       throw new IllegalArgumentException("Expected a ClusterConnection");
401     }
402     ClusterConnection clusterConn = (ClusterConnection) conn;
403     GetSpaceQuotaRegionSizesResponse response = QuotaStatusCalls.getMasterRegionSizes(
404         clusterConn, 0);
405     Map<TableName,Long> tableSizes = new HashMap<>();
406     for (RegionSizes sizes : response.getSizesList()) {
407       TableName tn = ProtobufUtil.toTableName(sizes.getTableName());
408       tableSizes.put(tn, sizes.getSize());
409     }
410     return tableSizes;
411   }
412 
413   /**
414    * Fetches the observed {@link SpaceQuotaSnapshot}s observed by a RegionServer.
415    */
416   public static Map<TableName,SpaceQuotaSnapshot> getRegionServerQuotaSnapshots(
417       Connection conn, ServerName regionServer) throws IOException {
418     if (!(conn instanceof ClusterConnection)) {
419       throw new IllegalArgumentException("Expected a ClusterConnection");
420     }
421     ClusterConnection clusterConn = (ClusterConnection) conn;
422     GetSpaceQuotaSnapshotsResponse response = QuotaStatusCalls.getRegionServerQuotaSnapshot(
423         clusterConn, 0, regionServer);
424     Map<TableName,SpaceQuotaSnapshot> snapshots = new HashMap<>();
425     for (TableQuotaSnapshot snapshot : response.getSnapshotsList()) {
426       snapshots.put(
427           ProtobufUtil.toTableName(snapshot.getTableName()),
428           SpaceQuotaSnapshot.toSpaceQuotaSnapshot(snapshot.getSnapshot()));
429     }
430     return snapshots;
431   }
432 
433   /**
434    * Returns the Master's view of a quota on the given {@code tableName} or null if the
435    * Master has no quota information on that table.
436    */
437   public static SpaceQuotaSnapshot getCurrentSnapshot(
438       Connection conn, TableName tn) throws IOException {
439     if (!(conn instanceof ClusterConnection)) {
440       throw new IllegalArgumentException("Expected a ClusterConnection");
441     }
442     ClusterConnection clusterConn = (ClusterConnection) conn;
443     GetQuotaStatesResponse resp = QuotaStatusCalls.getMasterQuotaStates(clusterConn, 0);
444     TableProtos.TableName protoTableName = ProtobufUtil.toProtoTableName(tn);
445     for (GetQuotaStatesResponse.TableQuotaSnapshot tableSnapshot : resp.getTableSnapshotsList()) {
446       if (protoTableName.equals(tableSnapshot.getTableName())) {
447         return SpaceQuotaSnapshot.toSpaceQuotaSnapshot(tableSnapshot.getSnapshot());
448       }
449     }
450     return null;
451   }
452 
453   /**
454    * Returns the Master's view of a quota on the given {@code namespace} or null if the
455    * Master has no quota information on that namespace.
456    */
457   public static SpaceQuotaSnapshot getCurrentSnapshot(
458       Connection conn, String namespace) throws IOException {
459     if (!(conn instanceof ClusterConnection)) {
460       throw new IllegalArgumentException("Expected a ClusterConnection");
461     }
462     ClusterConnection clusterConn = (ClusterConnection) conn;
463     GetQuotaStatesResponse resp = QuotaStatusCalls.getMasterQuotaStates(clusterConn, 0);
464     for (GetQuotaStatesResponse.NamespaceQuotaSnapshot nsSnapshot : resp.getNsSnapshotsList()) {
465       if (namespace.equals(nsSnapshot.getNamespace())) {
466         return SpaceQuotaSnapshot.toSpaceQuotaSnapshot(nsSnapshot.getSnapshot());
467       }
468     }
469     return null;
470   }
471 
472   /* =========================================================================
473    *  Quotas protobuf helpers
474    */
475   protected static Quotas quotasFromData(final byte[] data) throws IOException {
476     return quotasFromData(data, 0, data.length);
477   }
478 
479   protected static Quotas quotasFromData(
480       final byte[] data, int offset, int length) throws IOException {
481     int magicLen = ProtobufUtil.lengthOfPBMagic();
482     if (!ProtobufUtil.isPBMagicPrefix(data, offset, magicLen)) {
483       throw new IOException("Missing pb magic prefix");
484     }
485     return Quotas.parseFrom(new ByteArrayInputStream(data, offset + magicLen, length - magicLen));
486   }
487 
488   protected static byte[] quotasToData(final Quotas data) throws IOException {
489     ByteArrayOutputStream stream = new ByteArrayOutputStream();
490     stream.write(ProtobufUtil.PB_MAGIC);
491     data.writeTo(stream);
492     return stream.toByteArray();
493   }
494 
495   public static boolean isEmptyQuota(final Quotas quotas) {
496     boolean hasSettings = false;
497     hasSettings |= quotas.hasThrottle();
498     hasSettings |= quotas.hasBypassGlobals();
499     // Only when there is a space quota, make sure there's actually both fields provided
500     // Otherwise, it's a noop.
501     if (quotas.hasSpace()) {
502       hasSettings |= (quotas.getSpace().hasSoftLimit() && quotas.getSpace().hasViolationPolicy());
503     }
504     return !hasSettings;
505   }
506 
507   /*
508    * ========================================================================= HTable helpers
509    */
510   protected static Result doGet(final Connection connection, final Get get) throws IOException {
511     try (Table table = connection.getTable(QUOTA_TABLE_NAME)) {
512       return table.get(get);
513     }
514   }
515 
516   protected static Result[] doGet(final Connection connection, final List<Get> gets)
517       throws IOException {
518     try (Table table = connection.getTable(QUOTA_TABLE_NAME)) {
519       return table.get(gets);
520     }
521   }
522 
523   /*
524    * ========================================================================= Quota table row key
525    * helpers
526    */
527   protected static byte[] getUserRowKey(final String user) {
528     return Bytes.add(QUOTA_USER_ROW_KEY_PREFIX, Bytes.toBytes(user));
529   }
530 
531   protected static byte[] getTableRowKey(final TableName table) {
532     return Bytes.add(QUOTA_TABLE_ROW_KEY_PREFIX, table.getName());
533   }
534 
535   protected static byte[] getNamespaceRowKey(final String namespace) {
536     return Bytes.add(QUOTA_NAMESPACE_ROW_KEY_PREFIX, Bytes.toBytes(namespace));
537   }
538 
539   protected static byte[] getSettingsQualifierForUserTable(final TableName tableName) {
540     return Bytes.add(QUOTA_QUALIFIER_SETTINGS_PREFIX, tableName.getName());
541   }
542 
543   protected static byte[] getSettingsQualifierForUserNamespace(final String namespace) {
544     return Bytes.add(QUOTA_QUALIFIER_SETTINGS_PREFIX,
545       Bytes.toBytes(namespace + TableName.NAMESPACE_DELIM));
546   }
547 
548   protected static String getUserRowKeyRegex(final String user) {
549     return getRowKeyRegEx(QUOTA_USER_ROW_KEY_PREFIX, user);
550   }
551 
552   protected static String getTableRowKeyRegex(final String table) {
553     return getRowKeyRegEx(QUOTA_TABLE_ROW_KEY_PREFIX, table);
554   }
555 
556   protected static String getNamespaceRowKeyRegex(final String namespace) {
557     return getRowKeyRegEx(QUOTA_NAMESPACE_ROW_KEY_PREFIX, namespace);
558   }
559 
560   private static String getRowKeyRegEx(final byte[] prefix, final String regex) {
561     return '^' + Pattern.quote(Bytes.toString(prefix)) + regex + '$';
562   }
563 
564   protected static String getSettingsQualifierRegexForUserTable(final String table) {
565     return '^' + Pattern.quote(Bytes.toString(QUOTA_QUALIFIER_SETTINGS_PREFIX)) + table + "(?<!"
566         + Pattern.quote(Character.toString(TableName.NAMESPACE_DELIM)) + ")$";
567   }
568 
569   protected static String getSettingsQualifierRegexForUserNamespace(final String namespace) {
570     return '^' + Pattern.quote(Bytes.toString(QUOTA_QUALIFIER_SETTINGS_PREFIX)) + namespace
571         + Pattern.quote(Character.toString(TableName.NAMESPACE_DELIM)) + '$';
572   }
573 
574   protected static boolean isNamespaceRowKey(final byte[] key) {
575     return Bytes.startsWith(key, QUOTA_NAMESPACE_ROW_KEY_PREFIX);
576   }
577 
578   protected static String getNamespaceFromRowKey(final byte[] key) {
579     return Bytes.toString(key, QUOTA_NAMESPACE_ROW_KEY_PREFIX.length);
580   }
581 
582   protected static boolean isTableRowKey(final byte[] key) {
583     return Bytes.startsWith(key, QUOTA_TABLE_ROW_KEY_PREFIX);
584   }
585 
586   protected static TableName getTableFromRowKey(final byte[] key) {
587     return TableName.valueOf(Bytes.toString(key, QUOTA_TABLE_ROW_KEY_PREFIX.length));
588   }
589 
590   protected static boolean isUserRowKey(final byte[] key) {
591     return Bytes.startsWith(key, QUOTA_USER_ROW_KEY_PREFIX);
592   }
593 
594   protected static String getUserFromRowKey(final byte[] key) {
595     return Bytes.toString(key, QUOTA_USER_ROW_KEY_PREFIX.length);
596   }
597 
598   protected static SpaceQuota getProtoViolationPolicy(SpaceViolationPolicy policy) {
599     return SpaceQuota.newBuilder()
600           .setViolationPolicy(ProtobufUtil.toProtoViolationPolicy(policy))
601           .build();
602   }
603 
604   protected static SpaceViolationPolicy getViolationPolicy(SpaceQuota proto) {
605     if (!proto.hasViolationPolicy()) {
606       throw new IllegalArgumentException("Protobuf SpaceQuota does not have violation policy.");
607     }
608     return ProtobufUtil.toViolationPolicy(proto.getViolationPolicy());
609   }
610 }