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