/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.hadoop.hbase.quotas;

import static org.apache.hadoop.hbase.quotas.ThrottleQuotaTestUtil.doGets;
import static org.apache.hadoop.hbase.quotas.ThrottleQuotaTestUtil.doPuts;
import static org.apache.hadoop.hbase.quotas.ThrottleQuotaTestUtil.triggerExceedThrottleQuotaCacheRefresh;
import static org.apache.hadoop.hbase.quotas.ThrottleQuotaTestUtil.triggerNamespaceCacheRefresh;
import static org.apache.hadoop.hbase.quotas.ThrottleQuotaTestUtil.triggerRegionServerCacheRefresh;
import static org.apache.hadoop.hbase.quotas.ThrottleQuotaTestUtil.triggerTableCacheRefresh;
import static org.apache.hadoop.hbase.quotas.ThrottleQuotaTestUtil.triggerUserCacheRefresh;
import static org.apache.hadoop.hbase.quotas.ThrottleQuotaTestUtil.waitMinuteQuota;
import static org.junit.Assert.assertEquals;

import java.io.IOException;
import java.util.List;
import java.util.concurrent.TimeUnit;

import org.apache.hadoop.hbase.HBaseClassTestRule;
import org.apache.hadoop.hbase.HBaseTestingUtility;
import org.apache.hadoop.hbase.HConstants;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.Admin;
import org.apache.hadoop.hbase.client.Get;
import org.apache.hadoop.hbase.client.Table;
import org.apache.hadoop.hbase.security.User;
import org.apache.hadoop.hbase.testclassification.MediumTests;
import org.apache.hadoop.hbase.testclassification.RegionServerTests;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Ignore // Disabled because flakey. Fails ~30% on a resource constrained GCE though not on Apache.
@Category({RegionServerTests.class, MediumTests.class})
public class TestQuotaThrottle {

  @ClassRule
  public static final HBaseClassTestRule CLASS_RULE =
      HBaseClassTestRule.forClass(TestQuotaThrottle.class);

  private final static Logger LOG = LoggerFactory.getLogger(TestQuotaThrottle.class);

  private final static int REFRESH_TIME = 30 * 60000;

  private final static HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
  private final static byte[] FAMILY = Bytes.toBytes("cf");
  private final static byte[] QUALIFIER = Bytes.toBytes("q");

  private final static TableName[] TABLE_NAMES = new TableName[] {
    TableName.valueOf("TestQuotaAdmin0"),
    TableName.valueOf("TestQuotaAdmin1"),
    TableName.valueOf("TestQuotaAdmin2")
  };

  private final static String[] NAMESPACES = new String[] {
    "NAMESPACE01",
    "NAMESPACE02",
    "NAMESPACE03"
  };

  private static Table[] tables;

  @BeforeClass
  public static void setUpBeforeClass() throws Exception {
    TEST_UTIL.getConfiguration().setBoolean(QuotaUtil.QUOTA_CONF_KEY, true);
    TEST_UTIL.getConfiguration().setInt(QuotaCache.REFRESH_CONF_KEY, REFRESH_TIME);
    TEST_UTIL.getConfiguration().setInt("hbase.hstore.compactionThreshold", 10);
    TEST_UTIL.getConfiguration().setInt("hbase.regionserver.msginterval", 100);
    TEST_UTIL.getConfiguration().setInt("hbase.client.pause", 250);
    TEST_UTIL.getConfiguration().setInt(HConstants.HBASE_CLIENT_RETRIES_NUMBER, 6);
    TEST_UTIL.getConfiguration().setBoolean("hbase.master.enabletable.roundrobin", true);
    TEST_UTIL.startMiniCluster(1);
    TEST_UTIL.waitTableAvailable(QuotaTableUtil.QUOTA_TABLE_NAME);
    QuotaCache.TEST_FORCE_REFRESH = true;

    tables = new Table[TABLE_NAMES.length];
    for (int i = 0; i < TABLE_NAMES.length; ++i) {
      tables[i] = TEST_UTIL.createTable(TABLE_NAMES[i], FAMILY);
    }
  }

  @AfterClass
  public static void tearDownAfterClass() throws Exception {
    EnvironmentEdgeManager.reset();
    for (int i = 0; i < tables.length; ++i) {
      if (tables[i] != null) {
        tables[i].close();
        TEST_UTIL.deleteTable(TABLE_NAMES[i]);
      }
    }

    TEST_UTIL.shutdownMiniCluster();
  }

  @After
  public void tearDown() throws Exception {
    ThrottleQuotaTestUtil.clearQuotaCache(TEST_UTIL);
  }

  @Test
  public void testUserGlobalThrottle() throws Exception {
    final Admin admin = TEST_UTIL.getAdmin();
    final String userName = User.getCurrent().getShortName();

    // Add 6req/min limit
    admin.setQuota(QuotaSettingsFactory.throttleUser(userName, ThrottleType.REQUEST_NUMBER, 6,
      TimeUnit.MINUTES));
    triggerUserCacheRefresh(TEST_UTIL, false, TABLE_NAMES);

    // should execute at max 6 requests
    assertEquals(6, doPuts(100, FAMILY, QUALIFIER, tables));

    // wait a minute and you should get other 6 requests executed
    waitMinuteQuota();
    assertEquals(6, doPuts(100, FAMILY, QUALIFIER, tables));

    // Remove all the limits
    admin.setQuota(QuotaSettingsFactory.unthrottleUser(userName));
    triggerUserCacheRefresh(TEST_UTIL, true, TABLE_NAMES);
    assertEquals(60, doPuts(60, FAMILY, QUALIFIER, tables));
    assertEquals(60, doGets(60, tables));
  }

