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}