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.assertFalse;
022import static org.junit.Assert.assertNull;
023import static org.junit.Assert.assertTrue;
024import static org.junit.Assert.fail;
025
026import java.util.ArrayList;
027import java.util.HashMap;
028import java.util.List;
029import java.util.Map;
030import java.util.Map.Entry;
031import java.util.concurrent.atomic.AtomicLong;
032import org.apache.hadoop.conf.Configuration;
033import org.apache.hadoop.fs.FileStatus;
034import org.apache.hadoop.fs.FileSystem;
035import org.apache.hadoop.fs.Path;
036import org.apache.hadoop.hbase.DoNotRetryIOException;
037import org.apache.hadoop.hbase.HBaseClassTestRule;
038import org.apache.hadoop.hbase.HBaseTestingUtility;
039import org.apache.hadoop.hbase.HConstants;
040import org.apache.hadoop.hbase.TableName;
041import org.apache.hadoop.hbase.TableNotEnabledException;
042import org.apache.hadoop.hbase.client.Admin;
043import org.apache.hadoop.hbase.client.Append;
044import org.apache.hadoop.hbase.client.ClientServiceCallable;
045import org.apache.hadoop.hbase.client.Connection;
046import org.apache.hadoop.hbase.client.Delete;
047import org.apache.hadoop.hbase.client.Increment;
048import org.apache.hadoop.hbase.client.Mutation;
049import org.apache.hadoop.hbase.client.Put;
050import org.apache.hadoop.hbase.client.RegionInfo;
051import org.apache.hadoop.hbase.client.Result;
052import org.apache.hadoop.hbase.client.ResultScanner;
053import org.apache.hadoop.hbase.client.RpcRetryingCaller;
054import org.apache.hadoop.hbase.client.RpcRetryingCallerFactory;
055import org.apache.hadoop.hbase.client.Scan;
056import org.apache.hadoop.hbase.client.SecureBulkLoadClient;
057import org.apache.hadoop.hbase.client.Table;
058import org.apache.hadoop.hbase.ipc.RpcControllerFactory;
059import org.apache.hadoop.hbase.master.HMaster;
060import org.apache.hadoop.hbase.quotas.policies.DefaultViolationPolicyEnforcement;
061import org.apache.hadoop.hbase.regionserver.HRegionServer;
062import org.apache.hadoop.hbase.regionserver.TestHRegionServerBulkLoad;
063import org.apache.hadoop.hbase.security.AccessDeniedException;
064import org.apache.hadoop.hbase.testclassification.LargeTests;
065import org.apache.hadoop.hbase.util.Bytes;
066import org.apache.hadoop.hbase.util.Pair;
067import org.apache.hadoop.util.StringUtils;
068import org.junit.AfterClass;
069import org.junit.Before;
070import org.junit.BeforeClass;
071import org.junit.ClassRule;
072import org.junit.Rule;
073import org.junit.Test;
074import org.junit.experimental.categories.Category;
075import org.junit.rules.TestName;
076import org.slf4j.Logger;
077import org.slf4j.LoggerFactory;
078
079/**
080 * End-to-end test class for filesystem space quotas.
081 */
082@Category(LargeTests.class)
083public class TestSpaceQuotas {
084
085  @ClassRule
086  public static final HBaseClassTestRule CLASS_RULE =
087      HBaseClassTestRule.forClass(TestSpaceQuotas.class);
088
089  private static final Logger LOG = LoggerFactory.getLogger(TestSpaceQuotas.class);
090  private static final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
091  // Global for all tests in the class
092  private static final AtomicLong COUNTER = new AtomicLong(0);
093  private static final int NUM_RETRIES = 10;
094
095  @Rule
096  public TestName testName = new TestName();
097  private SpaceQuotaHelperForTests helper;
098  private final TableName NON_EXISTENT_TABLE = TableName.valueOf("NON_EXISTENT_TABLE");
099
100  @BeforeClass
101  public static void setUp() throws Exception {
102    Configuration conf = TEST_UTIL.getConfiguration();
103    SpaceQuotaHelperForTests.updateConfigForQuotas(conf);
104    TEST_UTIL.startMiniCluster(1);
105  }
106
107  @AfterClass
108  public static void tearDown() throws Exception {
109    TEST_UTIL.shutdownMiniCluster();
110  }
111
112  @Before
113  public void removeAllQuotas() throws Exception {
114    final Connection conn = TEST_UTIL.getConnection();
115    if (helper == null) {
116      helper = new SpaceQuotaHelperForTests(TEST_UTIL, testName, COUNTER);
117    }
118    // Wait for the quota table to be created
119    if (!conn.getAdmin().tableExists(QuotaUtil.QUOTA_TABLE_NAME)) {
120      helper.waitForQuotaTable(conn);
121    } else {
122      // Or, clean up any quotas from previous test runs.
123      helper.removeAllQuotas(conn);
124      assertEquals(0, helper.listNumDefinedQuotas(conn));
125    }
126  }
127
128  @Test
129  public void testNoInsertsWithPut() throws Exception {
130    Put p = new Put(Bytes.toBytes("to_reject"));
131    p.addColumn(
132        Bytes.toBytes(SpaceQuotaHelperForTests.F1), Bytes.toBytes("to"), Bytes.toBytes("reject"));
133    writeUntilViolationAndVerifyViolation(SpaceViolationPolicy.NO_INSERTS, p);
134  }
135
136  @Test
137  public void testNoInsertsWithAppend() throws Exception {
138    Append a = new Append(Bytes.toBytes("to_reject"));
139    a.addColumn(
140        Bytes.toBytes(SpaceQuotaHelperForTests.F1), Bytes.toBytes("to"), Bytes.toBytes("reject"));
141    writeUntilViolationAndVerifyViolation(SpaceViolationPolicy.NO_INSERTS, a);
142  }
143
144  @Test
145  public void testNoInsertsWithIncrement() throws Exception {
146    Increment i = new Increment(Bytes.toBytes("to_reject"));
147    i.addColumn(Bytes.toBytes(SpaceQuotaHelperForTests.F1), Bytes.toBytes("count"), 0);
148    writeUntilViolationAndVerifyViolation(SpaceViolationPolicy.NO_INSERTS, i);
149  }
150
151  @Test
152  public void testDeletesAfterNoInserts() throws Exception {
153    final TableName tn = writeUntilViolation(SpaceViolationPolicy.NO_INSERTS);
154    // Try a couple of times to verify that the quota never gets enforced, same as we
155    // do when we're trying to catch the failure.
156    Delete d = new Delete(Bytes.toBytes("should_not_be_rejected"));
157    for (int i = 0; i < NUM_RETRIES; i++) {
158      try (Table t = TEST_UTIL.getConnection().getTable(tn)) {
159        t.delete(d);
160      }
161    }
162  }
163
164  @Test
165  public void testNoWritesWithPut() throws Exception {
166    Put p = new Put(Bytes.toBytes("to_reject"));
167    p.addColumn(
168        Bytes.toBytes(SpaceQuotaHelperForTests.F1), Bytes.toBytes("to"), Bytes.toBytes("reject"));
169    writeUntilViolationAndVerifyViolation(SpaceViolationPolicy.NO_WRITES, p);
170  }
171
172  @Test
173  public void testNoWritesWithAppend() throws Exception {
174    Append a = new Append(Bytes.toBytes("to_reject"));
175    a.addColumn(
176        Bytes.toBytes(SpaceQuotaHelperForTests.F1), Bytes.toBytes("to"), Bytes.toBytes("reject"));
177    writeUntilViolationAndVerifyViolation(SpaceViolationPolicy.NO_WRITES, a);
178  }
179
180  @Test
181  public void testNoWritesWithIncrement() throws Exception {
182    Increment i = new Increment(Bytes.toBytes("to_reject"));
183    i.addColumn(Bytes.toBytes(SpaceQuotaHelperForTests.F1), Bytes.toBytes("count"), 0);
184    writeUntilViolationAndVerifyViolation(SpaceViolationPolicy.NO_WRITES, i);
185  }
186
187  @Test
188  public void testNoWritesWithDelete() throws Exception {
189    Delete d = new Delete(Bytes.toBytes("to_reject"));
190    writeUntilViolationAndVerifyViolation(SpaceViolationPolicy.NO_WRITES, d);
191  }
192
193  @Test
194  public void testNoCompactions() throws Exception {
195    Put p = new Put(Bytes.toBytes("to_reject"));
196    p.addColumn(
197        Bytes.toBytes(SpaceQuotaHelperForTests.F1), Bytes.toBytes("to"), Bytes.toBytes("reject"));
198    final TableName tn = writeUntilViolationAndVerifyViolation(
199        SpaceViolationPolicy.NO_WRITES_COMPACTIONS, p);
200    // We know the policy is active at this point
201
202    // Major compactions should be rejected
203    try {
204      TEST_UTIL.getAdmin().majorCompact(tn);
205      fail("Expected that invoking the compaction should throw an Exception");
206    } catch (DoNotRetryIOException e) {
207      // Expected!
208    }
209    // Minor compactions should also be rejected.
210    try {
211      TEST_UTIL.getAdmin().compact(tn);
212      fail("Expected that invoking the compaction should throw an Exception");
213    } catch (DoNotRetryIOException e) {
214      // Expected!
215    }
216  }
217
218  @Test
219  public void testNoEnableAfterDisablePolicy() throws Exception {
220    Put p = new Put(Bytes.toBytes("to_reject"));
221    p.addColumn(
222        Bytes.toBytes(SpaceQuotaHelperForTests.F1), Bytes.toBytes("to"), Bytes.toBytes("reject"));
223    final TableName tn = writeUntilViolation(SpaceViolationPolicy.DISABLE);
224    final Admin admin = TEST_UTIL.getAdmin();
225    // Disabling a table relies on some external action (over the other policies), so wait a bit
226    // more than the other tests.
227    for (int i = 0; i < NUM_RETRIES * 2; i++) {
228      if (admin.isTableEnabled(tn)) {
229        LOG.info(tn + " is still enabled, expecting it to be disabled. Will wait and re-check.");
230        Thread.sleep(2000);
231      }
232    }
233    assertFalse(tn + " is still enabled but it should be disabled", admin.isTableEnabled(tn));
234    try {
235      admin.enableTable(tn);
236    } catch (AccessDeniedException e) {
237      String exceptionContents = StringUtils.stringifyException(e);
238      final String expectedText = "violated space quota";
239      assertTrue("Expected the exception to contain " + expectedText + ", but was: "
240          + exceptionContents, exceptionContents.contains(expectedText));
241    }
242  }
243
244  @Test
245  public void testNoBulkLoadsWithNoWrites() throws Exception {
246    Put p = new Put(Bytes.toBytes("to_reject"));
247    p.addColumn(
248        Bytes.toBytes(SpaceQuotaHelperForTests.F1), Bytes.toBytes("to"), Bytes.toBytes("reject"));
249    TableName tableName = writeUntilViolationAndVerifyViolation(SpaceViolationPolicy.NO_WRITES, p);
250
251    // The table is now in violation. Try to do a bulk load
252    ClientServiceCallable<Void> callable = generateFileToLoad(tableName, 1, 50);
253    RpcRetryingCallerFactory factory = new RpcRetryingCallerFactory(TEST_UTIL.getConfiguration());
254    RpcRetryingCaller<Void> caller = factory.<Void> newCaller();
255    try {
256      caller.callWithRetries(callable, Integer.MAX_VALUE);
257      fail("Expected the bulk load call to fail!");
258    } catch (SpaceLimitingException e) {
259      // Pass
260      LOG.trace("Caught expected exception", e);
261    }
262  }
263
264  @Test
265  public void testAtomicBulkLoadUnderQuota() throws Exception {
266    // Need to verify that if the batch of hfiles cannot be loaded, none are loaded.
267    TableName tn = helper.createTableWithRegions(10);
268
269    final long sizeLimit = 50L * SpaceQuotaHelperForTests.ONE_KILOBYTE;
270    QuotaSettings settings = QuotaSettingsFactory.limitTableSpace(
271        tn, sizeLimit, SpaceViolationPolicy.NO_INSERTS);
272    TEST_UTIL.getAdmin().setQuota(settings);
273
274    HRegionServer rs = TEST_UTIL.getMiniHBaseCluster().getRegionServer(0);
275    RegionServerSpaceQuotaManager spaceQuotaManager = rs.getRegionServerSpaceQuotaManager();
276    Map<TableName,SpaceQuotaSnapshot> snapshots = spaceQuotaManager.copyQuotaSnapshots();
277    Map<RegionInfo,Long> regionSizes = getReportedSizesForTable(tn);
278    while (true) {
279      SpaceQuotaSnapshot snapshot = snapshots.get(tn);
280      if (snapshot != null && snapshot.getLimit() > 0) {
281        break;
282      }
283      LOG.debug(
284          "Snapshot does not yet realize quota limit: " + snapshots + ", regionsizes: " +
285          regionSizes);
286      Thread.sleep(3000);
287      snapshots = spaceQuotaManager.copyQuotaSnapshots();
288      regionSizes = getReportedSizesForTable(tn);
289    }
290    // Our quota limit should be reflected in the latest snapshot
291    SpaceQuotaSnapshot snapshot = snapshots.get(tn);
292    assertEquals(0L, snapshot.getUsage());
293    assertEquals(sizeLimit, snapshot.getLimit());
294
295    // We would also not have a "real" policy in violation
296    ActivePolicyEnforcement activePolicies = spaceQuotaManager.getActiveEnforcements();
297    SpaceViolationPolicyEnforcement enforcement = activePolicies.getPolicyEnforcement(tn);
298    assertTrue(
299        "Expected to find Noop policy, but got " + enforcement.getClass().getSimpleName(),
300        enforcement instanceof DefaultViolationPolicyEnforcement);
301
302    // Should generate two files, each of which is over 25KB each
303    ClientServiceCallable<Void> callable = generateFileToLoad(tn, 2, 500);
304    FileSystem fs = TEST_UTIL.getTestFileSystem();
305    FileStatus[] files = fs.listStatus(
306        new Path(fs.getHomeDirectory(), testName.getMethodName() + "_files"));
307    for (FileStatus file : files) {
308      assertTrue(
309          "Expected the file, " + file.getPath() + ",  length to be larger than 25KB, but was "
310              + file.getLen(),
311          file.getLen() > 25 * SpaceQuotaHelperForTests.ONE_KILOBYTE);
312      LOG.debug(file.getPath() + " -> " + file.getLen() +"B");
313    }
314
315    RpcRetryingCallerFactory factory = new RpcRetryingCallerFactory(TEST_UTIL.getConfiguration());
316    RpcRetryingCaller<Void> caller = factory.<Void> newCaller();
317    try {
318      caller.callWithRetries(callable, Integer.MAX_VALUE);
319      fail("Expected the bulk load call to fail!");
320    } catch (SpaceLimitingException e) {
321      // Pass
322      LOG.trace("Caught expected exception", e);
323    }
324    // Verify that we have no data in the table because neither file should have been
325    // loaded even though one of the files could have.
326    Table table = TEST_UTIL.getConnection().getTable(tn);
327    ResultScanner scanner = table.getScanner(new Scan());
328    try {
329      assertNull("Expected no results", scanner.next());
330    } finally{
331      scanner.close();
332    }
333  }
334
335  @Test
336  public void testTableQuotaOverridesNamespaceQuota() throws Exception {
337    final SpaceViolationPolicy policy = SpaceViolationPolicy.NO_INSERTS;
338    final TableName tn = helper.createTableWithRegions(10);
339
340    // 2MB limit on the table, 1GB limit on the namespace
341    final long tableLimit = 2L * SpaceQuotaHelperForTests.ONE_MEGABYTE;
342    final long namespaceLimit = 1024L * SpaceQuotaHelperForTests.ONE_MEGABYTE;
343    TEST_UTIL.getAdmin().setQuota(QuotaSettingsFactory.limitTableSpace(tn, tableLimit, policy));
344    TEST_UTIL.getAdmin().setQuota(QuotaSettingsFactory.limitNamespaceSpace(
345        tn.getNamespaceAsString(), namespaceLimit, policy));
346
347    // Write more data than should be allowed and flush it to disk
348    helper.writeData(tn, 3L * SpaceQuotaHelperForTests.ONE_MEGABYTE);
349
350    // This should be sufficient time for the chores to run and see the change.
351    Thread.sleep(5000);
352
353    // The write should be rejected because the table quota takes priority over the namespace
354    Put p = new Put(Bytes.toBytes("to_reject"));
355    p.addColumn(
356        Bytes.toBytes(SpaceQuotaHelperForTests.F1), Bytes.toBytes("to"), Bytes.toBytes("reject"));
357    verifyViolation(policy, tn, p);
358  }
359
360  @Test
361  public void testSetQuotaAndThenRemoveWithNoInserts() throws Exception {
362    setQuotaAndThenRemove(SpaceViolationPolicy.NO_INSERTS);
363  }
364
365  @Test
366  public void testSetQuotaAndThenRemoveWithNoWrite() throws Exception {
367    setQuotaAndThenRemove(SpaceViolationPolicy.NO_WRITES);
368  }
369
370  @Test
371  public void testSetQuotaAndThenRemoveWithNoWritesCompactions() throws Exception {
372    setQuotaAndThenRemove(SpaceViolationPolicy.NO_WRITES_COMPACTIONS);
373  }
374
375  @Test
376  public void testSetQuotaAndThenRemoveWithDisable() throws Exception {
377    setQuotaAndThenRemove(SpaceViolationPolicy.DISABLE);
378  }
379
380  @Test
381  public void testSetQuotaAndThenDropTableWithNoInserts() throws Exception {
382    setQuotaAndThenDropTable(SpaceViolationPolicy.NO_INSERTS);
383  }
384
385  @Test
386  public void testSetQuotaAndThenDropTableWithNoWrite() throws Exception {
387    setQuotaAndThenDropTable(SpaceViolationPolicy.NO_WRITES);
388  }
389
390  @Test
391  public void testSetQuotaAndThenDropTableeWithNoWritesCompactions() throws Exception {
392    setQuotaAndThenDropTable(SpaceViolationPolicy.NO_WRITES_COMPACTIONS);
393  }
394
395  @Test
396  public void testSetQuotaAndThenDropTableWithDisable() throws Exception {
397    setQuotaAndThenDropTable(SpaceViolationPolicy.DISABLE);
398  }
399
400  @Test
401  public void testSetQuotaAndThenIncreaseQuotaWithNoInserts() throws Exception {
402    setQuotaAndThenIncreaseQuota(SpaceViolationPolicy.NO_INSERTS);
403  }
404
405  @Test
406  public void testSetQuotaAndThenIncreaseQuotaWithNoWrite() throws Exception {
407    setQuotaAndThenIncreaseQuota(SpaceViolationPolicy.NO_WRITES);
408  }
409
410  @Test
411  public void testSetQuotaAndThenIncreaseQuotaWithNoWritesCompactions() throws Exception {
412    setQuotaAndThenIncreaseQuota(SpaceViolationPolicy.NO_WRITES_COMPACTIONS);
413  }
414
415  @Test
416  public void testSetQuotaAndThenRemoveInOneWithNoInserts() throws Exception {
417    setQuotaAndThenRemoveInOneAmongTwoTables(SpaceViolationPolicy.NO_INSERTS);
418  }
419
420  @Test
421  public void testSetQuotaAndThenRemoveInOneWithNoWrite() throws Exception {
422    setQuotaAndThenRemoveInOneAmongTwoTables(SpaceViolationPolicy.NO_WRITES);
423  }
424
425  @Test
426  public void testSetQuotaAndThenRemoveInOneWithNoWritesCompaction() throws Exception {
427    setQuotaAndThenRemoveInOneAmongTwoTables(SpaceViolationPolicy.NO_WRITES_COMPACTIONS);
428  }
429
430  @Test
431  public void testSetQuotaAndThenRemoveInOneWithDisable() throws Exception {
432    setQuotaAndThenRemoveInOneAmongTwoTables(SpaceViolationPolicy.DISABLE);
433  }
434
435  @Test
436  public void testSetQuotaOnNonExistingTableWithNoInserts() throws Exception {
437    setQuotaLimit(NON_EXISTENT_TABLE, SpaceViolationPolicy.NO_INSERTS, 2L);
438  }
439
440  @Test
441  public void testSetQuotaOnNonExistingTableWithNoWrites() throws Exception {
442    setQuotaLimit(NON_EXISTENT_TABLE, SpaceViolationPolicy.NO_WRITES, 2L);
443  }
444
445  @Test
446  public void testSetQuotaOnNonExistingTableWithNoWritesCompaction() throws Exception {
447    setQuotaLimit(NON_EXISTENT_TABLE, SpaceViolationPolicy.NO_WRITES_COMPACTIONS, 2L);
448  }
449
450  @Test
451  public void testSetQuotaOnNonExistingTableWithDisable() throws Exception {
452    setQuotaLimit(NON_EXISTENT_TABLE, SpaceViolationPolicy.DISABLE, 2L);
453  }
454
455  private void setQuotaAndThenRemove(SpaceViolationPolicy policy) throws Exception {
456    Put put = new Put(Bytes.toBytes("to_reject"));
457    put.addColumn(Bytes.toBytes(SpaceQuotaHelperForTests.F1), Bytes.toBytes("to"),
458      Bytes.toBytes("reject"));
459
460    // Do puts until we violate space policy
461    final TableName tn = writeUntilViolationAndVerifyViolation(policy, put);
462
463    // Now, remove the quota
464    removeQuotaFromtable(tn);
465
466    // Put some rows now: should not violate as quota settings removed
467    verifyNoViolation(policy, tn, put);
468  }
469
470  private void setQuotaAndThenDropTable(SpaceViolationPolicy policy) throws Exception {
471    Put put = new Put(Bytes.toBytes("to_reject"));
472    put.addColumn(Bytes.toBytes(SpaceQuotaHelperForTests.F1), Bytes.toBytes("to"),
473      Bytes.toBytes("reject"));
474
475    // Do puts until we violate space policy
476    final TableName tn = writeUntilViolationAndVerifyViolation(policy, put);
477
478    // Now, drop the table
479    TEST_UTIL.deleteTable(tn);
480    LOG.debug("Successfully deleted table ", tn);
481
482    // Now re-create the table
483    TEST_UTIL.createTable(tn, Bytes.toBytes(SpaceQuotaHelperForTests.F1));
484    LOG.debug("Successfully re-created table ", tn);
485
486    // Put some rows now: should not violate as table/quota was dropped
487    verifyNoViolation(policy, tn, put);
488  }
489
490  private void setQuotaAndThenIncreaseQuota(SpaceViolationPolicy policy) throws Exception {
491    Put put = new Put(Bytes.toBytes("to_reject"));
492    put.addColumn(Bytes.toBytes(SpaceQuotaHelperForTests.F1), Bytes.toBytes("to"),
493      Bytes.toBytes("reject"));
494
495    // Do puts until we violate space policy
496    final TableName tn = writeUntilViolationAndVerifyViolation(policy, put);
497
498    // Now, increase limit and perform put
499    setQuotaLimit(tn, policy, 4L);
500
501    // Put some row now: should not violate as quota limit increased
502    verifyNoViolation(policy, tn, put);
503  }
504
505  public void setQuotaAndThenRemoveInOneAmongTwoTables(SpaceViolationPolicy policy)
506      throws Exception {
507    Put put = new Put(Bytes.toBytes("to_reject"));
508    put.addColumn(Bytes.toBytes(SpaceQuotaHelperForTests.F1), Bytes.toBytes("to"),
509      Bytes.toBytes("reject"));
510
511    // Do puts until we violate space policy on table tn1
512    final TableName tn1 = writeUntilViolationAndVerifyViolation(policy, put);
513
514    // Do puts until we violate space policy on table tn2
515    final TableName tn2 = writeUntilViolationAndVerifyViolation(policy, put);
516
517    // Now, remove the quota from table tn1
518    removeQuotaFromtable(tn1);
519
520    // Put a new row now on tn1: should not violate as quota settings removed
521    verifyNoViolation(policy, tn1, put);
522    // Put a new row now on tn2: should violate as quota settings exists
523    verifyViolation(policy, tn2, put);
524  }
525
526  private void removeQuotaFromtable(final TableName tn) throws Exception {
527    QuotaSettings removeQuota = QuotaSettingsFactory.removeTableSpaceLimit(tn);
528    TEST_UTIL.getAdmin().setQuota(removeQuota);
529    LOG.debug("Space quota settings removed from the table ", tn);
530  }
531
532  private void setQuotaLimit(final TableName tn, SpaceViolationPolicy policy, long sizeInMBs)
533      throws Exception {
534    final long sizeLimit = sizeInMBs * SpaceQuotaHelperForTests.ONE_MEGABYTE;
535    QuotaSettings settings = QuotaSettingsFactory.limitTableSpace(tn, sizeLimit, policy);
536    TEST_UTIL.getAdmin().setQuota(settings);
537    LOG.debug("Quota limit set for table = {}, limit = {}", tn, sizeLimit);
538  }
539
540  private Map<RegionInfo,Long> getReportedSizesForTable(TableName tn) {
541    HMaster master = TEST_UTIL.getMiniHBaseCluster().getMaster();
542    MasterQuotaManager quotaManager = master.getMasterQuotaManager();
543    Map<RegionInfo,Long> filteredRegionSizes = new HashMap<>();
544    for (Entry<RegionInfo,Long> entry : quotaManager.snapshotRegionSizes().entrySet()) {
545      if (entry.getKey().getTable().equals(tn)) {
546        filteredRegionSizes.put(entry.getKey(), entry.getValue());
547      }
548    }
549    return filteredRegionSizes;
550  }
551
552  private TableName writeUntilViolation(SpaceViolationPolicy policyToViolate) throws Exception {
553    TableName tn = helper.createTableWithRegions(10);
554    setQuotaLimit(tn, policyToViolate, 2L);
555    // Write more data than should be allowed and flush it to disk
556    helper.writeData(tn, 3L * SpaceQuotaHelperForTests.ONE_MEGABYTE);
557
558    // This should be sufficient time for the chores to run and see the change.
559    Thread.sleep(5000);
560
561    return tn;
562  }
563
564  private TableName writeUntilViolationAndVerifyViolation(
565      SpaceViolationPolicy policyToViolate, Mutation m) throws Exception {
566    final TableName tn = writeUntilViolation(policyToViolate);
567    verifyViolation(policyToViolate, tn, m);
568    return tn;
569  }
570
571  private void verifyViolation(
572      SpaceViolationPolicy policyToViolate, TableName tn, Mutation m) throws Exception {
573    // But let's try a few times to get the exception before failing
574    boolean sawError = false;
575    for (int i = 0; i < NUM_RETRIES && !sawError; i++) {
576      try (Table table = TEST_UTIL.getConnection().getTable(tn)) {
577        if (m instanceof Put) {
578          table.put((Put) m);
579        } else if (m instanceof Delete) {
580          table.delete((Delete) m);
581        } else if (m instanceof Append) {
582          table.append((Append) m);
583        } else if (m instanceof Increment) {
584          table.increment((Increment) m);
585        } else {
586          fail(
587              "Failed to apply " + m.getClass().getSimpleName() +
588              " to the table. Programming error");
589        }
590        LOG.info("Did not reject the " + m.getClass().getSimpleName() + ", will sleep and retry");
591        Thread.sleep(2000);
592      } catch (Exception e) {
593        String msg = StringUtils.stringifyException(e);
594        if (policyToViolate.equals(SpaceViolationPolicy.DISABLE)) {
595          assertTrue(e instanceof TableNotEnabledException);
596        } else {
597          assertTrue("Expected exception message to contain the word '" + policyToViolate.name()
598              + "', but was " + msg,
599            msg.contains(policyToViolate.name()));
600        }
601        sawError = true;
602      }
603    }
604    if (!sawError) {
605      try (Table quotaTable = TEST_UTIL.getConnection().getTable(QuotaUtil.QUOTA_TABLE_NAME)) {
606        ResultScanner scanner = quotaTable.getScanner(new Scan());
607        Result result = null;
608        LOG.info("Dumping contents of hbase:quota table");
609        while ((result = scanner.next()) != null) {
610          LOG.info(Bytes.toString(result.getRow()) + " => " + result.toString());
611        }
612        scanner.close();
613      }
614    }
615    assertTrue(
616        "Expected to see an exception writing data to a table exceeding its quota", sawError);
617  }
618
619  private ClientServiceCallable<Void> generateFileToLoad(
620      TableName tn, int numFiles, int numRowsPerFile) throws Exception {
621    Connection conn = TEST_UTIL.getConnection();
622    FileSystem fs = TEST_UTIL.getTestFileSystem();
623    Configuration conf = TEST_UTIL.getConfiguration();
624    Path baseDir = new Path(fs.getHomeDirectory(), testName.getMethodName() + "_files");
625    fs.mkdirs(baseDir);
626    final List<Pair<byte[], String>> famPaths = new ArrayList<Pair<byte[], String>>();
627    for (int i = 1; i <= numFiles; i++) {
628      Path hfile = new Path(baseDir, "file" + i);
629      TestHRegionServerBulkLoad.createHFile(
630          fs, hfile, Bytes.toBytes(SpaceQuotaHelperForTests.F1), Bytes.toBytes("to"),
631          Bytes.toBytes("reject"), numRowsPerFile);
632      famPaths.add(new Pair<>(Bytes.toBytes(SpaceQuotaHelperForTests.F1), hfile.toString()));
633    }
634
635    // bulk load HFiles
636    Table table = conn.getTable(tn);
637    final String bulkToken = new SecureBulkLoadClient(conf, table).prepareBulkLoad(conn);
638    return new ClientServiceCallable<Void>(conn,
639        tn, Bytes.toBytes("row"), new RpcControllerFactory(conf).newController(), HConstants.PRIORITY_UNSET) {
640      @Override
641      public Void rpcCall() throws Exception {
642        SecureBulkLoadClient secureClient = null;
643        byte[] regionName = getLocation().getRegionInfo().getRegionName();
644        try (Table table = conn.getTable(getTableName())) {
645          secureClient = new SecureBulkLoadClient(conf, table);
646          secureClient.secureBulkLoadHFiles(getStub(), famPaths, regionName,
647                true, null, bulkToken);
648        }
649        return null;
650      }
651    };
652  }
653
654  private void verifyNoViolation(SpaceViolationPolicy policyToViolate, TableName tn, Mutation m)
655      throws Exception {
656    // But let's try a few times to write data before failing
657    boolean sawSuccess = false;
658    for (int i = 0; i < NUM_RETRIES && !sawSuccess; i++) {
659      try (Table table = TEST_UTIL.getConnection().getTable(tn)) {
660        if (m instanceof Put) {
661          table.put((Put) m);
662        } else if (m instanceof Delete) {
663          table.delete((Delete) m);
664        } else if (m instanceof Append) {
665          table.append((Append) m);
666        } else if (m instanceof Increment) {
667          table.increment((Increment) m);
668        } else {
669          fail(
670            "Failed to apply " + m.getClass().getSimpleName() + " to the table. Programming error");
671        }
672        sawSuccess = true;
673      } catch (Exception e) {
674        LOG.info("Rejected the " + m.getClass().getSimpleName() + ", will sleep and retry");
675        Thread.sleep(2000);
676      }
677    }
678    if (!sawSuccess) {
679      try (Table quotaTable = TEST_UTIL.getConnection().getTable(QuotaUtil.QUOTA_TABLE_NAME)) {
680        ResultScanner scanner = quotaTable.getScanner(new Scan());
681        Result result = null;
682        LOG.info("Dumping contents of hbase:quota table");
683        while ((result = scanner.next()) != null) {
684          LOG.info(Bytes.toString(result.getRow()) + " => " + result.toString());
685        }
686        scanner.close();
687      }
688    }
689    assertTrue("Expected to succeed in writing data to a table not having quota ", sawSuccess);
690  }
691}