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;
019
020import static org.junit.Assert.assertEquals;
021
022import java.util.List;
023import java.util.Map;
024import java.util.concurrent.TimeUnit;
025import org.apache.hadoop.conf.Configuration;
026import org.apache.hadoop.hbase.client.AsyncAdmin;
027import org.apache.hadoop.hbase.client.RegionInfo;
028import org.apache.hadoop.hbase.quotas.QuotaUtil;
029import org.apache.hadoop.hbase.testclassification.LargeTests;
030import org.apache.hadoop.hbase.testclassification.MiscTests;
031import org.apache.hadoop.hbase.util.Bytes;
032import org.junit.ClassRule;
033import org.junit.Rule;
034import org.junit.Test;
035import org.junit.experimental.categories.Category;
036import org.junit.rules.ExternalResource;
037import org.junit.rules.RuleChain;
038import org.junit.rules.TestRule;
039import org.junit.runner.RunWith;
040import org.junit.runners.Parameterized;
041import org.slf4j.Logger;
042import org.slf4j.LoggerFactory;
043
044/**
045 * Test that we can split and merge the quota table given the presence of various configuration
046 * settings.
047 */
048@Category({ MiscTests.class, LargeTests.class })
049@RunWith(Parameterized.class)
050public class TestSplitMergeQuotaTable {
051
052  private static final Logger LOG = LoggerFactory.getLogger(TestSplitMergeQuotaTable.class);
053
054  @ClassRule
055  public static final HBaseClassTestRule CLASS_RULE =
056    HBaseClassTestRule.forClass(TestSplitMergeQuotaTable.class);
057
058  @Parameterized.Parameters(name = "{1}")
059  public static Object[][] params() {
060    return new Object[][] { { Map.of(QuotaUtil.QUOTA_CONF_KEY, "false") },
061      { Map.of(QuotaUtil.QUOTA_CONF_KEY, "true") }, };
062  }
063
064  private final TableName tableName = QuotaUtil.QUOTA_TABLE_NAME;
065  private final MiniClusterRule miniClusterRule;
066
067  @Rule
068  public final RuleChain ruleChain;
069
070  public TestSplitMergeQuotaTable(Map<String, String> configMap) {
071    this.miniClusterRule = MiniClusterRule.newBuilder().setConfiguration(() -> {
072      Configuration conf = HBaseConfiguration.create();
073      conf.setInt(HConstants.HBASE_CLIENT_META_OPERATION_TIMEOUT, 1000);
074      conf.setInt(HConstants.HBASE_CLIENT_RETRIES_NUMBER, 2);
075      configMap.forEach(conf::set);
076      return conf;
077    }).build();
078    TestRule ensureQuotaTableRule = new ExternalResource() {
079      @Override
080      protected void before() throws Throwable {
081        if (
082          !miniClusterRule.getTestingUtility().getAsyncConnection().getAdmin()
083            .tableExists(QuotaUtil.QUOTA_TABLE_NAME).get(30, TimeUnit.SECONDS)
084        ) {
085          miniClusterRule.getTestingUtility().getHBaseCluster().getMaster()
086            .createSystemTable(QuotaUtil.QUOTA_TABLE_DESC);
087        }
088      }
089    };
090    this.ruleChain = RuleChain.outerRule(miniClusterRule).around(ensureQuotaTableRule);
091  }
092
093  @Test
094  public void testSplitMerge() throws Exception {
095    HBaseTestingUtil util = miniClusterRule.getTestingUtility();
096    util.waitTableAvailable(tableName, 30_000);
097    AsyncAdmin admin = util.getAsyncConnection().getAdmin();
098    admin.split(tableName, Bytes.toBytes(0x10)).get(30, TimeUnit.SECONDS);
099    util.waitFor(30_000, new Waiter.ExplainingPredicate<Exception>() {
100
101      @Override
102      public boolean evaluate() throws Exception {
103        // can potentially observe the parent and both children via this interface.
104        return admin.getRegions(tableName)
105          .thenApply(val -> val.stream()
106            .filter(info -> info.getReplicaId() == RegionInfo.DEFAULT_REPLICA_ID).toList())
107          .get(30, TimeUnit.SECONDS).size() > 1;
108      }
109
110      @Override
111      public String explainFailure() {
112        return "Split has not finished yet";
113      }
114    });
115    util.waitUntilNoRegionsInTransition();
116    List<RegionInfo> regionInfos = admin.getRegions(tableName)
117      .thenApply(val -> val.stream()
118        .filter(info -> info.getReplicaId() == RegionInfo.DEFAULT_REPLICA_ID).toList())
119      .get(30, TimeUnit.SECONDS);
120    assertEquals(2, regionInfos.size());
121    LOG.info("{}", regionInfos);
122    admin.mergeRegions(regionInfos.stream().map(RegionInfo::getRegionName).toList(), false).get(30,
123      TimeUnit.SECONDS);
124    util.waitFor(30000, new Waiter.ExplainingPredicate<Exception>() {
125
126      @Override
127      public boolean evaluate() throws Exception {
128        // can potentially observe the parent and both children via this interface.
129        return admin.getRegions(tableName)
130          .thenApply(val -> val.stream()
131            .filter(info -> info.getReplicaId() == RegionInfo.DEFAULT_REPLICA_ID).toList())
132          .get(30, TimeUnit.SECONDS).size() == 1;
133      }
134
135      @Override
136      public String explainFailure() {
137        return "Merge has not finished yet";
138      }
139    });
140    assertEquals(1, admin.getRegions(tableName).get(30, TimeUnit.SECONDS).size());
141  }
142}