  @Test
  public void testUserGlobalReadAndWriteThrottle() throws Exception {
    final Admin admin = TEST_UTIL.getAdmin();
    final String userName = User.getCurrent().getShortName();

    // Add 6req/min limit for read request
    admin.setQuota(
      QuotaSettingsFactory.throttleUser(userName, ThrottleType.READ_NUMBER, 6, TimeUnit.MINUTES));
    triggerUserCacheRefresh(TEST_UTIL, false, TABLE_NAMES);

    // not limit for write request and should execute at max 6 read requests
    assertEquals(60, doPuts(60, FAMILY, QUALIFIER, tables));
    assertEquals(6, doGets(100, tables));

    waitMinuteQuota();

    // Add 6req/min limit for write request
    admin.setQuota(
      QuotaSettingsFactory.throttleUser(userName, ThrottleType.WRITE_NUMBER, 6, TimeUnit.MINUTES));
    triggerUserCacheRefresh(TEST_UTIL, false, TABLE_NAMES);

    // should execute at max 6 read requests and at max 6 write write requests
    assertEquals(6, doGets(100, tables));
    assertEquals(6, doPuts(60, FAMILY, QUALIFIER, tables));

    // Remove all the limits
    admin.setQuota(QuotaSettingsFactory.unthrottleUser(userName));
    triggerUserCacheRefresh(TEST_UTIL, true, TABLE_NAMES);
    assertEquals(60, doPuts(60, FAMILY, QUALIFIER, tables));
    assertEquals(60, doGets(60, tables));
  }

  @Test
  public void testUserTableThrottle() throws Exception {
    final Admin admin = TEST_UTIL.getAdmin();
    final String userName = User.getCurrent().getShortName();

    // Add 6req/min limit
    admin.setQuota(QuotaSettingsFactory.throttleUser(userName, TABLE_NAMES[0],
      ThrottleType.REQUEST_NUMBER, 6, TimeUnit.MINUTES));
    triggerUserCacheRefresh(TEST_UTIL, false, TABLE_NAMES[0]);

    // should execute at max 6 requests on tables[0] and have no limit on tables[1]
    assertEquals(6, doPuts(100, FAMILY, QUALIFIER, tables[0]));
    assertEquals(30, doPuts(30, FAMILY, QUALIFIER, tables[1]));

    // wait a minute and you should get other 6 requests executed
    waitMinuteQuota();
    assertEquals(6, doPuts(100, FAMILY, QUALIFIER, tables[0]));

    // Remove all the limits
    admin.setQuota(QuotaSettingsFactory.unthrottleUser(userName, TABLE_NAMES[0]));
    triggerUserCacheRefresh(TEST_UTIL, true, TABLE_NAMES);
    assertEquals(60, doPuts(60, FAMILY, QUALIFIER, tables));
    assertEquals(60, doGets(60, tables));
  }

  @Test
  public void testUserUnThrottleByType() throws Exception {
    final Admin admin = TEST_UTIL.getAdmin();
    final String userName = User.getCurrent().getShortName();
    String userName01 = "user01";
    // Add 6req/min limit
    admin.setQuota(QuotaSettingsFactory
        .throttleUser(userName, ThrottleType.REQUEST_NUMBER, 6, TimeUnit.MINUTES));
    admin.setQuota(QuotaSettingsFactory
        .throttleUser(userName, ThrottleType.REQUEST_SIZE, 6, TimeUnit.MINUTES));
    admin.setQuota(QuotaSettingsFactory
        .throttleUser(userName01, ThrottleType.REQUEST_NUMBER, 6, TimeUnit.MINUTES));
    admin.setQuota(QuotaSettingsFactory
        .throttleUser(userName01, ThrottleType.REQUEST_SIZE, 6, TimeUnit.MINUTES));
    admin.setQuota(
        QuotaSettingsFactory.unthrottleUserByThrottleType(userName, ThrottleType.REQUEST_NUMBER));
    assertEquals(3, getQuotaSettingCount(admin));
    admin.setQuota(
        QuotaSettingsFactory.unthrottleUserByThrottleType(userName, ThrottleType.REQUEST_SIZE));
    assertEquals(2, getQuotaSettingCount(admin));
  }

  @Test
  public void testUserTableUnThrottleByType() throws Exception {
    final Admin admin = TEST_UTIL.getAdmin();
    final String userName = User.getCurrent().getShortName();
    String userName01 = "user01";
    // Add 6req/min limit
    admin.setQuota(QuotaSettingsFactory
        .throttleUser(userName, TABLE_NAMES[0], ThrottleType.REQUEST_NUMBER, 6, TimeUnit.MINUTES));
    admin.setQuota(QuotaSettingsFactory
        .throttleUser(userName, TABLE_NAMES[0], ThrottleType.REQUEST_SIZE, 6, TimeUnit.MINUTES));
    admin.setQuota(QuotaSettingsFactory
        .throttleUser(userName01, TABLE_NAMES[1], ThrottleType.REQUEST_NUMBER, 6,
            TimeUnit.MINUTES));
    admin.setQuota(QuotaSettingsFactory
        .throttleUser(userName01, TABLE_NAMES[1], ThrottleType.REQUEST_SIZE, 6, TimeUnit.MINUTES));
    admin.setQuota(QuotaSettingsFactory
        .unthrottleUserByThrottleType(userName, TABLE_NAMES[0], ThrottleType.REQUEST_NUMBER));
    assertEquals(3, getQuotaSettingCount(admin));
    admin.setQuota(QuotaSettingsFactory
        .unthrottleUserByThrottleType(userName, TABLE_NAMES[0], ThrottleType.REQUEST_SIZE));
    assertEquals(2, getQuotaSettingCount(admin));
  }

