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.assertTrue; 022 023import java.util.ArrayList; 024import java.util.Collections; 025import java.util.HashMap; 026import java.util.HashSet; 027import java.util.Iterator; 028import java.util.List; 029import java.util.Map; 030import java.util.Set; 031import org.apache.commons.lang3.StringUtils; 032import org.apache.hadoop.conf.Configuration; 033import org.apache.hadoop.hbase.HBaseClassTestRule; 034import org.apache.hadoop.hbase.HBaseConfiguration; 035import org.apache.hadoop.hbase.HConstants; 036import org.apache.hadoop.hbase.ServerName; 037import org.apache.hadoop.hbase.TableName; 038import org.apache.hadoop.hbase.client.RegionInfo; 039import org.apache.hadoop.hbase.master.LoadBalancer; 040import org.apache.hadoop.hbase.master.RegionPlan; 041import org.apache.hadoop.hbase.net.Address; 042import org.apache.hadoop.hbase.rsgroup.RSGroupBasedLoadBalancer; 043import org.apache.hadoop.hbase.rsgroup.RSGroupInfo; 044import org.apache.hadoop.hbase.testclassification.LargeTests; 045import org.junit.BeforeClass; 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.ArrayListMultimap; 053/** 054 * Test RSGroupBasedLoadBalancer with SimpleLoadBalancer as internal balancer 055 */ 056@Category(LargeTests.class) 057public class TestRSGroupBasedLoadBalancer extends RSGroupableBalancerTestBase { 058 @ClassRule 059 public static final HBaseClassTestRule CLASS_RULE = 060 HBaseClassTestRule.forClass(TestRSGroupBasedLoadBalancer.class); 061 private static final Logger LOG = LoggerFactory.getLogger(TestRSGroupBasedLoadBalancer.class); 062 private static RSGroupBasedLoadBalancer loadBalancer; 063 064 @BeforeClass 065 public static void beforeAllTests() throws Exception { 066 servers = generateServers(7); 067 groupMap = constructGroupInfo(servers, groups); 068 tableDescs = constructTableDesc(true); 069 Configuration conf = HBaseConfiguration.create(); 070 conf.set("hbase.regions.slop", "0"); 071 conf.set("hbase.rsgroup.grouploadbalancer.class", SimpleLoadBalancer.class.getCanonicalName()); 072 loadBalancer = new RSGroupBasedLoadBalancer(); 073 loadBalancer.setRsGroupInfoManager(getMockedGroupInfoManager()); 074 loadBalancer.setMasterServices(getMockedMaster()); 075 loadBalancer.setConf(conf); 076 loadBalancer.initialize(); 077 } 078 079 /** 080 * Test the load balancing algorithm. 081 * 082 * Invariant is that all servers of the group should be hosting either floor(average) or 083 * ceiling(average) 084 */ 085 @Test 086 public void testBalanceCluster() throws Exception { 087 // Test with/without per table balancer. 088 boolean[] perTableBalancerConfigs = { true, false }; 089 for (boolean isByTable : perTableBalancerConfigs) { 090 Configuration conf = loadBalancer.getConf(); 091 conf.setBoolean(HConstants.HBASE_MASTER_LOADBALANCE_BYTABLE, isByTable); 092 loadBalancer.setConf(conf); 093 Map<ServerName, List<RegionInfo>> servers = mockClusterServers(); 094 ArrayListMultimap<String, ServerAndLoad> list = convertToGroupBasedMap(servers); 095 LOG.info("Mock Cluster : " + printStats(list)); 096 Map<TableName, Map<ServerName, List<RegionInfo>>> LoadOfAllTable = 097 (Map) mockClusterServersWithTables(servers); 098 List<RegionPlan> plans = loadBalancer.balanceCluster(LoadOfAllTable); 099 ArrayListMultimap<String, ServerAndLoad> balancedCluster = reconcile(list, plans); 100 LOG.info("Mock Balance : " + printStats(balancedCluster)); 101 assertClusterAsBalanced(balancedCluster); 102 } 103 } 104 105 /** 106 * Tests the bulk assignment used during cluster startup. 107 * 108 * Round-robin. Should yield a balanced cluster so same invariant as the 109 * load balancer holds, all servers holding either floor(avg) or 110 * ceiling(avg). 111 */ 112 @Test 113 public void testBulkAssignment() throws Exception { 114 List<RegionInfo> regions = randomRegions(25); 115 Map<ServerName, List<RegionInfo>> assignments = loadBalancer 116 .roundRobinAssignment(regions, servers); 117 //test empty region/servers scenario 118 //this should not throw an NPE 119 loadBalancer.roundRobinAssignment(regions, Collections.emptyList()); 120 //test regular scenario 121 assertTrue(assignments.keySet().size() == servers.size()); 122 for (ServerName sn : assignments.keySet()) { 123 List<RegionInfo> regionAssigned = assignments.get(sn); 124 for (RegionInfo region : regionAssigned) { 125 TableName tableName = region.getTable(); 126 String groupName = 127 getMockedGroupInfoManager().getRSGroupOfTable(tableName); 128 assertTrue(StringUtils.isNotEmpty(groupName)); 129 RSGroupInfo gInfo = getMockedGroupInfoManager().getRSGroup( 130 groupName); 131 assertTrue( 132 "Region is not correctly assigned to group servers.", 133 gInfo.containsServer(sn.getAddress())); 134 } 135 } 136 ArrayListMultimap<String, ServerAndLoad> loadMap = convertToGroupBasedMap(assignments); 137 assertClusterAsBalanced(loadMap); 138 } 139 140 /** 141 * Test the cluster startup bulk assignment which attempts to retain assignment info. 142 */ 143 @Test 144 public void testRetainAssignment() throws Exception { 145 // Test simple case where all same servers are there 146 Map<ServerName, List<RegionInfo>> currentAssignments = mockClusterServers(); 147 Map<RegionInfo, ServerName> inputForTest = new HashMap<>(); 148 for (ServerName sn : currentAssignments.keySet()) { 149 for (RegionInfo region : currentAssignments.get(sn)) { 150 inputForTest.put(region, sn); 151 } 152 } 153 //verify region->null server assignment is handled 154 inputForTest.put(randomRegions(1).get(0), null); 155 Map<ServerName, List<RegionInfo>> newAssignment = loadBalancer 156 .retainAssignment(inputForTest, servers); 157 assertRetainedAssignment(inputForTest, servers, newAssignment); 158 } 159 160 /** 161 * Test BOGUS_SERVER_NAME among groups do not overwrite each other. 162 */ 163 @Test 164 public void testRoundRobinAssignment() throws Exception { 165 List<ServerName> onlineServers = new ArrayList<ServerName>(servers.size()); 166 onlineServers.addAll(servers); 167 List<RegionInfo> regions = randomRegions(25); 168 int bogusRegion = 0; 169 for(RegionInfo region : regions){ 170 String group = tableMap.get(region.getTable()); 171 if("dg3".equals(group) || "dg4".equals(group)){ 172 bogusRegion++; 173 } 174 } 175 Set<Address> offlineServers = new HashSet<Address>(); 176 offlineServers.addAll(groupMap.get("dg3").getServers()); 177 offlineServers.addAll(groupMap.get("dg4").getServers()); 178 for(Iterator<ServerName> it = onlineServers.iterator(); it.hasNext();){ 179 ServerName server = it.next(); 180 Address address = server.getAddress(); 181 if(offlineServers.contains(address)){ 182 it.remove(); 183 } 184 } 185 Map<ServerName, List<RegionInfo>> assignments = loadBalancer 186 .roundRobinAssignment(regions, onlineServers); 187 assertEquals(bogusRegion, assignments.get(LoadBalancer.BOGUS_SERVER_NAME).size()); 188 } 189}