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