  @Test
  public void testUserNameSpaceUnThrottleByType() throws Exception {
    final Admin admin = TEST_UTIL.getAdmin();
    final String userName = User.getCurrent().getShortName();
    String userName01 = "user01";
    // Add 6req/min limit
    admin.setQuota(QuotaSettingsFactory
        .throttleUser(userName, NAMESPACES[0], ThrottleType.REQUEST_NUMBER, 6, TimeUnit.MINUTES));
    admin.setQuota(QuotaSettingsFactory
        .throttleUser(userName, NAMESPACES[0], ThrottleType.REQUEST_SIZE, 6, TimeUnit.MINUTES));
    admin.setQuota(QuotaSettingsFactory
        .throttleUser(userName01, NAMESPACES[1], ThrottleType.REQUEST_NUMBER, 6, TimeUnit.MINUTES));
    admin.setQuota(QuotaSettingsFactory
        .throttleUser(userName01, NAMESPACES[1], ThrottleType.REQUEST_SIZE, 6, TimeUnit.MINUTES));
    admin.setQuota(QuotaSettingsFactory
        .unthrottleUserByThrottleType(userName, NAMESPACES[0], ThrottleType.REQUEST_NUMBER));
    assertEquals(3, getQuotaSettingCount(admin));
    admin.setQuota(QuotaSettingsFactory
        .unthrottleUserByThrottleType(userName, NAMESPACES[0], ThrottleType.REQUEST_SIZE));
    assertEquals(2, getQuotaSettingCount(admin));
  }

  @Test
  public void testTableUnThrottleByType() throws Exception {
    final Admin admin = TEST_UTIL.getAdmin();
    final String userName = User.getCurrent().getShortName();
    // Add 6req/min limit
    admin.setQuota(QuotaSettingsFactory
        .throttleTable(TABLE_NAMES[0], ThrottleType.REQUEST_NUMBER, 6, TimeUnit.MINUTES));
    admin.setQuota(QuotaSettingsFactory
        .throttleTable(TABLE_NAMES[0], ThrottleType.REQUEST_SIZE, 6, TimeUnit.MINUTES));
    admin.setQuota(QuotaSettingsFactory
        .throttleTable(TABLE_NAMES[1], ThrottleType.REQUEST_NUMBER, 6, TimeUnit.MINUTES));
    admin.setQuota(QuotaSettingsFactory
        .throttleTable(TABLE_NAMES[1], ThrottleType.REQUEST_SIZE, 6, TimeUnit.MINUTES));
    admin.setQuota(QuotaSettingsFactory
        .unthrottleTableByThrottleType(TABLE_NAMES[0], ThrottleType.REQUEST_NUMBER));
    assertEquals(3, getQuotaSettingCount(admin));
    admin.setQuota(QuotaSettingsFactory
        .unthrottleTableByThrottleType(TABLE_NAMES[0], ThrottleType.REQUEST_SIZE));
    assertEquals(2, getQuotaSettingCount(admin));
  }

  @Test
  public void testNameSpaceUnThrottleByType() throws Exception {
    final Admin admin = TEST_UTIL.getAdmin();
    final String userName = User.getCurrent().getShortName();
    // Add 6req/min limit
    admin.setQuota(QuotaSettingsFactory
        .throttleNamespace(NAMESPACES[0], ThrottleType.REQUEST_NUMBER, 6, TimeUnit.MINUTES));
    admin.setQuota(QuotaSettingsFactory
        .throttleNamespace(NAMESPACES[0], ThrottleType.REQUEST_SIZE, 6, TimeUnit.MINUTES));
    admin.setQuota(QuotaSettingsFactory
        .throttleNamespace(NAMESPACES[1], ThrottleType.REQUEST_NUMBER, 6, TimeUnit.MINUTES));
    admin.setQuota(QuotaSettingsFactory
        .throttleNamespace(NAMESPACES[1], ThrottleType.REQUEST_SIZE, 6, TimeUnit.MINUTES));
    admin.setQuota(QuotaSettingsFactory
        .unthrottleNamespaceByThrottleType(NAMESPACES[0], ThrottleType.REQUEST_NUMBER));
    assertEquals(3, getQuotaSettingCount(admin));
    admin.setQuota(QuotaSettingsFactory
        .unthrottleNamespaceByThrottleType(NAMESPACES[0], ThrottleType.REQUEST_SIZE));
    assertEquals(2, getQuotaSettingCount(admin));
  }

