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.jupiter.api.Assertions.assertEquals;
021
022import java.util.List;
023import java.util.Map;
024import java.util.concurrent.TimeUnit;
025import java.util.stream.Stream;
026import org.apache.hadoop.conf.Configuration;
027import org.apache.hadoop.hbase.client.AsyncAdmin;
028import org.apache.hadoop.hbase.client.RegionInfo;
029import org.apache.hadoop.hbase.quotas.QuotaUtil;
030import org.apache.hadoop.hbase.testclassification.LargeTests;
031import org.apache.hadoop.hbase.testclassification.MiscTests;
032import org.apache.hadoop.hbase.util.Bytes;
033import org.junit.jupiter.api.AfterEach;
034import org.junit.jupiter.api.BeforeEach;
035import org.junit.jupiter.api.Tag;
036import org.junit.jupiter.api.TestTemplate;
037import org.junit.jupiter.params.provider.Arguments;
038import org.slf4j.Logger;
039import org.slf4j.LoggerFactory;
040
041import org.apache.hbase.thirdparty.com.google.common.collect.ImmutableMap;
042
043/**
044 * Test that we can split and merge the quota table given the presence of various configuration
045 * settings.
046 */
047@Tag(MiscTests.TAG)
048@Tag(LargeTests.TAG)
049@HBaseParameterizedTestTemplate(name = "{index}: configMap = {0}")
050public class TestSplitMergeQuotaTable {
051
052  private static final Logger LOG = LoggerFactory.getLogger(TestSplitMergeQuotaTable.class);
053
054  public static Stream<Arguments> parameters() {
055    return Stream.of(Arguments.of(ImmutableMap.of(QuotaUtil.QUOTA_CONF_KEY, "false")),
056      Arguments.of(ImmutableMap.of(QuotaUtil.QUOTA_CONF_KEY, "true")));
057  }
058
059  private final HBaseTestingUtil util = new HBaseTestingUtil();
060
061  private final TableName tableName = QuotaUtil.QUOTA_TABLE_NAME;
062
063  private Map<String, String> configMap;
064
065  public TestSplitMergeQuotaTable(Map<String, String> configMap) {
066    this.configMap = configMap;
067  }
068
069  @BeforeEach
070  public void setUp() throws Exception {
071    Configuration conf = util.getConfiguration();
072    conf.setInt(HConstants.HBASE_CLIENT_META_OPERATION_TIMEOUT, 1000);
073    conf.setInt(HConstants.HBASE_CLIENT_RETRIES_NUMBER, 2);
074    configMap.forEach(conf::set);
075    util.startMiniCluster();
076    if (!util.getAdmin().tableExists(QuotaUtil.QUOTA_TABLE_NAME)) {
077      util.getHBaseCluster().getMaster().createSystemTable(QuotaUtil.QUOTA_TABLE_DESC);
078    }
079  }
080
081  @AfterEach
082  public void tearDown() throws Exception {
083    util.shutdownMiniCluster();
084  }
085
086  @TestTemplate
087  public void testSplitMerge() throws Exception {
088    util.waitTableAvailable(tableName, 30_000);
089    AsyncAdmin admin = util.getAsyncConnection().getAdmin();
090    admin.split(tableName, Bytes.toBytes(0x10)).get(30, TimeUnit.SECONDS);
091    util.waitFor(30_000, new Waiter.ExplainingPredicate<Exception>() {
092
093      @Override
094      public boolean evaluate() throws Exception {
095        // can potentially observe the parent and both children via this interface.
096        return admin.getRegions(tableName)
097          .thenApply(val -> val.stream()
098            .filter(info -> info.getReplicaId() == RegionInfo.DEFAULT_REPLICA_ID).toList())
099          .get(30, TimeUnit.SECONDS).size() > 1;
100      }
101
102      @Override
103      public String explainFailure() {
104        return "Split has not finished yet";
105      }
106    });
107    util.waitUntilNoRegionsInTransition();
108    List<RegionInfo> regionInfos = admin.getRegions(tableName)
109      .thenApply(val -> val.stream()
110        .filter(info -> info.getReplicaId() == RegionInfo.DEFAULT_REPLICA_ID).toList())
111      .get(30, TimeUnit.SECONDS);
112    assertEquals(2, regionInfos.size());
113    LOG.info("{}", regionInfos);
114    admin.mergeRegions(regionInfos.stream().map(RegionInfo::getRegionName).toList(), false).get(30,
115      TimeUnit.SECONDS);
116    util.waitFor(30000, new Waiter.ExplainingPredicate<Exception>() {
117
118      @Override
119      public boolean evaluate() throws Exception {
120        // can potentially observe the parent and both children via this interface.
121        return admin.getRegions(tableName)
122          .thenApply(val -> val.stream()
123            .filter(info -> info.getReplicaId() == RegionInfo.DEFAULT_REPLICA_ID).toList())
124          .get(30, TimeUnit.SECONDS).size() == 1;
125      }
126
127      @Override
128      public String explainFailure() {
129        return "Merge has not finished yet";
130      }
131    });
132    assertEquals(1, admin.getRegions(tableName).get(30, TimeUnit.SECONDS).size());
133  }
134}