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; 022import static org.junit.Assert.assertNotNull; 023import static org.junit.Assert.assertTrue; 024 025import java.io.IOException; 026import java.util.Arrays; 027import java.util.EnumSet; 028import java.util.List; 029import java.util.Map; 030import org.apache.hadoop.conf.Configuration; 031import org.apache.hadoop.hbase.ClusterMetrics; 032import org.apache.hadoop.hbase.ClusterMetrics.Option; 033import org.apache.hadoop.hbase.HBaseClassTestRule; 034import org.apache.hadoop.hbase.HBaseTestingUtility; 035import org.apache.hadoop.hbase.HConstants; 036import org.apache.hadoop.hbase.MiniHBaseCluster; 037import org.apache.hadoop.hbase.ServerName; 038import org.apache.hadoop.hbase.TableName; 039import org.apache.hadoop.hbase.Waiter; 040import org.apache.hadoop.hbase.client.Admin; 041import org.apache.hadoop.hbase.client.ColumnFamilyDescriptor; 042import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder; 043import org.apache.hadoop.hbase.client.RegionInfo; 044import org.apache.hadoop.hbase.client.TableDescriptor; 045import org.apache.hadoop.hbase.client.TableDescriptorBuilder; 046import org.apache.hadoop.hbase.favored.FavoredNodesManager; 047import org.apache.hadoop.hbase.master.HMaster; 048import org.apache.hadoop.hbase.master.LoadBalancer; 049import org.apache.hadoop.hbase.master.RackManager; 050import org.apache.hadoop.hbase.master.assignment.RegionStates; 051import org.apache.hadoop.hbase.regionserver.HRegion; 052import org.apache.hadoop.hbase.regionserver.HRegionServer; 053import org.apache.hadoop.hbase.regionserver.Region; 054import org.apache.hadoop.hbase.testclassification.LargeTests; 055import org.apache.hadoop.hbase.util.Bytes; 056import org.apache.hadoop.hbase.util.JVMClusterUtil; 057import org.junit.After; 058import org.junit.Before; 059import org.junit.BeforeClass; 060import org.junit.ClassRule; 061import org.junit.Rule; 062import org.junit.Test; 063import org.junit.experimental.categories.Category; 064import org.junit.rules.TestName; 065import org.slf4j.Logger; 066import org.slf4j.LoggerFactory; 067 068import org.apache.hbase.thirdparty.com.google.common.collect.Lists; 069import org.apache.hbase.thirdparty.com.google.common.collect.Maps; 070 071@Category(LargeTests.class) 072public class TestFavoredStochasticBalancerPickers extends BalancerTestBase { 073 074 @ClassRule 075 public static final HBaseClassTestRule CLASS_RULE = 076 HBaseClassTestRule.forClass(TestFavoredStochasticBalancerPickers.class); 077 078 private static final Logger LOG = 079 LoggerFactory.getLogger(TestFavoredStochasticBalancerPickers.class); 080 081 private static final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility(); 082 private static final int SLAVES = 6; 083 private static final int REGIONS = SLAVES * 3; 084 private static Configuration conf; 085 086 private Admin admin; 087 private MiniHBaseCluster cluster; 088 089 @Rule 090 public TestName name = new TestName(); 091 092 @BeforeClass 093 public static void setupBeforeClass() throws Exception { 094 conf = TEST_UTIL.getConfiguration(); 095 // Enable favored nodes based load balancer 096 conf.setClass(HConstants.HBASE_MASTER_LOADBALANCER_CLASS, 097 LoadOnlyFavoredStochasticBalancer.class, LoadBalancer.class); 098 conf.setLong("hbase.master.balancer.stochastic.maxRunningTime", 30000); 099 conf.setInt("hbase.master.balancer.stochastic.moveCost", 0); 100 conf.setBoolean("hbase.master.balancer.stochastic.execute.maxSteps", true); 101 conf.set(BaseLoadBalancer.TABLES_ON_MASTER, "none"); 102 } 103 104 @Before 105 public void startCluster() throws Exception { 106 TEST_UTIL.startMiniCluster(SLAVES); 107 TEST_UTIL.getDFSCluster().waitClusterUp(); 108 TEST_UTIL.getHBaseCluster().waitForActiveAndReadyMaster(120 * 1000); 109 cluster = TEST_UTIL.getMiniHBaseCluster(); 110 admin = TEST_UTIL.getAdmin(); 111 admin.setBalancerRunning(false, true); 112 } 113 114 @After 115 public void stopCluster() throws Exception { 116 TEST_UTIL.cleanupTestDir(); 117 TEST_UTIL.shutdownMiniCluster(); 118 } 119 120 @Test 121 public void testPickers() throws Exception { 122 TableName tableName = TableName.valueOf(name.getMethodName()); 123 ColumnFamilyDescriptor columnFamilyDescriptor = 124 ColumnFamilyDescriptorBuilder.newBuilder(HConstants.CATALOG_FAMILY).build(); 125 TableDescriptor desc = 126 TableDescriptorBuilder.newBuilder(tableName).setColumnFamily(columnFamilyDescriptor).build(); 127 admin.createTable(desc, Bytes.toBytes("aaa"), Bytes.toBytes("zzz"), REGIONS); 128 TEST_UTIL.waitUntilAllRegionsAssigned(tableName); 129 TEST_UTIL.loadTable(admin.getConnection().getTable(tableName), HConstants.CATALOG_FAMILY); 130 admin.flush(tableName); 131 132 HMaster master = cluster.getMaster(); 133 FavoredNodesManager fnm = master.getFavoredNodesManager(); 134 ServerName masterServerName = master.getServerName(); 135 List<ServerName> excludedServers = Lists.newArrayList(masterServerName); 136 final ServerName mostLoadedServer = getRSWithMaxRegions(tableName, excludedServers); 137 assertNotNull(mostLoadedServer); 138 int numRegions = getTableRegionsFromServer(tableName, mostLoadedServer).size(); 139 excludedServers.add(mostLoadedServer); 140 // Lets find another server with more regions to calculate number of regions to move 141 ServerName source = getRSWithMaxRegions(tableName, excludedServers); 142 assertNotNull(source); 143 int regionsToMove = getTableRegionsFromServer(tableName, source).size() / 2; 144 145 // Since move only works if the target is part of favored nodes of the region, lets get all 146 // regions that are movable to mostLoadedServer 147 List<RegionInfo> hris = getRegionsThatCanBeMoved(tableName, mostLoadedServer); 148 RegionStates rst = master.getAssignmentManager().getRegionStates(); 149 for (int i = 0; i < regionsToMove; i++) { 150 final RegionInfo regionInfo = hris.get(i); 151 admin.move(regionInfo.getEncodedNameAsBytes(), mostLoadedServer); 152 LOG.info("Moving region: " + hris.get(i).getRegionNameAsString() + " to " + mostLoadedServer); 153 TEST_UTIL.waitFor(60000, new Waiter.Predicate<Exception>() { 154 @Override 155 public boolean evaluate() throws Exception { 156 return ServerName.isSameAddress(rst.getRegionServerOfRegion(regionInfo), 157 mostLoadedServer); 158 } 159 }); 160 } 161 final int finalRegions = numRegions + regionsToMove; 162 TEST_UTIL.waitUntilNoRegionsInTransition(60000); 163 TEST_UTIL.waitFor(60000, new Waiter.Predicate<Exception>() { 164 @Override 165 public boolean evaluate() throws Exception { 166 int numRegions = getTableRegionsFromServer(tableName, mostLoadedServer).size(); 167 return (numRegions == finalRegions); 168 } 169 }); 170 TEST_UTIL.getHBaseCluster().startRegionServerAndWait(60000); 171 172 Map<ServerName, List<RegionInfo>> serverAssignments = Maps.newHashMap(); 173 ClusterMetrics status = admin.getClusterMetrics(EnumSet.of(Option.LIVE_SERVERS)); 174 for (ServerName sn : status.getLiveServerMetrics().keySet()) { 175 if (!ServerName.isSameAddress(sn, masterServerName)) { 176 serverAssignments.put(sn, getTableRegionsFromServer(tableName, sn)); 177 } 178 } 179 RegionLocationFinder regionFinder = new RegionLocationFinder(); 180 regionFinder.setClusterMetrics(admin.getClusterMetrics(EnumSet.of(Option.LIVE_SERVERS))); 181 regionFinder.setConf(conf); 182 regionFinder.setServices(TEST_UTIL.getMiniHBaseCluster().getMaster()); 183 BalancerClusterState cluster = 184 new BalancerClusterState(serverAssignments, null, regionFinder, new RackManager(conf)); 185 LoadOnlyFavoredStochasticBalancer balancer = (LoadOnlyFavoredStochasticBalancer) TEST_UTIL 186 .getMiniHBaseCluster().getMaster().getLoadBalancer(); 187 188 cluster.sortServersByRegionCount(); 189 Integer[] servers = cluster.serverIndicesSortedByRegionCount; 190 LOG.info("Servers sorted by region count:" + Arrays.toString(servers)); 191 LOG.info("Cluster dump: " + cluster); 192 if (!mostLoadedServer.equals(cluster.servers[servers[servers.length - 1]])) { 193 LOG.error("Most loaded server: " + mostLoadedServer + " does not match: " 194 + cluster.servers[servers[servers.length - 1]]); 195 } 196 assertEquals(mostLoadedServer, cluster.servers[servers[servers.length - 1]]); 197 FavoredStochasticBalancer.FavoredNodeLoadPicker loadPicker = 198 balancer.new FavoredNodeLoadPicker(); 199 boolean userRegionPicked = false; 200 for (int i = 0; i < 100; i++) { 201 if (userRegionPicked) { 202 break; 203 } else { 204 BalanceAction action = loadPicker.generate(cluster); 205 if (action.getType() == BalanceAction.Type.MOVE_REGION) { 206 MoveRegionAction moveRegionAction = (MoveRegionAction) action; 207 RegionInfo region = cluster.regions[moveRegionAction.getRegion()]; 208 assertNotEquals(-1, moveRegionAction.getToServer()); 209 ServerName destinationServer = cluster.servers[moveRegionAction.getToServer()]; 210 assertEquals(cluster.servers[moveRegionAction.getFromServer()], mostLoadedServer); 211 if (!region.getTable().isSystemTable()) { 212 List<ServerName> favNodes = fnm.getFavoredNodes(region); 213 assertTrue(favNodes.contains(ServerName.valueOf(destinationServer.getAddress(), -1))); 214 userRegionPicked = true; 215 } 216 } 217 } 218 } 219 assertTrue("load picker did not pick expected regions in 100 iterations.", userRegionPicked); 220 } 221 222 /* 223 * A region can only be moved to one of its favored node. Hence this method helps us to get that 224 * list which makes it easy to write non-flaky tests. 225 */ 226 private List<RegionInfo> getRegionsThatCanBeMoved(TableName tableName, ServerName serverName) { 227 List<RegionInfo> regions = Lists.newArrayList(); 228 RegionStates rst = cluster.getMaster().getAssignmentManager().getRegionStates(); 229 FavoredNodesManager fnm = cluster.getMaster().getFavoredNodesManager(); 230 for (RegionInfo regionInfo : fnm.getRegionsOfFavoredNode(serverName)) { 231 if ( 232 regionInfo.getTable().equals(tableName) 233 && !ServerName.isSameAddress(rst.getRegionServerOfRegion(regionInfo), serverName) 234 ) { 235 regions.add(regionInfo); 236 } 237 } 238 return regions; 239 } 240 241 private List<RegionInfo> getTableRegionsFromServer(TableName tableName, ServerName source) 242 throws IOException { 243 List<RegionInfo> regionInfos = Lists.newArrayList(); 244 HRegionServer regionServer = cluster.getRegionServer(source); 245 for (Region region : regionServer.getRegions(tableName)) { 246 regionInfos.add(region.getRegionInfo()); 247 } 248 return regionInfos; 249 } 250 251 private ServerName getRSWithMaxRegions(TableName tableName, List<ServerName> excludeNodes) 252 throws IOException { 253 254 int maxRegions = 0; 255 ServerName maxLoadedServer = null; 256 257 for (JVMClusterUtil.RegionServerThread rst : cluster.getLiveRegionServerThreads()) { 258 List<HRegion> regions = rst.getRegionServer().getRegions(tableName); 259 LOG.debug("Server: " + rst.getRegionServer().getServerName() + " regions: " + regions.size()); 260 if (regions.size() > maxRegions) { 261 if ( 262 excludeNodes == null 263 || !doesMatchExcludeNodes(excludeNodes, rst.getRegionServer().getServerName()) 264 ) { 265 maxRegions = regions.size(); 266 maxLoadedServer = rst.getRegionServer().getServerName(); 267 } 268 } 269 } 270 return maxLoadedServer; 271 } 272 273 private boolean doesMatchExcludeNodes(List<ServerName> excludeNodes, ServerName sn) { 274 for (ServerName excludeSN : excludeNodes) { 275 if (ServerName.isSameAddress(sn, excludeSN)) { 276 return true; 277 } 278 } 279 return false; 280 } 281}