  @Test
  public void testRegionServerUnThrottleByType() throws Exception {
    final Admin admin = TEST_UTIL.getAdmin();
    final String[] REGIONSERVER = { "RS01", "RS02" };

    admin.setQuota(QuotaSettingsFactory
        .throttleRegionServer(REGIONSERVER[0], ThrottleType.READ_NUMBER, 4, TimeUnit.MINUTES));
    admin.setQuota(QuotaSettingsFactory
        .throttleRegionServer(REGIONSERVER[0], ThrottleType.WRITE_NUMBER, 4, TimeUnit.MINUTES));
    admin.setQuota(QuotaSettingsFactory
        .throttleRegionServer(REGIONSERVER[1], ThrottleType.READ_NUMBER, 4, TimeUnit.MINUTES));
    admin.setQuota(QuotaSettingsFactory
        .throttleRegionServer(REGIONSERVER[1], ThrottleType.WRITE_NUMBER, 4, TimeUnit.MINUTES));

    admin.setQuota(QuotaSettingsFactory
        .unthrottleRegionServerByThrottleType(REGIONSERVER[0], ThrottleType.READ_NUMBER));
    assertEquals(3, getQuotaSettingCount(admin));
    admin.setQuota(QuotaSettingsFactory
        .unthrottleRegionServerByThrottleType(REGIONSERVER[0], ThrottleType.WRITE_NUMBER));
    assertEquals(2, getQuotaSettingCount(admin));
  }

  public int getQuotaSettingCount(Admin admin) throws IOException {
    List<QuotaSettings> list_quotas = admin.getQuota(new QuotaFilter());
    int quotaSettingCount = 0;
    for (QuotaSettings setting : list_quotas) {
      quotaSettingCount++;
      LOG.info("Quota Setting:" + setting);
    }
    return quotaSettingCount;
  }

  @Test
  public void testUserTableReadAndWriteThrottle() throws Exception {
    final Admin admin = TEST_UTIL.getAdmin();
    final String userName = User.getCurrent().getShortName();

    // Add 6req/min limit for write request on tables[0]
    admin.setQuota(QuotaSettingsFactory.throttleUser(userName, TABLE_NAMES[0],
      ThrottleType.WRITE_NUMBER, 6, TimeUnit.MINUTES));
    triggerUserCacheRefresh(TEST_UTIL, false, TABLE_NAMES[0]);

    // should execute at max 6 write requests and have no limit for read request
    assertEquals(6, doPuts(100, FAMILY, QUALIFIER, tables[0]));
    assertEquals(60, doGets(60, tables[0]));

    // no limit on tables[1]
    assertEquals(60, doPuts(60, FAMILY, QUALIFIER, tables[1]));
    assertEquals(60, doGets(60, tables[1]));

    // wait a minute and you should get other 6 write requests executed
    waitMinuteQuota();

    // Add 6req/min limit for read request on tables[0]
    admin.setQuota(QuotaSettingsFactory.throttleUser(userName, TABLE_NAMES[0],
      ThrottleType.READ_NUMBER, 6, TimeUnit.MINUTES));
    triggerUserCacheRefresh(TEST_UTIL, false, TABLE_NAMES[0]);

    // should execute at max 6 read requests and at max 6 write requests
    assertEquals(6, doPuts(100, FAMILY, QUALIFIER, tables[0]));
    assertEquals(6, doGets(60, tables[0]));

    // no limit on tables[1]
    assertEquals(30, doPuts(30, FAMILY, QUALIFIER, tables[1]));
    assertEquals(30, doGets(30, tables[1]));

    // Remove all the limits
    admin.setQuota(QuotaSettingsFactory.unthrottleUser(userName, TABLE_NAMES[0]));
    triggerUserCacheRefresh(TEST_UTIL, true, TABLE_NAMES);
    assertEquals(60, doPuts(60, FAMILY, QUALIFIER, tables));
    assertEquals(60, doGets(60, tables));
  }

  @Test
  public void testUserNamespaceThrottle() throws Exception {
    final Admin admin = TEST_UTIL.getAdmin();
    final String userName = User.getCurrent().getShortName();
    final String NAMESPACE = "default";

    // Add 6req/min limit
    admin.setQuota(QuotaSettingsFactory.throttleUser(userName, NAMESPACE,
      ThrottleType.REQUEST_NUMBER, 6, TimeUnit.MINUTES));
    triggerUserCacheRefresh(TEST_UTIL, false, TABLE_NAMES[0]);

    // should execute at max 6 requests on tables[0] and have no limit on tables[1]
    assertEquals(6, doPuts(100, FAMILY, QUALIFIER, tables[0]));

    // wait a minute and you should get other 6 requests executed
    waitMinuteQuota();
    assertEquals(6, doPuts(100, FAMILY, QUALIFIER, tables[1]));

    // Remove all the limits
    admin.setQuota(QuotaSettingsFactory.unthrottleUser(userName, NAMESPACE));
    triggerUserCacheRefresh(TEST_UTIL, true, TABLE_NAMES);
    assertEquals(60, doPuts(60, FAMILY, QUALIFIER, tables));
    assertEquals(60, doGets(60, tables));
  }

