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.ByteArrayInputStream; 022import java.io.ByteArrayOutputStream; 023import java.io.IOException; 024import java.util.Collection; 025import java.util.HashMap; 026import java.util.List; 027import java.util.Map; 028import java.util.Objects; 029import java.util.regex.Pattern; 030 031import org.apache.commons.lang3.StringUtils; 032import org.apache.hadoop.hbase.Cell; 033import org.apache.hadoop.hbase.CellScanner; 034import org.apache.hadoop.hbase.CompareOperator; 035import org.apache.hadoop.hbase.NamespaceDescriptor; 036import org.apache.hadoop.hbase.ServerName; 037import org.apache.hadoop.hbase.TableName; 038import org.apache.yetus.audience.InterfaceAudience; 039import org.apache.yetus.audience.InterfaceStability; 040import org.slf4j.Logger; 041import org.slf4j.LoggerFactory; 042import org.apache.hadoop.hbase.client.ClusterConnection; 043import org.apache.hadoop.hbase.client.Connection; 044import org.apache.hadoop.hbase.client.Get; 045import org.apache.hadoop.hbase.client.Put; 046import org.apache.hadoop.hbase.client.QuotaStatusCalls; 047import org.apache.hadoop.hbase.client.Result; 048import org.apache.hadoop.hbase.client.ResultScanner; 049import org.apache.hadoop.hbase.client.Scan; 050import org.apache.hadoop.hbase.client.Table; 051import org.apache.hadoop.hbase.filter.ColumnPrefixFilter; 052import org.apache.hadoop.hbase.filter.Filter; 053import org.apache.hadoop.hbase.filter.FilterList; 054import org.apache.hadoop.hbase.filter.QualifierFilter; 055import org.apache.hadoop.hbase.filter.RegexStringComparator; 056import org.apache.hadoop.hbase.filter.RowFilter; 057import org.apache.hadoop.hbase.protobuf.ProtobufMagic; 058import org.apache.hbase.thirdparty.com.google.protobuf.ByteString; 059import org.apache.hbase.thirdparty.com.google.protobuf.InvalidProtocolBufferException; 060import org.apache.hbase.thirdparty.com.google.protobuf.TextFormat; 061import org.apache.hbase.thirdparty.com.google.protobuf.UnsafeByteOperations; 062import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil; 063import org.apache.hadoop.hbase.shaded.protobuf.generated.HBaseProtos; 064import org.apache.hadoop.hbase.shaded.protobuf.generated.QuotaProtos; 065import org.apache.hadoop.hbase.shaded.protobuf.generated.QuotaProtos.GetQuotaStatesResponse; 066import org.apache.hadoop.hbase.shaded.protobuf.generated.QuotaProtos.GetSpaceQuotaRegionSizesResponse; 067import org.apache.hadoop.hbase.shaded.protobuf.generated.QuotaProtos.GetSpaceQuotaSnapshotsResponse; 068import org.apache.hadoop.hbase.shaded.protobuf.generated.QuotaProtos.GetSpaceQuotaSnapshotsResponse.TableQuotaSnapshot; 069import org.apache.hadoop.hbase.shaded.protobuf.generated.QuotaProtos.GetSpaceQuotaRegionSizesResponse.RegionSizes; 070import org.apache.hadoop.hbase.shaded.protobuf.generated.QuotaProtos.Quotas; 071import org.apache.hadoop.hbase.shaded.protobuf.generated.QuotaProtos.SpaceQuota; 072import org.apache.hadoop.hbase.util.Bytes; 073 074/** 075 * Helper class to interact with the quota table. 076 * <table> 077 * <tr><th>ROW-KEY</th><th>FAM/QUAL</th><th>DATA</th></tr> 078 * <tr><td>n.<namespace></td><td>q:s</td><td><global-quotas></td></tr> 079 * <tr><td>n.<namespace></td><td>u:p</td><td><namespace-quota policy></td></tr> 080 * <tr><td>n.<namespace></td><td>u:s</td><td><SpaceQuotaSnapshot></td></tr> 081 * <tr><td>t.<table></td><td>q:s</td><td><global-quotas></td></tr> 082 * <tr><td>t.<table></td><td>u:p</td><td><table-quota policy></td></tr> 083 * <tr><td>t.<table></td><td>u:ss.<snapshot name></td><td><SpaceQuotaSnapshot></td></tr> 084 * <tr><td>u.<user></td><td>q:s</td><td><global-quotas></td></tr> 085 * <tr><td>u.<user></td><td>q:s.<table></td><td><table-quotas></td></tr> 086 * <tr><td>u.<user></td><td>q:s.<ns></td><td><namespace-quotas></td></tr> 087 * </table> 088 */ 089@InterfaceAudience.Private 090@InterfaceStability.Evolving 091public class QuotaTableUtil { 092 private static final Logger LOG = LoggerFactory.getLogger(QuotaTableUtil.class); 093 094 /** System table for quotas */ 095 public static final TableName QUOTA_TABLE_NAME = 096 TableName.valueOf(NamespaceDescriptor.SYSTEM_NAMESPACE_NAME_STR, "quota"); 097 098 protected static final byte[] QUOTA_FAMILY_INFO = Bytes.toBytes("q"); 099 protected static final byte[] QUOTA_FAMILY_USAGE = Bytes.toBytes("u"); 100 protected static final byte[] QUOTA_QUALIFIER_SETTINGS = Bytes.toBytes("s"); 101 protected static final byte[] QUOTA_QUALIFIER_SETTINGS_PREFIX = Bytes.toBytes("s."); 102 protected static final byte[] QUOTA_QUALIFIER_POLICY = Bytes.toBytes("p"); 103 protected static final byte[] QUOTA_SNAPSHOT_SIZE_QUALIFIER = Bytes.toBytes("ss"); 104 protected static final String QUOTA_POLICY_COLUMN = 105 Bytes.toString(QUOTA_FAMILY_USAGE) + ":" + Bytes.toString(QUOTA_QUALIFIER_POLICY); 106 protected static final byte[] QUOTA_USER_ROW_KEY_PREFIX = Bytes.toBytes("u."); 107 protected static final byte[] QUOTA_TABLE_ROW_KEY_PREFIX = Bytes.toBytes("t."); 108 protected static final byte[] QUOTA_NAMESPACE_ROW_KEY_PREFIX = Bytes.toBytes("n."); 109 110 /* ========================================================================= 111 * Quota "settings" helpers 112 */ 113 public static Quotas getTableQuota(final Connection connection, final TableName table) 114 throws IOException { 115 return getQuotas(connection, getTableRowKey(table)); 116 } 117 118 public static Quotas getNamespaceQuota(final Connection connection, final String namespace) 119 throws IOException { 120 return getQuotas(connection, getNamespaceRowKey(namespace)); 121 } 122 123 public static Quotas getUserQuota(final Connection connection, final String user) 124 throws IOException { 125 return getQuotas(connection, getUserRowKey(user)); 126 } 127 128 public static Quotas getUserQuota(final Connection connection, final String user, 129 final TableName table) throws IOException { 130 return getQuotas(connection, getUserRowKey(user), getSettingsQualifierForUserTable(table)); 131 } 132 133 public static Quotas getUserQuota(final Connection connection, final String user, 134 final String namespace) throws IOException { 135 return getQuotas(connection, getUserRowKey(user), 136 getSettingsQualifierForUserNamespace(namespace)); 137 } 138 139 private static Quotas getQuotas(final Connection connection, final byte[] rowKey) 140 throws IOException { 141 return getQuotas(connection, rowKey, QUOTA_QUALIFIER_SETTINGS); 142 } 143 144 private static Quotas getQuotas(final Connection connection, final byte[] rowKey, 145 final byte[] qualifier) throws IOException { 146 Get get = new Get(rowKey); 147 get.addColumn(QUOTA_FAMILY_INFO, qualifier); 148 Result result = doGet(connection, get); 149 if (result.isEmpty()) { 150 return null; 151 } 152 return quotasFromData(result.getValue(QUOTA_FAMILY_INFO, qualifier)); 153 } 154 155 public static Get makeGetForTableQuotas(final TableName table) { 156 Get get = new Get(getTableRowKey(table)); 157 get.addFamily(QUOTA_FAMILY_INFO); 158 return get; 159 } 160 161 public static Get makeGetForNamespaceQuotas(final String namespace) { 162 Get get = new Get(getNamespaceRowKey(namespace)); 163 get.addFamily(QUOTA_FAMILY_INFO); 164 return get; 165 } 166 167 public static Get makeGetForUserQuotas(final String user, final Iterable<TableName> tables, 168 final Iterable<String> namespaces) { 169 Get get = new Get(getUserRowKey(user)); 170 get.addColumn(QUOTA_FAMILY_INFO, QUOTA_QUALIFIER_SETTINGS); 171 for (final TableName table: tables) { 172 get.addColumn(QUOTA_FAMILY_INFO, getSettingsQualifierForUserTable(table)); 173 } 174 for (final String ns: namespaces) { 175 get.addColumn(QUOTA_FAMILY_INFO, getSettingsQualifierForUserNamespace(ns)); 176 } 177 return get; 178 } 179 180 public static Scan makeScan(final QuotaFilter filter) { 181 Scan scan = new Scan(); 182 scan.addFamily(QUOTA_FAMILY_INFO); 183 if (filter != null && !filter.isNull()) { 184 scan.setFilter(makeFilter(filter)); 185 } 186 return scan; 187 } 188 189 /** 190 * converts quotafilter to serializeable filterlists. 191 */ 192 public static Filter makeFilter(final QuotaFilter filter) { 193 FilterList filterList = new FilterList(FilterList.Operator.MUST_PASS_ALL); 194 if (StringUtils.isNotEmpty(filter.getUserFilter())) { 195 FilterList userFilters = new FilterList(FilterList.Operator.MUST_PASS_ONE); 196 boolean hasFilter = false; 197 198 if (StringUtils.isNotEmpty(filter.getNamespaceFilter())) { 199 FilterList nsFilters = new FilterList(FilterList.Operator.MUST_PASS_ALL); 200 nsFilters.addFilter(new RowFilter(CompareOperator.EQUAL, 201 new RegexStringComparator(getUserRowKeyRegex(filter.getUserFilter()), 0))); 202 nsFilters.addFilter(new QualifierFilter(CompareOperator.EQUAL, 203 new RegexStringComparator( 204 getSettingsQualifierRegexForUserNamespace(filter.getNamespaceFilter()), 0))); 205 userFilters.addFilter(nsFilters); 206 hasFilter = true; 207 } 208 if (StringUtils.isNotEmpty(filter.getTableFilter())) { 209 FilterList tableFilters = new FilterList(FilterList.Operator.MUST_PASS_ALL); 210 tableFilters.addFilter(new RowFilter(CompareOperator.EQUAL, 211 new RegexStringComparator(getUserRowKeyRegex(filter.getUserFilter()), 0))); 212 tableFilters.addFilter(new QualifierFilter(CompareOperator.EQUAL, 213 new RegexStringComparator( 214 getSettingsQualifierRegexForUserTable(filter.getTableFilter()), 0))); 215 userFilters.addFilter(tableFilters); 216 hasFilter = true; 217 } 218 if (!hasFilter) { 219 userFilters.addFilter(new RowFilter(CompareOperator.EQUAL, 220 new RegexStringComparator(getUserRowKeyRegex(filter.getUserFilter()), 0))); 221 } 222 223 filterList.addFilter(userFilters); 224 } else if (StringUtils.isNotEmpty(filter.getTableFilter())) { 225 filterList.addFilter(new RowFilter(CompareOperator.EQUAL, 226 new RegexStringComparator(getTableRowKeyRegex(filter.getTableFilter()), 0))); 227 } else if (StringUtils.isNotEmpty(filter.getNamespaceFilter())) { 228 filterList.addFilter(new RowFilter(CompareOperator.EQUAL, 229 new RegexStringComparator(getNamespaceRowKeyRegex(filter.getNamespaceFilter()), 0))); 230 } 231 return filterList; 232 } 233 234 /** 235 * Creates a {@link Scan} which returns only quota snapshots from the quota table. 236 */ 237 public static Scan makeQuotaSnapshotScan() { 238 return makeQuotaSnapshotScanForTable(null); 239 } 240 241 /** 242 * Fetches all {@link SpaceQuotaSnapshot} objects from the {@code hbase:quota} table. 243 * 244 * @param conn The HBase connection 245 * @return A map of table names and their computed snapshot. 246 */ 247 public static Map<TableName,SpaceQuotaSnapshot> getSnapshots(Connection conn) throws IOException { 248 Map<TableName,SpaceQuotaSnapshot> snapshots = new HashMap<>(); 249 try (Table quotaTable = conn.getTable(QUOTA_TABLE_NAME); 250 ResultScanner rs = quotaTable.getScanner(makeQuotaSnapshotScan())) { 251 for (Result r : rs) { 252 extractQuotaSnapshot(r, snapshots); 253 } 254 } 255 return snapshots; 256 } 257 258 /** 259 * Creates a {@link Scan} which returns only {@link SpaceQuotaSnapshot} from the quota table for a 260 * specific table. 261 * @param tn Optionally, a table name to limit the scan's rowkey space. Can be null. 262 */ 263 public static Scan makeQuotaSnapshotScanForTable(TableName tn) { 264 Scan s = new Scan(); 265 // Limit to "u:v" column 266 s.addColumn(QUOTA_FAMILY_USAGE, QUOTA_QUALIFIER_POLICY); 267 if (null == tn) { 268 s.setRowPrefixFilter(QUOTA_TABLE_ROW_KEY_PREFIX); 269 } else { 270 byte[] row = getTableRowKey(tn); 271 // Limit rowspace to the "t:" prefix 272 s.withStartRow(row, true).withStopRow(row, true); 273 } 274 return s; 275 } 276 277 /** 278 * Extracts the {@link SpaceViolationPolicy} and {@link TableName} from the provided 279 * {@link Result} and adds them to the given {@link Map}. If the result does not contain 280 * the expected information or the serialized policy in the value is invalid, this method 281 * will throw an {@link IllegalArgumentException}. 282 * 283 * @param result A row from the quota table. 284 * @param snapshots A map of snapshots to add the result of this method into. 285 */ 286 public static void extractQuotaSnapshot( 287 Result result, Map<TableName,SpaceQuotaSnapshot> snapshots) { 288 byte[] row = Objects.requireNonNull(result).getRow(); 289 if (row == null) { 290 throw new IllegalArgumentException("Provided result had a null row"); 291 } 292 final TableName targetTableName = getTableFromRowKey(row); 293 Cell c = result.getColumnLatestCell(QUOTA_FAMILY_USAGE, QUOTA_QUALIFIER_POLICY); 294 if (c == null) { 295 throw new IllegalArgumentException("Result did not contain the expected column " 296 + QUOTA_POLICY_COLUMN + ", " + result.toString()); 297 } 298 ByteString buffer = UnsafeByteOperations.unsafeWrap( 299 c.getValueArray(), c.getValueOffset(), c.getValueLength()); 300 try { 301 QuotaProtos.SpaceQuotaSnapshot snapshot = QuotaProtos.SpaceQuotaSnapshot.parseFrom(buffer); 302 snapshots.put(targetTableName, SpaceQuotaSnapshot.toSpaceQuotaSnapshot(snapshot)); 303 } catch (InvalidProtocolBufferException e) { 304 throw new IllegalArgumentException( 305 "Result did not contain a valid SpaceQuota protocol buffer message", e); 306 } 307 } 308 309 public static interface UserQuotasVisitor { 310 void visitUserQuotas(final String userName, final Quotas quotas) 311 throws IOException; 312 void visitUserQuotas(final String userName, final TableName table, final Quotas quotas) 313 throws IOException; 314 void visitUserQuotas(final String userName, final String namespace, final Quotas quotas) 315 throws IOException; 316 } 317 318 public static interface TableQuotasVisitor { 319 void visitTableQuotas(final TableName tableName, final Quotas quotas) 320 throws IOException; 321 } 322 323 public static interface NamespaceQuotasVisitor { 324 void visitNamespaceQuotas(final String namespace, final Quotas quotas) 325 throws IOException; 326 } 327 328 public static interface QuotasVisitor extends UserQuotasVisitor, 329 TableQuotasVisitor, NamespaceQuotasVisitor { 330 } 331 332 public static void parseResult(final Result result, final QuotasVisitor visitor) 333 throws IOException { 334 byte[] row = result.getRow(); 335 if (isNamespaceRowKey(row)) { 336 parseNamespaceResult(result, visitor); 337 } else if (isTableRowKey(row)) { 338 parseTableResult(result, visitor); 339 } else if (isUserRowKey(row)) { 340 parseUserResult(result, visitor); 341 } else { 342 LOG.warn("unexpected row-key: " + Bytes.toString(row)); 343 } 344 } 345 346 public static void parseResultToCollection(final Result result, 347 Collection<QuotaSettings> quotaSettings) throws IOException { 348 349 QuotaTableUtil.parseResult(result, new QuotaTableUtil.QuotasVisitor() { 350 @Override 351 public void visitUserQuotas(String userName, Quotas quotas) { 352 quotaSettings.addAll(QuotaSettingsFactory.fromUserQuotas(userName, quotas)); 353 } 354 355 @Override 356 public void visitUserQuotas(String userName, TableName table, Quotas quotas) { 357 quotaSettings.addAll(QuotaSettingsFactory.fromUserQuotas(userName, table, quotas)); 358 } 359 360 @Override 361 public void visitUserQuotas(String userName, String namespace, Quotas quotas) { 362 quotaSettings.addAll(QuotaSettingsFactory.fromUserQuotas(userName, namespace, quotas)); 363 } 364 365 @Override 366 public void visitTableQuotas(TableName tableName, Quotas quotas) { 367 quotaSettings.addAll(QuotaSettingsFactory.fromTableQuotas(tableName, quotas)); 368 } 369 370 @Override 371 public void visitNamespaceQuotas(String namespace, Quotas quotas) { 372 quotaSettings.addAll(QuotaSettingsFactory.fromNamespaceQuotas(namespace, quotas)); 373 } 374 }); 375 } 376 377 public static void parseNamespaceResult(final Result result, 378 final NamespaceQuotasVisitor visitor) throws IOException { 379 String namespace = getNamespaceFromRowKey(result.getRow()); 380 parseNamespaceResult(namespace, result, visitor); 381 } 382 383 protected static void parseNamespaceResult(final String namespace, final Result result, 384 final NamespaceQuotasVisitor visitor) throws IOException { 385 byte[] data = result.getValue(QUOTA_FAMILY_INFO, QUOTA_QUALIFIER_SETTINGS); 386 if (data != null) { 387 Quotas quotas = quotasFromData(data); 388 visitor.visitNamespaceQuotas(namespace, quotas); 389 } 390 } 391 392 public static void parseTableResult(final Result result, final TableQuotasVisitor visitor) 393 throws IOException { 394 TableName table = getTableFromRowKey(result.getRow()); 395 parseTableResult(table, result, visitor); 396 } 397 398 protected static void parseTableResult(final TableName table, final Result result, 399 final TableQuotasVisitor visitor) throws IOException { 400 byte[] data = result.getValue(QUOTA_FAMILY_INFO, QUOTA_QUALIFIER_SETTINGS); 401 if (data != null) { 402 Quotas quotas = quotasFromData(data); 403 visitor.visitTableQuotas(table, quotas); 404 } 405 } 406 407 public static void parseUserResult(final Result result, final UserQuotasVisitor visitor) 408 throws IOException { 409 String userName = getUserFromRowKey(result.getRow()); 410 parseUserResult(userName, result, visitor); 411 } 412 413 protected static void parseUserResult(final String userName, final Result result, 414 final UserQuotasVisitor visitor) throws IOException { 415 Map<byte[], byte[]> familyMap = result.getFamilyMap(QUOTA_FAMILY_INFO); 416 if (familyMap == null || familyMap.isEmpty()) return; 417 418 for (Map.Entry<byte[], byte[]> entry: familyMap.entrySet()) { 419 Quotas quotas = quotasFromData(entry.getValue()); 420 if (Bytes.startsWith(entry.getKey(), QUOTA_QUALIFIER_SETTINGS_PREFIX)) { 421 String name = Bytes.toString(entry.getKey(), QUOTA_QUALIFIER_SETTINGS_PREFIX.length); 422 if (name.charAt(name.length() - 1) == TableName.NAMESPACE_DELIM) { 423 String namespace = name.substring(0, name.length() - 1); 424 visitor.visitUserQuotas(userName, namespace, quotas); 425 } else { 426 TableName table = TableName.valueOf(name); 427 visitor.visitUserQuotas(userName, table, quotas); 428 } 429 } else if (Bytes.equals(entry.getKey(), QUOTA_QUALIFIER_SETTINGS)) { 430 visitor.visitUserQuotas(userName, quotas); 431 } 432 } 433 } 434 435 /** 436 * Creates a {@link Put} to store the given {@code snapshot} for the given {@code tableName} in 437 * the quota table. 438 */ 439 static Put createPutForSpaceSnapshot(TableName tableName, SpaceQuotaSnapshot snapshot) { 440 Put p = new Put(getTableRowKey(tableName)); 441 p.addColumn( 442 QUOTA_FAMILY_USAGE, QUOTA_QUALIFIER_POLICY, 443 SpaceQuotaSnapshot.toProtoSnapshot(snapshot).toByteArray()); 444 return p; 445 } 446 447 /** 448 * Creates a {@link Get} for the HBase snapshot's size against the given table. 449 */ 450 static Get makeGetForSnapshotSize(TableName tn, String snapshot) { 451 Get g = new Get(Bytes.add(QUOTA_TABLE_ROW_KEY_PREFIX, Bytes.toBytes(tn.toString()))); 452 g.addColumn( 453 QUOTA_FAMILY_USAGE, 454 Bytes.add(QUOTA_SNAPSHOT_SIZE_QUALIFIER, Bytes.toBytes(snapshot))); 455 return g; 456 } 457 458 /** 459 * Creates a {@link Put} to persist the current size of the {@code snapshot} with respect to 460 * the given {@code table}. 461 */ 462 static Put createPutForSnapshotSize(TableName tableName, String snapshot, long size) { 463 // We just need a pb message with some `long usage`, so we can just reuse the 464 // SpaceQuotaSnapshot message instead of creating a new one. 465 Put p = new Put(getTableRowKey(tableName)); 466 p.addColumn(QUOTA_FAMILY_USAGE, getSnapshotSizeQualifier(snapshot), 467 org.apache.hadoop.hbase.shaded.protobuf.generated.QuotaProtos.SpaceQuotaSnapshot 468 .newBuilder().setQuotaUsage(size).build().toByteArray()); 469 return p; 470 } 471 472 /** 473 * Creates a {@code Put} for the namespace's total snapshot size. 474 */ 475 static Put createPutForNamespaceSnapshotSize(String namespace, long size) { 476 Put p = new Put(getNamespaceRowKey(namespace)); 477 p.addColumn(QUOTA_FAMILY_USAGE, QUOTA_SNAPSHOT_SIZE_QUALIFIER, 478 org.apache.hadoop.hbase.shaded.protobuf.generated.QuotaProtos.SpaceQuotaSnapshot 479 .newBuilder().setQuotaUsage(size).build().toByteArray()); 480 return p; 481 } 482 483 /** 484 * Fetches the computed size of all snapshots against tables in a namespace for space quotas. 485 */ 486 static long getNamespaceSnapshotSize( 487 Connection conn, String namespace) throws IOException { 488 try (Table quotaTable = conn.getTable(QuotaTableUtil.QUOTA_TABLE_NAME)) { 489 Result r = quotaTable.get(createGetNamespaceSnapshotSize(namespace)); 490 if (r.isEmpty()) { 491 return 0L; 492 } 493 r.advance(); 494 return parseSnapshotSize(r.current()); 495 } catch (InvalidProtocolBufferException e) { 496 throw new IOException("Could not parse snapshot size value for namespace " + namespace, e); 497 } 498 } 499 500 /** 501 * Creates a {@code Get} to fetch the namespace's total snapshot size. 502 */ 503 static Get createGetNamespaceSnapshotSize(String namespace) { 504 Get g = new Get(getNamespaceRowKey(namespace)); 505 g.addColumn(QUOTA_FAMILY_USAGE, QUOTA_SNAPSHOT_SIZE_QUALIFIER); 506 return g; 507 } 508 509 /** 510 * Parses the snapshot size from the given Cell's value. 511 */ 512 static long parseSnapshotSize(Cell c) throws InvalidProtocolBufferException { 513 ByteString bs = UnsafeByteOperations.unsafeWrap( 514 c.getValueArray(), c.getValueOffset(), c.getValueLength()); 515 return QuotaProtos.SpaceQuotaSnapshot.parseFrom(bs).getQuotaUsage(); 516 } 517 518 static Scan createScanForSpaceSnapshotSizes() { 519 return createScanForSpaceSnapshotSizes(null); 520 } 521 522 static Scan createScanForSpaceSnapshotSizes(TableName table) { 523 Scan s = new Scan(); 524 if (null == table) { 525 // Read all tables, just look at the row prefix 526 s.setRowPrefixFilter(QUOTA_TABLE_ROW_KEY_PREFIX); 527 } else { 528 // Fetch the exact row for the table 529 byte[] rowkey = getTableRowKey(table); 530 // Fetch just this one row 531 s.withStartRow(rowkey).withStopRow(rowkey, true); 532 } 533 534 // Just the usage family and only the snapshot size qualifiers 535 return s.addFamily(QUOTA_FAMILY_USAGE).setFilter( 536 new ColumnPrefixFilter(QUOTA_SNAPSHOT_SIZE_QUALIFIER)); 537 } 538 539 /** 540 * Fetches any persisted HBase snapshot sizes stored in the quota table. The sizes here are 541 * computed relative to the table which the snapshot was created from. A snapshot's size will 542 * not include the size of files which the table still refers. These sizes, in bytes, are what 543 * is used internally to compute quota violation for tables and namespaces. 544 * 545 * @return A map of snapshot name to size in bytes per space quota computations 546 */ 547 public static Map<String,Long> getObservedSnapshotSizes(Connection conn) throws IOException { 548 try (Table quotaTable = conn.getTable(QUOTA_TABLE_NAME); 549 ResultScanner rs = quotaTable.getScanner(createScanForSpaceSnapshotSizes())) { 550 final Map<String,Long> snapshotSizes = new HashMap<>(); 551 for (Result r : rs) { 552 CellScanner cs = r.cellScanner(); 553 while (cs.advance()) { 554 Cell c = cs.current(); 555 final String snapshot = extractSnapshotNameFromSizeCell(c); 556 final long size = parseSnapshotSize(c); 557 snapshotSizes.put(snapshot, size); 558 } 559 } 560 return snapshotSizes; 561 } 562 } 563 564 /* ========================================================================= 565 * Space quota status RPC helpers 566 */ 567 /** 568 * Fetches the table sizes on the filesystem as tracked by the HBase Master. 569 */ 570 public static Map<TableName,Long> getMasterReportedTableSizes( 571 Connection conn) throws IOException { 572 if (!(conn instanceof ClusterConnection)) { 573 throw new IllegalArgumentException("Expected a ClusterConnection"); 574 } 575 ClusterConnection clusterConn = (ClusterConnection) conn; 576 GetSpaceQuotaRegionSizesResponse response = QuotaStatusCalls.getMasterRegionSizes( 577 clusterConn, 0); 578 Map<TableName,Long> tableSizes = new HashMap<>(); 579 for (RegionSizes sizes : response.getSizesList()) { 580 TableName tn = ProtobufUtil.toTableName(sizes.getTableName()); 581 tableSizes.put(tn, sizes.getSize()); 582 } 583 return tableSizes; 584 } 585 586 /** 587 * Fetches the observed {@link SpaceQuotaSnapshot}s observed by a RegionServer. 588 */ 589 public static Map<TableName,SpaceQuotaSnapshot> getRegionServerQuotaSnapshots( 590 Connection conn, ServerName regionServer) throws IOException { 591 if (!(conn instanceof ClusterConnection)) { 592 throw new IllegalArgumentException("Expected a ClusterConnection"); 593 } 594 ClusterConnection clusterConn = (ClusterConnection) conn; 595 GetSpaceQuotaSnapshotsResponse response = QuotaStatusCalls.getRegionServerQuotaSnapshot( 596 clusterConn, 0, regionServer); 597 Map<TableName,SpaceQuotaSnapshot> snapshots = new HashMap<>(); 598 for (TableQuotaSnapshot snapshot : response.getSnapshotsList()) { 599 snapshots.put( 600 ProtobufUtil.toTableName(snapshot.getTableName()), 601 SpaceQuotaSnapshot.toSpaceQuotaSnapshot(snapshot.getSnapshot())); 602 } 603 return snapshots; 604 } 605 606 /** 607 * Returns the Master's view of a quota on the given {@code tableName} or null if the 608 * Master has no quota information on that table. 609 */ 610 public static SpaceQuotaSnapshot getCurrentSnapshot( 611 Connection conn, TableName tn) throws IOException { 612 if (!(conn instanceof ClusterConnection)) { 613 throw new IllegalArgumentException("Expected a ClusterConnection"); 614 } 615 ClusterConnection clusterConn = (ClusterConnection) conn; 616 GetQuotaStatesResponse resp = QuotaStatusCalls.getMasterQuotaStates(clusterConn, 0); 617 HBaseProtos.TableName protoTableName = ProtobufUtil.toProtoTableName(tn); 618 for (GetQuotaStatesResponse.TableQuotaSnapshot tableSnapshot : resp.getTableSnapshotsList()) { 619 if (protoTableName.equals(tableSnapshot.getTableName())) { 620 return SpaceQuotaSnapshot.toSpaceQuotaSnapshot(tableSnapshot.getSnapshot()); 621 } 622 } 623 return null; 624 } 625 626 /** 627 * Returns the Master's view of a quota on the given {@code namespace} or null if the 628 * Master has no quota information on that namespace. 629 */ 630 public static SpaceQuotaSnapshot getCurrentSnapshot( 631 Connection conn, String namespace) throws IOException { 632 if (!(conn instanceof ClusterConnection)) { 633 throw new IllegalArgumentException("Expected a ClusterConnection"); 634 } 635 ClusterConnection clusterConn = (ClusterConnection) conn; 636 GetQuotaStatesResponse resp = QuotaStatusCalls.getMasterQuotaStates(clusterConn, 0); 637 for (GetQuotaStatesResponse.NamespaceQuotaSnapshot nsSnapshot : resp.getNsSnapshotsList()) { 638 if (namespace.equals(nsSnapshot.getNamespace())) { 639 return SpaceQuotaSnapshot.toSpaceQuotaSnapshot(nsSnapshot.getSnapshot()); 640 } 641 } 642 return null; 643 } 644 645 /* ========================================================================= 646 * Quotas protobuf helpers 647 */ 648 protected static Quotas quotasFromData(final byte[] data) throws IOException { 649 return quotasFromData(data, 0, data.length); 650 } 651 652 protected static Quotas quotasFromData( 653 final byte[] data, int offset, int length) throws IOException { 654 int magicLen = ProtobufMagic.lengthOfPBMagic(); 655 if (!ProtobufMagic.isPBMagicPrefix(data, offset, magicLen)) { 656 throw new IOException("Missing pb magic prefix"); 657 } 658 return Quotas.parseFrom(new ByteArrayInputStream(data, offset + magicLen, length - magicLen)); 659 } 660 661 protected static byte[] quotasToData(final Quotas data) throws IOException { 662 ByteArrayOutputStream stream = new ByteArrayOutputStream(); 663 stream.write(ProtobufMagic.PB_MAGIC); 664 data.writeTo(stream); 665 return stream.toByteArray(); 666 } 667 668 public static boolean isEmptyQuota(final Quotas quotas) { 669 boolean hasSettings = false; 670 hasSettings |= quotas.hasThrottle(); 671 hasSettings |= quotas.hasBypassGlobals(); 672 // Only when there is a space quota, make sure there's actually both fields provided 673 // Otherwise, it's a noop. 674 if (quotas.hasSpace()) { 675 hasSettings |= (quotas.getSpace().hasSoftLimit() && quotas.getSpace().hasViolationPolicy()); 676 } 677 return !hasSettings; 678 } 679 680 /* ========================================================================= 681 * HTable helpers 682 */ 683 protected static Result doGet(final Connection connection, final Get get) 684 throws IOException { 685 try (Table table = connection.getTable(QUOTA_TABLE_NAME)) { 686 return table.get(get); 687 } 688 } 689 690 protected static Result[] doGet(final Connection connection, final List<Get> gets) 691 throws IOException { 692 try (Table table = connection.getTable(QUOTA_TABLE_NAME)) { 693 return table.get(gets); 694 } 695 } 696 697 /* ========================================================================= 698 * Quota table row key helpers 699 */ 700 protected static byte[] getUserRowKey(final String user) { 701 return Bytes.add(QUOTA_USER_ROW_KEY_PREFIX, Bytes.toBytes(user)); 702 } 703 704 protected static byte[] getTableRowKey(final TableName table) { 705 return Bytes.add(QUOTA_TABLE_ROW_KEY_PREFIX, table.getName()); 706 } 707 708 protected static byte[] getNamespaceRowKey(final String namespace) { 709 return Bytes.add(QUOTA_NAMESPACE_ROW_KEY_PREFIX, Bytes.toBytes(namespace)); 710 } 711 712 protected static byte[] getSettingsQualifierForUserTable(final TableName tableName) { 713 return Bytes.add(QUOTA_QUALIFIER_SETTINGS_PREFIX, tableName.getName()); 714 } 715 716 protected static byte[] getSettingsQualifierForUserNamespace(final String namespace) { 717 return Bytes.add(QUOTA_QUALIFIER_SETTINGS_PREFIX, 718 Bytes.toBytes(namespace + TableName.NAMESPACE_DELIM)); 719 } 720 721 protected static String getUserRowKeyRegex(final String user) { 722 return getRowKeyRegEx(QUOTA_USER_ROW_KEY_PREFIX, user); 723 } 724 725 protected static String getTableRowKeyRegex(final String table) { 726 return getRowKeyRegEx(QUOTA_TABLE_ROW_KEY_PREFIX, table); 727 } 728 729 protected static String getNamespaceRowKeyRegex(final String namespace) { 730 return getRowKeyRegEx(QUOTA_NAMESPACE_ROW_KEY_PREFIX, namespace); 731 } 732 733 private static String getRowKeyRegEx(final byte[] prefix, final String regex) { 734 return '^' + Pattern.quote(Bytes.toString(prefix)) + regex + '$'; 735 } 736 737 protected static String getSettingsQualifierRegexForUserTable(final String table) { 738 return '^' + Pattern.quote(Bytes.toString(QUOTA_QUALIFIER_SETTINGS_PREFIX)) + 739 table + "(?<!" + Pattern.quote(Character.toString(TableName.NAMESPACE_DELIM)) + ")$"; 740 } 741 742 protected static String getSettingsQualifierRegexForUserNamespace(final String namespace) { 743 return '^' + Pattern.quote(Bytes.toString(QUOTA_QUALIFIER_SETTINGS_PREFIX)) + 744 namespace + Pattern.quote(Character.toString(TableName.NAMESPACE_DELIM)) + '$'; 745 } 746 747 protected static boolean isNamespaceRowKey(final byte[] key) { 748 return Bytes.startsWith(key, QUOTA_NAMESPACE_ROW_KEY_PREFIX); 749 } 750 751 protected static String getNamespaceFromRowKey(final byte[] key) { 752 return Bytes.toString(key, QUOTA_NAMESPACE_ROW_KEY_PREFIX.length); 753 } 754 755 protected static boolean isTableRowKey(final byte[] key) { 756 return Bytes.startsWith(key, QUOTA_TABLE_ROW_KEY_PREFIX); 757 } 758 759 protected static TableName getTableFromRowKey(final byte[] key) { 760 return TableName.valueOf(Bytes.toString(key, QUOTA_TABLE_ROW_KEY_PREFIX.length)); 761 } 762 763 protected static boolean isUserRowKey(final byte[] key) { 764 return Bytes.startsWith(key, QUOTA_USER_ROW_KEY_PREFIX); 765 } 766 767 protected static String getUserFromRowKey(final byte[] key) { 768 return Bytes.toString(key, QUOTA_USER_ROW_KEY_PREFIX.length); 769 } 770 771 protected static SpaceQuota getProtoViolationPolicy(SpaceViolationPolicy policy) { 772 return SpaceQuota.newBuilder() 773 .setViolationPolicy(ProtobufUtil.toProtoViolationPolicy(policy)) 774 .build(); 775 } 776 777 protected static SpaceViolationPolicy getViolationPolicy(SpaceQuota proto) { 778 if (!proto.hasViolationPolicy()) { 779 throw new IllegalArgumentException("Protobuf SpaceQuota does not have violation policy."); 780 } 781 return ProtobufUtil.toViolationPolicy(proto.getViolationPolicy()); 782 } 783 784 protected static byte[] getSnapshotSizeQualifier(String snapshotName) { 785 return Bytes.add(QUOTA_SNAPSHOT_SIZE_QUALIFIER, Bytes.toBytes(snapshotName)); 786 } 787 788 protected static String extractSnapshotNameFromSizeCell(Cell c) { 789 return Bytes.toString( 790 c.getQualifierArray(), c.getQualifierOffset() + QUOTA_SNAPSHOT_SIZE_QUALIFIER.length, 791 c.getQualifierLength() - QUOTA_SNAPSHOT_SIZE_QUALIFIER.length); 792 } 793 794 protected static long extractSnapshotSize( 795 byte[] data, int offset, int length) throws InvalidProtocolBufferException { 796 ByteString byteStr = UnsafeByteOperations.unsafeWrap(data, offset, length); 797 return org.apache.hadoop.hbase.shaded.protobuf.generated.QuotaProtos.SpaceQuotaSnapshot 798 .parseFrom(byteStr).getQuotaUsage(); 799 } 800}