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