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