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 */ 018package org.apache.hadoop.hbase.quotas; 019 020import static org.junit.Assert.assertEquals; 021import static org.junit.Assert.assertTrue; 022import static org.junit.Assert.fail; 023 024import java.io.IOException; 025import java.util.ArrayList; 026import java.util.Collection; 027import java.util.Collections; 028import java.util.List; 029import java.util.Map.Entry; 030import java.util.Objects; 031import java.util.Set; 032import java.util.concurrent.atomic.AtomicLong; 033import org.apache.hadoop.conf.Configuration; 034import org.apache.hadoop.fs.FileSystem; 035import org.apache.hadoop.fs.Path; 036import org.apache.hadoop.hbase.HBaseTestingUtility; 037import org.apache.hadoop.hbase.HConstants; 038import org.apache.hadoop.hbase.MiniHBaseCluster; 039import org.apache.hadoop.hbase.NamespaceDescriptor; 040import org.apache.hadoop.hbase.TableName; 041import org.apache.hadoop.hbase.TableNotEnabledException; 042import org.apache.hadoop.hbase.Waiter.Predicate; 043import org.apache.hadoop.hbase.client.Admin; 044import org.apache.hadoop.hbase.client.Append; 045import org.apache.hadoop.hbase.client.ClientServiceCallable; 046import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder; 047import org.apache.hadoop.hbase.client.Connection; 048import org.apache.hadoop.hbase.client.Delete; 049import org.apache.hadoop.hbase.client.Increment; 050import org.apache.hadoop.hbase.client.Mutation; 051import org.apache.hadoop.hbase.client.Put; 052import org.apache.hadoop.hbase.client.Result; 053import org.apache.hadoop.hbase.client.ResultScanner; 054import org.apache.hadoop.hbase.client.Scan; 055import org.apache.hadoop.hbase.client.SecureBulkLoadClient; 056import org.apache.hadoop.hbase.client.Table; 057import org.apache.hadoop.hbase.client.TableDescriptor; 058import org.apache.hadoop.hbase.client.TableDescriptorBuilder; 059import org.apache.hadoop.hbase.ipc.RpcControllerFactory; 060import org.apache.hadoop.hbase.regionserver.HRegion; 061import org.apache.hadoop.hbase.regionserver.HStore; 062import org.apache.hadoop.hbase.regionserver.HStoreFile; 063import org.apache.hadoop.hbase.regionserver.TestHRegionServerBulkLoad; 064import org.apache.hadoop.hbase.util.Bytes; 065import org.apache.hadoop.hbase.util.Pair; 066import org.apache.hadoop.util.StringUtils; 067import org.apache.yetus.audience.InterfaceAudience; 068import org.junit.rules.TestName; 069import org.slf4j.Logger; 070import org.slf4j.LoggerFactory; 071 072import org.apache.hbase.thirdparty.com.google.common.collect.HashMultimap; 073import org.apache.hbase.thirdparty.com.google.common.collect.Iterables; 074import org.apache.hbase.thirdparty.com.google.common.collect.Multimap; 075 076@InterfaceAudience.Private 077public class SpaceQuotaHelperForTests { 078 private static final Logger LOG = LoggerFactory.getLogger(SpaceQuotaHelperForTests.class); 079 080 public static final int SIZE_PER_VALUE = 256; 081 public static final String F1 = "f1"; 082 public static final long ONE_KILOBYTE = 1024L; 083 public static final long ONE_MEGABYTE = ONE_KILOBYTE * ONE_KILOBYTE; 084 public static final long ONE_GIGABYTE = ONE_MEGABYTE * ONE_KILOBYTE; 085 086 private final HBaseTestingUtility testUtil; 087 private final TestName testName; 088 private final AtomicLong counter; 089 private static final int NUM_RETRIES = 10; 090 091 public SpaceQuotaHelperForTests(HBaseTestingUtility testUtil, TestName testName, 092 AtomicLong counter) { 093 this.testUtil = Objects.requireNonNull(testUtil); 094 this.testName = Objects.requireNonNull(testName); 095 this.counter = Objects.requireNonNull(counter); 096 } 097 098 // 099 // Static helpers 100 // 101 102 static void updateConfigForQuotas(Configuration conf) { 103 // Increase the frequency of some of the chores for responsiveness of the test 104 conf.setInt(FileSystemUtilizationChore.FS_UTILIZATION_CHORE_DELAY_KEY, 1000); 105 conf.setInt(FileSystemUtilizationChore.FS_UTILIZATION_CHORE_PERIOD_KEY, 1000); 106 conf.setInt(QuotaObserverChore.QUOTA_OBSERVER_CHORE_DELAY_KEY, 1000); 107 conf.setInt(QuotaObserverChore.QUOTA_OBSERVER_CHORE_PERIOD_KEY, 1000); 108 conf.setInt(SpaceQuotaRefresherChore.POLICY_REFRESHER_CHORE_DELAY_KEY, 1000); 109 conf.setInt(SpaceQuotaRefresherChore.POLICY_REFRESHER_CHORE_PERIOD_KEY, 1000); 110 conf.setInt(SnapshotQuotaObserverChore.SNAPSHOT_QUOTA_CHORE_DELAY_KEY, 1000); 111 conf.setInt(SnapshotQuotaObserverChore.SNAPSHOT_QUOTA_CHORE_PERIOD_KEY, 1000); 112 conf.setInt(RegionSizeReportingChore.REGION_SIZE_REPORTING_CHORE_PERIOD_KEY, 1000); 113 conf.setInt(RegionSizeReportingChore.REGION_SIZE_REPORTING_CHORE_DELAY_KEY, 1000); 114 // The period at which we check for compacted files that should be deleted from HDFS 115 conf.setInt("hbase.hfile.compaction.discharger.interval", 5 * 1000); 116 conf.setBoolean(QuotaUtil.QUOTA_CONF_KEY, true); 117 } 118 119 // 120 // Helpers 121 // 122 123 /** 124 * Returns the number of quotas defined in the HBase quota table. 125 */ 126 long listNumDefinedQuotas(Connection conn) throws IOException { 127 QuotaRetriever scanner = QuotaRetriever.open(conn.getConfiguration()); 128 try { 129 return Iterables.size(scanner); 130 } finally { 131 if (scanner != null) { 132 scanner.close(); 133 } 134 } 135 } 136 137 /** 138 * Writes the given mutation into a table until it violates the given policy. Verifies that the 139 * policy has been violated & then returns the name of the table created & written into. 140 */ 141 TableName writeUntilViolationAndVerifyViolation(SpaceViolationPolicy policyToViolate, Mutation m) 142 throws Exception { 143 final TableName tn = writeUntilViolation(policyToViolate); 144 verifyViolation(policyToViolate, tn, m); 145 return tn; 146 } 147 148 /** 149 * Writes the given mutation into a table until it violates the given policy. Returns the name of 150 * the table created & written into. 151 */ 152 TableName writeUntilViolation(SpaceViolationPolicy policyToViolate) throws Exception { 153 TableName tn = createTableWithRegions(10); 154 setQuotaLimit(tn, policyToViolate, 2L); 155 // Write more data than should be allowed and flush it to disk 156 writeData(tn, 3L * SpaceQuotaHelperForTests.ONE_MEGABYTE); 157 158 // This should be sufficient time for the chores to run and see the change. 159 Thread.sleep(5000); 160 161 return tn; 162 } 163 164 TableName writeUntilViolationAndVerifyViolationInNamespace(String ns, 165 SpaceViolationPolicy policyToViolate, Mutation m) throws Exception { 166 final TableName tn = writeUntilViolationInNamespace(ns, policyToViolate); 167 verifyViolation(policyToViolate, tn, m); 168 return tn; 169 } 170 171 TableName writeUntilViolationInNamespace(String ns, SpaceViolationPolicy policyToViolate) 172 throws Exception { 173 TableName tn = createTableWithRegions(ns, 10); 174 175 setQuotaLimit(ns, policyToViolate, 4L); 176 177 // Write more data than should be allowed and flush it to disk 178 writeData(tn, 5L * SpaceQuotaHelperForTests.ONE_MEGABYTE); 179 180 // This should be sufficient time for the chores to run and see the change. 181 Thread.sleep(5000); 182 183 return tn; 184 } 185 186 /** 187 * Verifies that the given policy on the given table has been violated 188 */ 189 void verifyViolation(SpaceViolationPolicy policyToViolate, TableName tn, Mutation m) 190 throws Exception { 191 // But let's try a few times to get the exception before failing 192 boolean sawError = false; 193 String msg = ""; 194 for (int i = 0; i < NUM_RETRIES && !sawError; i++) { 195 try (Table table = testUtil.getConnection().getTable(tn)) { 196 if (m instanceof Put) { 197 table.put((Put) m); 198 } else if (m instanceof Delete) { 199 table.delete((Delete) m); 200 } else if (m instanceof Append) { 201 table.append((Append) m); 202 } else if (m instanceof Increment) { 203 table.increment((Increment) m); 204 } else { 205 fail( 206 "Failed to apply " + m.getClass().getSimpleName() + " to the table. Programming error"); 207 } 208 LOG.info("Did not reject the " + m.getClass().getSimpleName() + ", will sleep and retry"); 209 Thread.sleep(2000); 210 } catch (Exception e) { 211 msg = StringUtils.stringifyException(e); 212 if ( 213 (policyToViolate.equals(SpaceViolationPolicy.DISABLE) 214 && e instanceof TableNotEnabledException) || msg.contains(policyToViolate.name()) 215 ) { 216 LOG.info("Got the expected exception={}", msg); 217 sawError = true; 218 break; 219 } else { 220 LOG.warn("Did not get the expected exception, will sleep and retry", e); 221 Thread.sleep(2000); 222 } 223 } 224 } 225 if (!sawError) { 226 try (Table quotaTable = testUtil.getConnection().getTable(QuotaUtil.QUOTA_TABLE_NAME)) { 227 ResultScanner scanner = quotaTable.getScanner(new Scan()); 228 Result result = null; 229 LOG.info("Dumping contents of hbase:quota table"); 230 while ((result = scanner.next()) != null) { 231 LOG.info(Bytes.toString(result.getRow()) + " => " + result.toString()); 232 } 233 scanner.close(); 234 } 235 } else { 236 if (policyToViolate.equals(SpaceViolationPolicy.DISABLE)) { 237 assertTrue( 238 msg.contains("TableNotEnabledException") || msg.contains(policyToViolate.name())); 239 } else { 240 assertTrue("Expected exception message to contain the word '" + policyToViolate.name() 241 + "', but was " + msg, msg.contains(policyToViolate.name())); 242 } 243 } 244 assertTrue("Expected to see an exception writing data to a table exceeding its quota", 245 sawError); 246 } 247 248 /** 249 * Verifies that no policy has been violated on the given table 250 */ 251 void verifyNoViolation(TableName tn, Mutation m) throws Exception { 252 // But let's try a few times to write data before failing 253 boolean sawSuccess = false; 254 for (int i = 0; i < NUM_RETRIES && !sawSuccess; i++) { 255 try (Table table = testUtil.getConnection().getTable(tn)) { 256 if (m instanceof Put) { 257 table.put((Put) m); 258 } else if (m instanceof Delete) { 259 table.delete((Delete) m); 260 } else if (m instanceof Append) { 261 table.append((Append) m); 262 } else if (m instanceof Increment) { 263 table.increment((Increment) m); 264 } else { 265 fail("Failed to apply " + m.getClass().getSimpleName() + " to the table." 266 + " Programming error"); 267 } 268 sawSuccess = true; 269 } catch (Exception e) { 270 LOG.info("Rejected the " + m.getClass().getSimpleName() + ", will sleep and retry"); 271 Thread.sleep(2000); 272 } 273 } 274 if (!sawSuccess) { 275 try (Table quotaTable = testUtil.getConnection().getTable(QuotaUtil.QUOTA_TABLE_NAME)) { 276 ResultScanner scanner = quotaTable.getScanner(new Scan()); 277 Result result = null; 278 LOG.info("Dumping contents of hbase:quota table"); 279 while ((result = scanner.next()) != null) { 280 LOG.info(Bytes.toString(result.getRow()) + " => " + result.toString()); 281 } 282 scanner.close(); 283 } 284 } 285 assertTrue("Expected to succeed in writing data to a table not having quota ", sawSuccess); 286 } 287 288 /** 289 * Verifies that table usage snapshot exists for the table 290 */ 291 void verifyTableUsageSnapshotForSpaceQuotaExist(TableName tn) throws Exception { 292 boolean sawUsageSnapshot = false; 293 try (Table quotaTable = testUtil.getConnection().getTable(QuotaTableUtil.QUOTA_TABLE_NAME)) { 294 Scan s = QuotaTableUtil.makeQuotaSnapshotScanForTable(tn); 295 ResultScanner rs = quotaTable.getScanner(s); 296 sawUsageSnapshot = (rs.next() != null); 297 } 298 assertTrue("Expected to succeed in getting table usage snapshots for space quota", 299 sawUsageSnapshot); 300 } 301 302 /** 303 * Sets the given quota (policy & limit) on the passed table. 304 */ 305 void setQuotaLimit(final TableName tn, SpaceViolationPolicy policy, long sizeInMBs) 306 throws Exception { 307 final long sizeLimit = sizeInMBs * SpaceQuotaHelperForTests.ONE_MEGABYTE; 308 QuotaSettings settings = QuotaSettingsFactory.limitTableSpace(tn, sizeLimit, policy); 309 testUtil.getAdmin().setQuota(settings); 310 LOG.debug("Quota limit set for table = {}, limit = {}", tn, sizeLimit); 311 } 312 313 /** 314 * Sets the given quota (policy & limit) on the passed namespace. 315 */ 316 void setQuotaLimit(String ns, SpaceViolationPolicy policy, long sizeInMBs) throws Exception { 317 final long sizeLimit = sizeInMBs * SpaceQuotaHelperForTests.ONE_MEGABYTE; 318 QuotaSettings settings = QuotaSettingsFactory.limitNamespaceSpace(ns, sizeLimit, policy); 319 testUtil.getAdmin().setQuota(settings); 320 LOG.debug("Quota limit set for namespace = {}, limit = {}", ns, sizeLimit); 321 } 322 323 /** 324 * Removes the space quota from the given table 325 */ 326 void removeQuotaFromtable(final TableName tn) throws Exception { 327 QuotaSettings removeQuota = QuotaSettingsFactory.removeTableSpaceLimit(tn); 328 testUtil.getAdmin().setQuota(removeQuota); 329 LOG.debug("Space quota settings removed from the table ", tn); 330 } 331 332 /** 333 * @param tn the tablename 334 * @param numFiles number of files 335 * @param numRowsPerFile number of rows per file 336 * @return a clientServiceCallable which can be used with the Caller factory for bulk load 337 * @throws Exception when failed to get connection, table or preparation of the bulk load 338 */ 339 ClientServiceCallable<Void> generateFileToLoad(TableName tn, int numFiles, int numRowsPerFile) 340 throws Exception { 341 Connection conn = testUtil.getConnection(); 342 FileSystem fs = testUtil.getTestFileSystem(); 343 Configuration conf = testUtil.getConfiguration(); 344 Path baseDir = new Path(fs.getHomeDirectory(), testName.getMethodName() + "_files"); 345 fs.mkdirs(baseDir); 346 final List<Pair<byte[], String>> famPaths = new ArrayList<Pair<byte[], String>>(); 347 for (int i = 1; i <= numFiles; i++) { 348 Path hfile = new Path(baseDir, "file" + i); 349 TestHRegionServerBulkLoad.createHFile(fs, hfile, Bytes.toBytes(SpaceQuotaHelperForTests.F1), 350 Bytes.toBytes("to"), Bytes.toBytes("reject"), numRowsPerFile); 351 famPaths.add(new Pair<>(Bytes.toBytes(SpaceQuotaHelperForTests.F1), hfile.toString())); 352 } 353 354 // bulk load HFiles 355 Table table = conn.getTable(tn); 356 final String bulkToken = new SecureBulkLoadClient(conf, table).prepareBulkLoad(conn); 357 return new ClientServiceCallable<Void>(conn, tn, Bytes.toBytes("row"), 358 new RpcControllerFactory(conf).newController(), HConstants.PRIORITY_UNSET, 359 Collections.emptyMap()) { 360 @Override 361 public Void rpcCall() throws Exception { 362 SecureBulkLoadClient secureClient = null; 363 byte[] regionName = getLocation().getRegionInfo().getRegionName(); 364 try (Table table = conn.getTable(getTableName())) { 365 secureClient = new SecureBulkLoadClient(conf, table); 366 secureClient.secureBulkLoadHFiles(getStub(), famPaths, regionName, true, null, bulkToken); 367 } 368 return null; 369 } 370 }; 371 } 372 373 /** 374 * Bulk-loads a number of files with a number of rows to the given table. 375 */ 376 // ClientServiceCallable<Boolean> generateFileToLoad( 377 // TableName tn, int numFiles, int numRowsPerFile) throws Exception { 378 // Connection conn = testUtil.getConnection(); 379 // FileSystem fs = testUtil.getTestFileSystem(); 380 // Configuration conf = testUtil.getConfiguration(); 381 // Path baseDir = new Path(fs.getHomeDirectory(), testName.getMethodName() + "_files"); 382 // fs.mkdirs(baseDir); 383 // final List<Pair<byte[], String>> famPaths = new ArrayList<>(); 384 // for (int i = 1; i <= numFiles; i++) { 385 // Path hfile = new Path(baseDir, "file" + i); 386 // TestHRegionServerBulkLoad.createHFile( 387 // fs, hfile, Bytes.toBytes(SpaceQuotaHelperForTests.F1), Bytes.toBytes("my"), 388 // Bytes.toBytes("file"), numRowsPerFile); 389 // famPaths.add(new Pair<>(Bytes.toBytes(SpaceQuotaHelperForTests.F1), hfile.toString())); 390 // } 391 // 392 // // bulk load HFiles 393 // Table table = conn.getTable(tn); 394 // final String bulkToken = new SecureBulkLoadClient(conf, table).prepareBulkLoad(conn); 395 // return new ClientServiceCallable<Boolean>( 396 // conn, tn, Bytes.toBytes("row"), new RpcControllerFactory(conf).newController(), 397 // HConstants.PRIORITY_UNSET) { 398 // @Override 399 // public Boolean rpcCall() throws Exception { 400 // SecureBulkLoadClient secureClient = null; 401 // byte[] regionName = getLocation().getRegion().getRegionName(); 402 // try (Table table = conn.getTable(getTableName())) { 403 // secureClient = new SecureBulkLoadClient(conf, table); 404 // return secureClient.secureBulkLoadHFiles(getStub(), famPaths, regionName, 405 // true, null, bulkToken); 406 // } 407 // } 408 // }; 409 // } 410 411 /** 412 * Removes the space quota from the given namespace 413 */ 414 void removeQuotaFromNamespace(String ns) throws Exception { 415 QuotaSettings removeQuota = QuotaSettingsFactory.removeNamespaceSpaceLimit(ns); 416 Admin admin = testUtil.getAdmin(); 417 admin.setQuota(removeQuota); 418 LOG.debug("Space quota settings removed from the namespace ", ns); 419 } 420 421 /** 422 * Removes all quotas defined in the HBase quota table. 423 */ 424 void removeAllQuotas() throws Exception { 425 final Connection conn = testUtil.getConnection(); 426 removeAllQuotas(conn); 427 assertEquals(0, listNumDefinedQuotas(conn)); 428 } 429 430 /** 431 * Removes all quotas defined in the HBase quota table. 432 */ 433 void removeAllQuotas(Connection conn) throws IOException { 434 // Wait for the quota table to be created 435 if (!conn.getAdmin().tableExists(QuotaUtil.QUOTA_TABLE_NAME)) { 436 waitForQuotaTable(conn); 437 } else { 438 // Or, clean up any quotas from previous test runs. 439 QuotaRetriever scanner = QuotaRetriever.open(conn.getConfiguration()); 440 try { 441 for (QuotaSettings quotaSettings : scanner) { 442 final String namespace = quotaSettings.getNamespace(); 443 final TableName tableName = quotaSettings.getTableName(); 444 final String userName = quotaSettings.getUserName(); 445 if (namespace != null) { 446 LOG.debug("Deleting quota for namespace: " + namespace); 447 QuotaUtil.deleteNamespaceQuota(conn, namespace); 448 } else if (tableName != null) { 449 LOG.debug("Deleting quota for table: " + tableName); 450 QuotaUtil.deleteTableQuota(conn, tableName); 451 } else if (userName != null) { 452 LOG.debug("Deleting quota for user: " + userName); 453 QuotaUtil.deleteUserQuota(conn, userName); 454 } 455 } 456 } finally { 457 if (scanner != null) { 458 scanner.close(); 459 } 460 } 461 } 462 } 463 464 QuotaSettings getTableSpaceQuota(Connection conn, TableName tn) throws IOException { 465 try (QuotaRetriever scanner = QuotaRetriever.open(conn.getConfiguration(), 466 new QuotaFilter().setTableFilter(tn.getNameAsString()))) { 467 for (QuotaSettings setting : scanner) { 468 if (setting.getTableName().equals(tn) && setting.getQuotaType() == QuotaType.SPACE) { 469 return setting; 470 } 471 } 472 return null; 473 } 474 } 475 476 /** 477 * Waits 30seconds for the HBase quota table to exist. 478 */ 479 public void waitForQuotaTable(Connection conn) throws IOException { 480 waitForQuotaTable(conn, 30_000); 481 } 482 483 /** 484 * Waits {@code timeout} milliseconds for the HBase quota table to exist. 485 */ 486 public void waitForQuotaTable(Connection conn, long timeout) throws IOException { 487 testUtil.waitFor(timeout, 1000, new Predicate<IOException>() { 488 @Override 489 public boolean evaluate() throws IOException { 490 return conn.getAdmin().tableExists(QuotaUtil.QUOTA_TABLE_NAME); 491 } 492 }); 493 } 494 495 void writeData(TableName tn, long sizeInBytes) throws IOException { 496 writeData(testUtil.getConnection(), tn, sizeInBytes); 497 } 498 499 void writeData(Connection conn, TableName tn, long sizeInBytes) throws IOException { 500 writeData(tn, sizeInBytes, Bytes.toBytes("q1")); 501 } 502 503 void writeData(TableName tn, long sizeInBytes, String qual) throws IOException { 504 writeData(tn, sizeInBytes, Bytes.toBytes(qual)); 505 } 506 507 void writeData(TableName tn, long sizeInBytes, byte[] qual) throws IOException { 508 final Connection conn = testUtil.getConnection(); 509 final Table table = conn.getTable(tn); 510 try { 511 List<Put> updates = new ArrayList<>(); 512 long bytesToWrite = sizeInBytes; 513 long rowKeyId = 0L; 514 final StringBuilder sb = new StringBuilder(); 515 while (bytesToWrite > 0L) { 516 sb.setLength(0); 517 sb.append(Long.toString(rowKeyId)); 518 // Use the reverse counter as the rowKey to get even spread across all regions 519 Put p = new Put(Bytes.toBytes(sb.reverse().toString())); 520 byte[] value = new byte[SIZE_PER_VALUE]; 521 Bytes.random(value); 522 p.addColumn(Bytes.toBytes(F1), qual, value); 523 updates.add(p); 524 525 // Batch ~13KB worth of updates 526 if (updates.size() > 50) { 527 table.put(updates); 528 updates.clear(); 529 } 530 531 // Just count the value size, ignore the size of rowkey + column 532 bytesToWrite -= SIZE_PER_VALUE; 533 rowKeyId++; 534 } 535 536 // Write the final batch 537 if (!updates.isEmpty()) { 538 table.put(updates); 539 } 540 541 LOG.debug("Data was written to HBase"); 542 // Push the data to disk. 543 testUtil.getAdmin().flush(tn); 544 LOG.debug("Data flushed to disk"); 545 } finally { 546 table.close(); 547 } 548 } 549 550 NamespaceDescriptor createNamespace() throws Exception { 551 return createNamespace(null); 552 } 553 554 NamespaceDescriptor createNamespace(String namespace) throws Exception { 555 if (namespace == null || namespace.trim().isEmpty()) 556 namespace = "ns" + counter.getAndIncrement(); 557 NamespaceDescriptor nd = NamespaceDescriptor.create(namespace).build(); 558 testUtil.getAdmin().createNamespace(nd); 559 return nd; 560 } 561 562 Multimap<TableName, QuotaSettings> createTablesWithSpaceQuotas() throws Exception { 563 final Admin admin = testUtil.getAdmin(); 564 final Multimap<TableName, QuotaSettings> tablesWithQuotas = HashMultimap.create(); 565 566 final TableName tn1 = createTable(); 567 final TableName tn2 = createTable(); 568 569 NamespaceDescriptor nd = createNamespace(); 570 final TableName tn3 = createTableInNamespace(nd); 571 final TableName tn4 = createTableInNamespace(nd); 572 final TableName tn5 = createTableInNamespace(nd); 573 574 final long sizeLimit1 = 1024L * 1024L * 1024L * 1024L * 5L; // 5TB 575 final SpaceViolationPolicy violationPolicy1 = SpaceViolationPolicy.NO_WRITES; 576 QuotaSettings qs1 = QuotaSettingsFactory.limitTableSpace(tn1, sizeLimit1, violationPolicy1); 577 tablesWithQuotas.put(tn1, qs1); 578 admin.setQuota(qs1); 579 580 final long sizeLimit2 = 1024L * 1024L * 1024L * 200L; // 200GB 581 final SpaceViolationPolicy violationPolicy2 = SpaceViolationPolicy.NO_WRITES_COMPACTIONS; 582 QuotaSettings qs2 = QuotaSettingsFactory.limitTableSpace(tn2, sizeLimit2, violationPolicy2); 583 tablesWithQuotas.put(tn2, qs2); 584 admin.setQuota(qs2); 585 586 final long sizeLimit3 = 1024L * 1024L * 1024L * 1024L * 100L; // 100TB 587 final SpaceViolationPolicy violationPolicy3 = SpaceViolationPolicy.NO_INSERTS; 588 QuotaSettings qs3 = 589 QuotaSettingsFactory.limitNamespaceSpace(nd.getName(), sizeLimit3, violationPolicy3); 590 tablesWithQuotas.put(tn3, qs3); 591 tablesWithQuotas.put(tn4, qs3); 592 tablesWithQuotas.put(tn5, qs3); 593 admin.setQuota(qs3); 594 595 final long sizeLimit4 = 1024L * 1024L * 1024L * 5L; // 5GB 596 final SpaceViolationPolicy violationPolicy4 = SpaceViolationPolicy.NO_INSERTS; 597 QuotaSettings qs4 = QuotaSettingsFactory.limitTableSpace(tn5, sizeLimit4, violationPolicy4); 598 // Override the ns quota for tn5, import edge-case to catch table quota taking 599 // precedence over ns quota. 600 tablesWithQuotas.put(tn5, qs4); 601 admin.setQuota(qs4); 602 603 return tablesWithQuotas; 604 } 605 606 TableName getNextTableName() { 607 return getNextTableName(NamespaceDescriptor.DEFAULT_NAMESPACE_NAME_STR); 608 } 609 610 TableName getNextTableName(String namespace) { 611 return TableName.valueOf(namespace, testName.getMethodName() + counter.getAndIncrement()); 612 } 613 614 TableName createTable() throws Exception { 615 return createTableWithRegions(1); 616 } 617 618 TableName createTableWithRegions(int numRegions) throws Exception { 619 return createTableWithRegions(NamespaceDescriptor.DEFAULT_NAMESPACE_NAME_STR, numRegions); 620 } 621 622 TableName createTableWithRegions(Admin admin, int numRegions) throws Exception { 623 return createTableWithRegions(admin, NamespaceDescriptor.DEFAULT_NAMESPACE_NAME_STR, numRegions, 624 0); 625 } 626 627 TableName createTableWithRegions(String namespace, int numRegions) throws Exception { 628 return createTableWithRegions(testUtil.getAdmin(), namespace, numRegions, 0); 629 } 630 631 TableName createTableWithRegions(Admin admin, String namespace, int numRegions, 632 int numberOfReplicas) throws Exception { 633 final TableName tn = getNextTableName(namespace); 634 635 // Delete the old table 636 if (admin.tableExists(tn)) { 637 admin.disableTable(tn); 638 admin.deleteTable(tn); 639 } 640 641 // Create the table 642 TableDescriptor tableDesc; 643 if (numberOfReplicas > 0) { 644 tableDesc = TableDescriptorBuilder.newBuilder(tn).setRegionReplication(numberOfReplicas) 645 .setColumnFamily(ColumnFamilyDescriptorBuilder.of(F1)).build(); 646 } else { 647 tableDesc = TableDescriptorBuilder.newBuilder(tn) 648 .setColumnFamily(ColumnFamilyDescriptorBuilder.of(F1)).build(); 649 } 650 if (numRegions == 1) { 651 admin.createTable(tableDesc); 652 } else { 653 admin.createTable(tableDesc, Bytes.toBytes("0"), Bytes.toBytes("9"), numRegions); 654 } 655 return tn; 656 } 657 658 TableName createTableInNamespace(NamespaceDescriptor nd) throws Exception { 659 final Admin admin = testUtil.getAdmin(); 660 final TableName tn = 661 TableName.valueOf(nd.getName(), testName.getMethodName() + counter.getAndIncrement()); 662 663 // Delete the old table 664 if (admin.tableExists(tn)) { 665 admin.disableTable(tn); 666 admin.deleteTable(tn); 667 } 668 669 // Create the table 670 TableDescriptor tableDesc = TableDescriptorBuilder.newBuilder(tn) 671 .addColumnFamily(ColumnFamilyDescriptorBuilder.of(F1)).build(); 672 673 admin.createTable(tableDesc); 674 return tn; 675 } 676 677 void partitionTablesByQuotaTarget(Multimap<TableName, QuotaSettings> quotas, 678 Set<TableName> tablesWithTableQuota, Set<TableName> tablesWithNamespaceQuota) { 679 // Partition the tables with quotas by table and ns quota 680 for (Entry<TableName, QuotaSettings> entry : quotas.entries()) { 681 SpaceLimitSettings settings = (SpaceLimitSettings) entry.getValue(); 682 TableName tn = entry.getKey(); 683 if (settings.getTableName() != null) { 684 tablesWithTableQuota.add(tn); 685 } 686 if (settings.getNamespace() != null) { 687 tablesWithNamespaceQuota.add(tn); 688 } 689 690 if (settings.getTableName() == null && settings.getNamespace() == null) { 691 fail("Unexpected table name with null tableName and namespace: " + tn); 692 } 693 } 694 } 695 696 /** 697 * Abstraction to simplify the case where a test needs to verify a certain state on a 698 * {@code SpaceQuotaSnapshot}. This class fails-fast when there is no such snapshot obtained from 699 * the Master. As such, it is not useful to verify the lack of a snapshot. 700 */ 701 static abstract class SpaceQuotaSnapshotPredicate implements Predicate<Exception> { 702 private final Connection conn; 703 private final TableName tn; 704 private final String ns; 705 706 SpaceQuotaSnapshotPredicate(Connection conn, TableName tn) { 707 this(Objects.requireNonNull(conn), Objects.requireNonNull(tn), null); 708 } 709 710 SpaceQuotaSnapshotPredicate(Connection conn, String ns) { 711 this(Objects.requireNonNull(conn), null, Objects.requireNonNull(ns)); 712 } 713 714 SpaceQuotaSnapshotPredicate(Connection conn, TableName tn, String ns) { 715 if ((null != tn && null != ns) || (null == tn && null == ns)) { 716 throw new IllegalArgumentException( 717 "One of TableName and Namespace must be non-null, and the other null"); 718 } 719 this.conn = conn; 720 this.tn = tn; 721 this.ns = ns; 722 } 723 724 @Override 725 public boolean evaluate() throws Exception { 726 SpaceQuotaSnapshot snapshot; 727 if (null == ns) { 728 snapshot = (SpaceQuotaSnapshot) conn.getAdmin().getCurrentSpaceQuotaSnapshot(tn); 729 } else { 730 snapshot = (SpaceQuotaSnapshot) conn.getAdmin().getCurrentSpaceQuotaSnapshot(ns); 731 } 732 733 LOG.debug("Saw quota snapshot for " + (null == tn ? ns : tn) + ": " + snapshot); 734 if (null == snapshot) { 735 return false; 736 } 737 return evaluate(snapshot); 738 } 739 740 /** 741 * Must determine if the given {@code SpaceQuotaSnapshot} meets some criteria. 742 * @param snapshot a non-null snapshot obtained from the HBase Master 743 * @return true if the criteria is met, false otherwise 744 */ 745 abstract boolean evaluate(SpaceQuotaSnapshot snapshot) throws Exception; 746 } 747 748 /** 749 * Predicate that waits for all store files in a table to have no compacted files. 750 */ 751 static class NoFilesToDischarge implements Predicate<Exception> { 752 private final MiniHBaseCluster cluster; 753 private final TableName tn; 754 755 NoFilesToDischarge(MiniHBaseCluster cluster, TableName tn) { 756 this.cluster = cluster; 757 this.tn = tn; 758 } 759 760 @Override 761 public boolean evaluate() throws Exception { 762 for (HRegion region : cluster.getRegions(tn)) { 763 for (HStore store : region.getStores()) { 764 Collection<HStoreFile> files = 765 store.getStoreEngine().getStoreFileManager().getCompactedfiles(); 766 if (null != files && !files.isEmpty()) { 767 LOG.debug(region.getRegionInfo().getEncodedName() + " still has compacted files"); 768 return false; 769 } 770 } 771 } 772 return true; 773 } 774 } 775}