  @Test
  public void testUserNamespaceReadAndWriteThrottle() throws Exception {
    final Admin admin = TEST_UTIL.getAdmin();
    final String userName = User.getCurrent().getShortName();
    final String NAMESPACE = "default";

    // Add 6req/min limit for read request
    admin.setQuota(QuotaSettingsFactory.throttleUser(userName, NAMESPACE, ThrottleType.READ_NUMBER,
      6, TimeUnit.MINUTES));
    triggerUserCacheRefresh(TEST_UTIL, false, TABLE_NAMES[0]);

    // should execute at max 6 read requests and have no limit for write request
    assertEquals(6, doGets(60, tables[0]));
    assertEquals(60, doPuts(60, FAMILY, QUALIFIER, tables[0]));

    waitMinuteQuota();

    // Add 6req/min limit for write request, too
    admin.setQuota(QuotaSettingsFactory.throttleUser(userName, NAMESPACE, ThrottleType.WRITE_NUMBER,
      6, TimeUnit.MINUTES));
    triggerUserCacheRefresh(TEST_UTIL, false, TABLE_NAMES[0]);

    // should execute at max 6 read requests and at max 6 write requests
    assertEquals(6, doGets(60, tables[0]));
    assertEquals(6, doPuts(60, FAMILY, QUALIFIER, tables[0]));

    // Remove all the limits
    admin.setQuota(QuotaSettingsFactory.unthrottleUser(userName, NAMESPACE));
    triggerUserCacheRefresh(TEST_UTIL, true, TABLE_NAMES);
    assertEquals(60, doPuts(60, FAMILY, QUALIFIER, tables));
    assertEquals(60, doGets(60, tables));
  }

  @Test
  public void testTableGlobalThrottle() throws Exception {
    final Admin admin = TEST_UTIL.getAdmin();

    // Add 6req/min limit
    admin.setQuota(QuotaSettingsFactory.throttleTable(TABLE_NAMES[0], ThrottleType.REQUEST_NUMBER,
      6, TimeUnit.MINUTES));
    triggerTableCacheRefresh(TEST_UTIL, false, TABLE_NAMES[0]);

    // should execute at max 6 requests
    assertEquals(6, doPuts(100, FAMILY, QUALIFIER, tables[0]));
    // should have no limits
    assertEquals(30, doPuts(30, FAMILY, QUALIFIER, tables[1]));

    // wait a minute and you should get other 6 requests executed
    waitMinuteQuota();
    assertEquals(6, doPuts(100, FAMILY, QUALIFIER, tables[0]));

    // Remove all the limits
    admin.setQuota(QuotaSettingsFactory.unthrottleTable(TABLE_NAMES[0]));
    triggerTableCacheRefresh(TEST_UTIL, true, TABLE_NAMES[0]);
    assertEquals(80, doGets(80, tables[0], tables[1]));
  }

  @Test
  public void testTableGlobalReadAndWriteThrottle() throws Exception {
    final Admin admin = TEST_UTIL.getAdmin();

    // Add 6req/min limit for read request
    admin.setQuota(QuotaSettingsFactory.throttleTable(TABLE_NAMES[0], ThrottleType.READ_NUMBER, 6,
      TimeUnit.MINUTES));
    triggerTableCacheRefresh(TEST_UTIL, false, TABLE_NAMES[0]);

    // should execute at max 6 read requests and have no limit for write request
    assertEquals(6, doGets(100, tables[0]));
    assertEquals(100, doPuts(100, FAMILY, QUALIFIER, tables[0]));
    // should have no limits on tables[1]
    assertEquals(30, doPuts(30, FAMILY, QUALIFIER, tables[1]));
    assertEquals(30, doGets(30, tables[1]));

    // wait a minute and you should get other 6 requests executed
    waitMinuteQuota();

    // Add 6req/min limit for write request, too
    admin.setQuota(QuotaSettingsFactory.throttleTable(TABLE_NAMES[0], ThrottleType.WRITE_NUMBER, 6,
      TimeUnit.MINUTES));
    triggerTableCacheRefresh(TEST_UTIL, false, TABLE_NAMES[0]);

    // should execute at max 6 read requests and at max 6 write requests
    assertEquals(6, doGets(100, tables[0]));
    assertEquals(6, doPuts(100, FAMILY, QUALIFIER, tables[0]));
    // should have no limits on tables[1]
    assertEquals(30, doPuts(30, FAMILY, QUALIFIER, tables[1]));
    assertEquals(30, doGets(30, tables[1]));

    // Remove all the limits
    admin.setQuota(QuotaSettingsFactory.unthrottleTable(TABLE_NAMES[0]));
    triggerTableCacheRefresh(TEST_UTIL, true, TABLE_NAMES[0]);
    assertEquals(80, doGets(80, tables[0], tables[1]));
  }

  @Test
  public void testNamespaceGlobalThrottle() throws Exception {
    final Admin admin = TEST_UTIL.getAdmin();
    final String NAMESPACE = "default";

    // Add 6req/min limit
    admin.setQuota(QuotaSettingsFactory.throttleNamespace(NAMESPACE, ThrottleType.REQUEST_NUMBER, 6,
      TimeUnit.MINUTES));
    triggerNamespaceCacheRefresh(TEST_UTIL, false, TABLE_NAMES[0]);

    // should execute at max 6 requests
    assertEquals(6, doPuts(100, FAMILY, QUALIFIER, tables[0]));

    // wait a minute and you should get other 6 requests executed
    waitMinuteQuota();
    assertEquals(6, doPuts(100, FAMILY, QUALIFIER, tables[1]));

    admin.setQuota(QuotaSettingsFactory.unthrottleNamespace(NAMESPACE));
    triggerNamespaceCacheRefresh(TEST_UTIL, true, TABLE_NAMES[0]);
    assertEquals(40, doPuts(40, FAMILY, QUALIFIER, tables[0]));
  }

