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}