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.master.balancer; 019 020import static org.junit.jupiter.api.Assertions.assertEquals; 021import static org.junit.jupiter.api.Assertions.assertNotEquals; 022 023import java.io.IOException; 024import java.util.Collection; 025import java.util.List; 026import java.util.Map; 027import java.util.Set; 028import java.util.stream.Collectors; 029import org.apache.hadoop.hbase.HBaseTestingUtil; 030import org.apache.hadoop.hbase.HConstants; 031import org.apache.hadoop.hbase.HRegionLocation; 032import org.apache.hadoop.hbase.ServerName; 033import org.apache.hadoop.hbase.TableName; 034import org.apache.hadoop.hbase.client.Admin; 035import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder; 036import org.apache.hadoop.hbase.client.Connection; 037import org.apache.hadoop.hbase.client.RegionInfo; 038import org.apache.hadoop.hbase.client.TableDescriptor; 039import org.apache.hadoop.hbase.client.TableDescriptorBuilder; 040import org.apache.hadoop.hbase.quotas.QuotaUtil; 041import org.apache.hadoop.hbase.testclassification.LargeTests; 042import org.apache.hadoop.hbase.util.Bytes; 043import org.junit.jupiter.api.AfterEach; 044import org.junit.jupiter.api.BeforeEach; 045import org.junit.jupiter.api.Tag; 046import org.junit.jupiter.api.Test; 047import org.slf4j.Logger; 048import org.slf4j.LoggerFactory; 049 050import org.apache.hbase.thirdparty.com.google.common.collect.ImmutableSet; 051 052@Tag(LargeTests.TAG) 053public class TestMetaTableIsolationBalancerConditional { 054 055 private static final Logger LOG = 056 LoggerFactory.getLogger(TestMetaTableIsolationBalancerConditional.class); 057 private static final HBaseTestingUtil TEST_UTIL = new HBaseTestingUtil(); 058 059 private static final int NUM_SERVERS = 3; 060 061 @BeforeEach 062 public void setUp() throws Exception { 063 TEST_UTIL.getConfiguration().setBoolean(BalancerConditionals.ISOLATE_META_TABLE_KEY, true); 064 TEST_UTIL.getConfiguration().setBoolean(QuotaUtil.QUOTA_CONF_KEY, true); // for another table 065 TEST_UTIL.getConfiguration().setLong(HConstants.HBASE_BALANCER_PERIOD, 1000L); 066 TEST_UTIL.getConfiguration().setBoolean("hbase.master.balancer.stochastic.runMaxSteps", true); 067 068 TEST_UTIL.startMiniCluster(NUM_SERVERS); 069 } 070 071 @AfterEach 072 public void tearDown() throws Exception { 073 TEST_UTIL.shutdownMiniCluster(); 074 } 075 076 @Test 077 public void testTableIsolation() throws Exception { 078 Connection connection = TEST_UTIL.getConnection(); 079 Admin admin = connection.getAdmin(); 080 081 // Create "product" table with 3 regions 082 TableName productTableName = TableName.valueOf("product"); 083 TableDescriptor productTableDescriptor = TableDescriptorBuilder.newBuilder(productTableName) 084 .setColumnFamily(ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes("0")).build()) 085 .build(); 086 admin.createTable(productTableDescriptor, 087 BalancerConditionalsTestUtil.generateSplits(2 * NUM_SERVERS)); 088 089 Set<TableName> tablesToBeSeparated = ImmutableSet.<TableName> builder() 090 .add(TableName.META_TABLE_NAME).add(QuotaUtil.QUOTA_TABLE_NAME).add(productTableName).build(); 091 092 // Pause the balancer 093 admin.balancerSwitch(false, true); 094 095 // Move all regions (product, meta, and quotas) to one RegionServer 096 List<RegionInfo> allRegions = tablesToBeSeparated.stream().map(t -> { 097 try { 098 return admin.getRegions(t); 099 } catch (IOException e) { 100 throw new RuntimeException(e); 101 } 102 }).flatMap(Collection::stream).toList(); 103 String targetServer = 104 TEST_UTIL.getHBaseCluster().getRegionServer(0).getServerName().getServerName(); 105 for (RegionInfo region : allRegions) { 106 admin.move(region.getEncodedNameAsBytes(), Bytes.toBytes(targetServer)); 107 } 108 109 validateRegionLocationsWithRetry(connection, tablesToBeSeparated, productTableName, false, 110 false); 111 112 // Unpause the balancer and run it 113 admin.balancerSwitch(true, true); 114 admin.balance(); 115 116 validateRegionLocationsWithRetry(connection, tablesToBeSeparated, productTableName, true, true); 117 } 118 119 private static void validateRegionLocationsWithRetry(Connection connection, 120 Set<TableName> tableNames, TableName productTableName, boolean areDistributed, 121 boolean runBalancerOnFailure) throws InterruptedException, IOException { 122 for (int i = 0; i < 100; i++) { 123 Map<TableName, Set<ServerName>> tableToServers = getTableToServers(connection, tableNames); 124 try { 125 validateRegionLocations(tableToServers, productTableName, areDistributed); 126 } catch (AssertionError e) { 127 if (i == 99) { 128 throw e; 129 } 130 LOG.warn("Failed to validate region locations. Will retry", e); 131 BalancerConditionalsTestUtil.printRegionLocations(TEST_UTIL.getConnection()); 132 if (runBalancerOnFailure) { 133 connection.getAdmin().balance(); 134 } 135 Thread.sleep(1000); 136 } 137 } 138 } 139 140 private static void validateRegionLocations(Map<TableName, Set<ServerName>> tableToServers, 141 TableName productTableName, boolean shouldBeBalanced) { 142 // Validate that the region assignments 143 ServerName metaServer = 144 tableToServers.get(TableName.META_TABLE_NAME).stream().findFirst().orElseThrow(); 145 ServerName quotaServer = 146 tableToServers.get(QuotaUtil.QUOTA_TABLE_NAME).stream().findFirst().orElseThrow(); 147 Set<ServerName> productServers = tableToServers.get(productTableName); 148 149 if (shouldBeBalanced) { 150 assertNotEquals(metaServer, quotaServer, 151 "Meta table and quota table should not share a server"); 152 for (ServerName productServer : productServers) { 153 assertNotEquals(productServer, metaServer, 154 "Meta table and product table should not share servers"); 155 } 156 } else { 157 assertEquals(metaServer, quotaServer, "Quota table and product table must share servers"); 158 for (ServerName server : productServers) { 159 assertEquals(server, metaServer, "Meta table and product table must share servers"); 160 } 161 } 162 } 163 164 private static Map<TableName, Set<ServerName>> getTableToServers(Connection connection, 165 Set<TableName> tableNames) { 166 return tableNames.stream().collect(Collectors.toMap(t -> t, t -> { 167 try { 168 return connection.getRegionLocator(t).getAllRegionLocations().stream() 169 .map(HRegionLocation::getServerName).collect(Collectors.toSet()); 170 } catch (IOException e) { 171 throw new RuntimeException(e); 172 } 173 })); 174 } 175}