  @Test
  public void testNamespaceGlobalReadAndWriteThrottle() throws Exception {
    final Admin admin = TEST_UTIL.getAdmin();
    final String NAMESPACE = "default";

    // Add 6req/min limit for write request
    admin.setQuota(QuotaSettingsFactory.throttleNamespace(NAMESPACE, ThrottleType.WRITE_NUMBER, 6,
      TimeUnit.MINUTES));
    triggerNamespaceCacheRefresh(TEST_UTIL, false, TABLE_NAMES[0]);

    // should execute at max 6 write requests and no limit for read request
    assertEquals(6, doPuts(100, FAMILY, QUALIFIER, tables[0]));
    assertEquals(100, doGets(100, tables[0]));

    // wait a minute and you should get other 6 requests executed
    waitMinuteQuota();

    // Add 6req/min limit for read request, too
    admin.setQuota(QuotaSettingsFactory.throttleNamespace(NAMESPACE, ThrottleType.READ_NUMBER, 6,
      TimeUnit.MINUTES));
    triggerNamespaceCacheRefresh(TEST_UTIL, false, TABLE_NAMES[0]);

    // should execute at max 6 write requests and at max 6 read requests
    assertEquals(6, doPuts(100, FAMILY, QUALIFIER, tables[0]));
    assertEquals(6, doGets(100, tables[0]));

    admin.setQuota(QuotaSettingsFactory.unthrottleNamespace(NAMESPACE));
    triggerNamespaceCacheRefresh(TEST_UTIL, true, TABLE_NAMES[0]);
    assertEquals(40, doPuts(40, FAMILY, QUALIFIER, tables[0]));
  }

  @Test
  public void testUserAndTableThrottle() throws Exception {
    final Admin admin = TEST_UTIL.getAdmin();
    final String userName = User.getCurrent().getShortName();

    // Add 6req/min limit for the user on tables[0]
    admin.setQuota(QuotaSettingsFactory.throttleUser(userName, TABLE_NAMES[0],
      ThrottleType.REQUEST_NUMBER, 6, TimeUnit.MINUTES));
    triggerUserCacheRefresh(TEST_UTIL, false, TABLE_NAMES[0]);
    // Add 12req/min limit for the user
    admin.setQuota(QuotaSettingsFactory.throttleUser(userName, ThrottleType.REQUEST_NUMBER, 12,
      TimeUnit.MINUTES));
    triggerUserCacheRefresh(TEST_UTIL, false, TABLE_NAMES[1], TABLE_NAMES[2]);
    // Add 8req/min limit for the tables[1]
    admin.setQuota(QuotaSettingsFactory.throttleTable(TABLE_NAMES[1], ThrottleType.REQUEST_NUMBER,
      8, TimeUnit.MINUTES));
    triggerTableCacheRefresh(TEST_UTIL, false, TABLE_NAMES[1]);
    // Add a lower table level throttle on tables[0]
    admin.setQuota(QuotaSettingsFactory.throttleTable(TABLE_NAMES[0], ThrottleType.REQUEST_NUMBER,
      3, TimeUnit.MINUTES));
    triggerTableCacheRefresh(TEST_UTIL, false, TABLE_NAMES[0]);

    // should execute at max 12 requests
    assertEquals(12, doGets(100, tables[2]));

    // should execute at max 8 requests
    waitMinuteQuota();
    assertEquals(8, doGets(100, tables[1]));

    // should execute at max 3 requests
    waitMinuteQuota();
    assertEquals(3, doPuts(100, FAMILY, QUALIFIER, tables[0]));

    // Remove all the throttling rules
    admin.setQuota(QuotaSettingsFactory.unthrottleUser(userName, TABLE_NAMES[0]));
    admin.setQuota(QuotaSettingsFactory.unthrottleUser(userName));
    triggerUserCacheRefresh(TEST_UTIL, true, TABLE_NAMES[0], TABLE_NAMES[1]);

    admin.setQuota(QuotaSettingsFactory.unthrottleTable(TABLE_NAMES[1]));
    triggerTableCacheRefresh(TEST_UTIL, true, TABLE_NAMES[1]);
    waitMinuteQuota();
    assertEquals(40, doGets(40, tables[1]));

    admin.setQuota(QuotaSettingsFactory.unthrottleTable(TABLE_NAMES[0]));
    triggerTableCacheRefresh(TEST_UTIL, true, TABLE_NAMES[0]);
    waitMinuteQuota();
    assertEquals(40, doGets(40, tables[0]));
  }

  @Test
  public void testUserGlobalBypassThrottle() throws Exception {
    final Admin admin = TEST_UTIL.getAdmin();
    final String userName = User.getCurrent().getShortName();
    final String NAMESPACE = "default";

    // Add 6req/min limit for tables[0]
    admin.setQuota(QuotaSettingsFactory.throttleTable(TABLE_NAMES[0], ThrottleType.REQUEST_NUMBER,
      6, TimeUnit.MINUTES));
    triggerTableCacheRefresh(TEST_UTIL, false, TABLE_NAMES[0]);
    // Add 13req/min limit for the user
    admin.setQuota(QuotaSettingsFactory.throttleNamespace(NAMESPACE, ThrottleType.REQUEST_NUMBER,
      13, TimeUnit.MINUTES));
    triggerNamespaceCacheRefresh(TEST_UTIL, false, TABLE_NAMES[1]);

    // should execute at max 6 requests on table[0] and (13 - 6) on table[1]
    assertEquals(6, doPuts(100, FAMILY, QUALIFIER, tables[0]));
    assertEquals(7, doGets(100, tables[1]));
    waitMinuteQuota();

    // Set the global bypass for the user
    admin.setQuota(QuotaSettingsFactory.bypassGlobals(userName, true));
    admin.setQuota(QuotaSettingsFactory.throttleUser(userName, TABLE_NAMES[2],
      ThrottleType.REQUEST_NUMBER, 6, TimeUnit.MINUTES));
    triggerUserCacheRefresh(TEST_UTIL, false, TABLE_NAMES[2]);
    assertEquals(30, doGets(30, tables[0]));
    assertEquals(30, doGets(30, tables[1]));
    waitMinuteQuota();

    // Remove the global bypass
    // should execute at max 6 requests on table[0] and (13 - 6) on table[1]
    admin.setQuota(QuotaSettingsFactory.bypassGlobals(userName, false));
    admin.setQuota(QuotaSettingsFactory.unthrottleUser(userName, TABLE_NAMES[2]));
    triggerUserCacheRefresh(TEST_UTIL, true, TABLE_NAMES[2]);
    assertEquals(6, doPuts(100, FAMILY, QUALIFIER, tables[0]));
    assertEquals(7, doGets(100, tables[1]));

    // unset throttle
    admin.setQuota(QuotaSettingsFactory.unthrottleTable(TABLE_NAMES[0]));
    admin.setQuota(QuotaSettingsFactory.unthrottleNamespace(NAMESPACE));
    waitMinuteQuota();
    triggerTableCacheRefresh(TEST_UTIL, true, TABLE_NAMES[0]);
    triggerNamespaceCacheRefresh(TEST_UTIL, true, TABLE_NAMES[1]);
    assertEquals(30, doGets(30, tables[0]));
    assertEquals(30, doGets(30, tables[1]));
  }

  @Test
  public void testTableWriteCapacityUnitThrottle() throws Exception {
    final Admin admin = TEST_UTIL.getAdmin();

    // Add 6CU/min limit
    admin.setQuota(QuotaSettingsFactory.throttleTable(TABLE_NAMES[0],
      ThrottleType.WRITE_CAPACITY_UNIT, 6, TimeUnit.MINUTES));
    triggerTableCacheRefresh(TEST_UTIL, false, TABLE_NAMES[0]);

    // should execute at max 6 capacity units because each put size is 1 capacity unit
    assertEquals(6, doPuts(20, 10, FAMILY, QUALIFIER, tables[0]));

    // wait a minute and you should execute at max 3 capacity units because each put size is 2
    // capacity unit
    waitMinuteQuota();
    assertEquals(3, doPuts(20, 1025, FAMILY, QUALIFIER, tables[0]));

    admin.setQuota(QuotaSettingsFactory.unthrottleTable(TABLE_NAMES[0]));
    triggerTableCacheRefresh(TEST_UTIL, true, TABLE_NAMES[0]);
  }

  @Test
  public void testTableReadCapacityUnitThrottle() throws Exception {
    final Admin admin = TEST_UTIL.getAdmin();

    // Add 6CU/min limit
    admin.setQuota(QuotaSettingsFactory.throttleTable(TABLE_NAMES[0],
      ThrottleType.READ_CAPACITY_UNIT, 6, TimeUnit.MINUTES));
    triggerTableCacheRefresh(TEST_UTIL, false, TABLE_NAMES[0]);

    assertEquals(20, doPuts(20, 10, FAMILY, QUALIFIER, tables[0]));
    // should execute at max 6 capacity units because each get size is 1 capacity unit
    assertEquals(6, doGets(20, tables[0]));

    assertEquals(20, doPuts(20, 2015, FAMILY, QUALIFIER, tables[0]));
    // wait a minute and you should execute at max 3 capacity units because each get size is 2
    // capacity unit on tables[0]
    waitMinuteQuota();
    assertEquals(3, doGets(20, tables[0]));

    admin.setQuota(QuotaSettingsFactory.unthrottleTable(TABLE_NAMES[0]));
    triggerTableCacheRefresh(TEST_UTIL, true, TABLE_NAMES[0]);
  }

  @Test
  public void testTableExistsGetThrottle() throws Exception {
    final Admin admin = TEST_UTIL.getAdmin();

    // Add throttle quota
    admin.setQuota(QuotaSettingsFactory.throttleTable(TABLE_NAMES[0], ThrottleType.REQUEST_NUMBER,
      100, TimeUnit.MINUTES));
    triggerTableCacheRefresh(TEST_UTIL, false, TABLE_NAMES[0]);

    Table table = TEST_UTIL.getConnection().getTable(TABLE_NAMES[0]);
    // An exists call when having throttle quota
    table.exists(new Get(Bytes.toBytes("abc")));

    admin.setQuota(QuotaSettingsFactory.unthrottleTable(TABLE_NAMES[0]));
    triggerTableCacheRefresh(TEST_UTIL, true, TABLE_NAMES[0]);
  }

  @Test
  public void testRegionServerThrottle() throws Exception {
    final Admin admin = TEST_UTIL.getAdmin();
    admin.setQuota(QuotaSettingsFactory.throttleTable(TABLE_NAMES[0], ThrottleType.WRITE_NUMBER, 5,
      TimeUnit.MINUTES));

    // requests are throttled by table quota
    admin.setQuota(QuotaSettingsFactory.throttleRegionServer(
      QuotaTableUtil.QUOTA_REGION_SERVER_ROW_KEY, ThrottleType.WRITE_NUMBER, 7, TimeUnit.MINUTES));
    triggerTableCacheRefresh(TEST_UTIL, false, TABLE_NAMES[0]);
    triggerRegionServerCacheRefresh(TEST_UTIL, false);
    assertEquals(5, doPuts(10, FAMILY, QUALIFIER, tables[0]));
    triggerRegionServerCacheRefresh(TEST_UTIL, false);
    assertEquals(5, doPuts(10, FAMILY, QUALIFIER, tables[0]));

    // requests are throttled by region server quota
    admin.setQuota(QuotaSettingsFactory.throttleRegionServer(
      QuotaTableUtil.QUOTA_REGION_SERVER_ROW_KEY, ThrottleType.WRITE_NUMBER, 4, TimeUnit.MINUTES));
    triggerRegionServerCacheRefresh(TEST_UTIL, false);
    assertEquals(4, doPuts(10, FAMILY, QUALIFIER, tables[0]));
    triggerRegionServerCacheRefresh(TEST_UTIL, false);
    assertEquals(4, doPuts(10, FAMILY, QUALIFIER, tables[0]));

    // unthrottle
    admin.setQuota(
      QuotaSettingsFactory.unthrottleRegionServer(QuotaTableUtil.QUOTA_REGION_SERVER_ROW_KEY));
    triggerRegionServerCacheRefresh(TEST_UTIL, true);
    admin.setQuota(QuotaSettingsFactory.unthrottleTable(TABLE_NAMES[0]));
    triggerTableCacheRefresh(TEST_UTIL, true, TABLE_NAMES[0]);
    triggerRegionServerCacheRefresh(TEST_UTIL, true);
  }

  @Test
  public void testExceedThrottleQuota() throws Exception {
    final Admin admin = TEST_UTIL.getAdmin();
    admin.setQuota(QuotaSettingsFactory.throttleTable(TABLE_NAMES[0], ThrottleType.WRITE_NUMBER, 5,
      TimeUnit.MINUTES));
    triggerTableCacheRefresh(TEST_UTIL, false, TABLE_NAMES[0]);
    admin.setQuota(QuotaSettingsFactory.throttleRegionServer(
      QuotaTableUtil.QUOTA_REGION_SERVER_ROW_KEY, ThrottleType.WRITE_NUMBER, 20, TimeUnit.SECONDS));
    admin.setQuota(QuotaSettingsFactory.throttleRegionServer(
      QuotaTableUtil.QUOTA_REGION_SERVER_ROW_KEY, ThrottleType.READ_NUMBER, 10, TimeUnit.SECONDS));
    triggerRegionServerCacheRefresh(TEST_UTIL, false);

    // enable exceed throttle quota
    admin.exceedThrottleQuotaSwitch(true);
    // exceed table limit and allowed by region server limit
    triggerExceedThrottleQuotaCacheRefresh(TEST_UTIL, true);
    waitMinuteQuota();
    assertEquals(10, doPuts(10, FAMILY, QUALIFIER, tables[0]));
    // exceed table limit and throttled by region server limit
    waitMinuteQuota();
    assertEquals(20, doPuts(25, FAMILY, QUALIFIER, tables[0]));

    // set region server limiter is lower than table limiter
    admin.setQuota(QuotaSettingsFactory.throttleRegionServer(
      QuotaTableUtil.QUOTA_REGION_SERVER_ROW_KEY, ThrottleType.WRITE_NUMBER, 2, TimeUnit.SECONDS));
    triggerRegionServerCacheRefresh(TEST_UTIL, false);
    // throttled by region server limiter
    waitMinuteQuota();
    assertEquals(2, doPuts(10, FAMILY, QUALIFIER, tables[0]));
    admin.setQuota(QuotaSettingsFactory.throttleRegionServer(
      QuotaTableUtil.QUOTA_REGION_SERVER_ROW_KEY, ThrottleType.WRITE_NUMBER, 20, TimeUnit.SECONDS));
    triggerRegionServerCacheRefresh(TEST_UTIL, false);

    // disable exceed throttle quota
    admin.exceedThrottleQuotaSwitch(false);
    triggerExceedThrottleQuotaCacheRefresh(TEST_UTIL, false);
    waitMinuteQuota();
    // throttled by table limit
    assertEquals(5, doPuts(10, FAMILY, QUALIFIER, tables[0]));

    // enable exceed throttle quota and unthrottle region server
    admin.exceedThrottleQuotaSwitch(true);
    triggerExceedThrottleQuotaCacheRefresh(TEST_UTIL, true);
    waitMinuteQuota();
    admin.setQuota(
      QuotaSettingsFactory.unthrottleRegionServer(QuotaTableUtil.QUOTA_REGION_SERVER_ROW_KEY));
    triggerRegionServerCacheRefresh(TEST_UTIL, true);
    waitMinuteQuota();
    // throttled by table limit
    assertEquals(5, doPuts(10, FAMILY, QUALIFIER, tables[0]));

    // disable exceed throttle quota
    admin.exceedThrottleQuotaSwitch(false);
    triggerExceedThrottleQuotaCacheRefresh(TEST_UTIL, false);
    // unthrottle table
    admin.setQuota(QuotaSettingsFactory.unthrottleTable(TABLE_NAMES[0]));
    triggerTableCacheRefresh(TEST_UTIL, true, TABLE_NAMES[0]);
  }
}
