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.assertTrue;
021
022import java.util.ArrayList;
023import java.util.HashMap;
024import java.util.List;
025import java.util.Map;
026import java.util.TreeMap;
027import org.apache.hadoop.conf.Configuration;
028import org.apache.hadoop.hbase.HBaseClassTestRule;
029import org.apache.hadoop.hbase.HBaseConfiguration;
030import org.apache.hadoop.hbase.ServerName;
031import org.apache.hadoop.hbase.TableName;
032import org.apache.hadoop.hbase.client.RegionInfo;
033import org.apache.hadoop.hbase.master.LoadBalancer;
034import org.apache.hadoop.hbase.master.RegionPlan;
035import org.apache.hadoop.hbase.testclassification.MasterTests;
036import org.apache.hadoop.hbase.testclassification.MediumTests;
037import org.apache.hadoop.hbase.util.Pair;
038import org.apache.hadoop.net.DNSToSwitchMapping;
039import org.junit.BeforeClass;
040import org.junit.ClassRule;
041import org.junit.Rule;
042import org.junit.Test;
043import org.junit.experimental.categories.Category;
044import org.junit.rules.TestName;
045import org.slf4j.Logger;
046import org.slf4j.LoggerFactory;
047
048/**
049 * Test the load balancer that is created by default.
050 */
051@Category({MasterTests.class, MediumTests.class})
052public class TestDefaultLoadBalancer extends BalancerTestBase {
053
054  @ClassRule
055  public static final HBaseClassTestRule CLASS_RULE =
056      HBaseClassTestRule.forClass(TestDefaultLoadBalancer.class);
057
058  private static final Logger LOG = LoggerFactory.getLogger(TestDefaultLoadBalancer.class);
059
060  private static LoadBalancer loadBalancer;
061
062  @BeforeClass
063  public static void beforeAllTests() throws Exception {
064    Configuration conf = HBaseConfiguration.create();
065    conf.setClass("hbase.util.ip.to.rack.determiner", MockMapping.class, DNSToSwitchMapping.class);
066    conf.set("hbase.regions.slop", "0");
067    loadBalancer = new SimpleLoadBalancer();
068    loadBalancer.setConf(conf);
069  }
070
071  // int[testnum][servernumber] -> numregions
072  int[][] clusterStateMocks = new int[][] {
073      // 1 node
074      new int[] { 0 },
075      new int[] { 1 },
076      new int[] { 10 },
077      // 2 node
078      new int[] { 0, 0 },
079      new int[] { 2, 0 },
080      new int[] { 2, 1 },
081      new int[] { 2, 2 },
082      new int[] { 2, 3 },
083      new int[] { 2, 4 },
084      new int[] { 1, 1 },
085      new int[] { 0, 1 },
086      new int[] { 10, 1 },
087      new int[] { 14, 1432 },
088      new int[] { 47, 53 },
089      // 3 node
090      new int[] { 0, 1, 2 },
091      new int[] { 1, 2, 3 },
092      new int[] { 0, 2, 2 },
093      new int[] { 0, 3, 0 },
094      new int[] { 0, 4, 0 },
095      new int[] { 20, 20, 0 },
096      // 4 node
097      new int[] { 0, 1, 2, 3 },
098      new int[] { 4, 0, 0, 0 },
099      new int[] { 5, 0, 0, 0 },
100      new int[] { 6, 6, 0, 0 },
101      new int[] { 6, 2, 0, 0 },
102      new int[] { 6, 1, 0, 0 },
103      new int[] { 6, 0, 0, 0 },
104      new int[] { 4, 4, 4, 7 },
105      new int[] { 4, 4, 4, 8 },
106      new int[] { 0, 0, 0, 7 },
107      // 5 node
108      new int[] { 1, 1, 1, 1, 4 },
109      // more nodes
110      new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 },
111      new int[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 10 }, new int[] { 6, 6, 5, 6, 6, 6, 6, 6, 6, 1 },
112      new int[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 54 }, new int[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 55 },
113      new int[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 56 }, new int[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 16 },
114      new int[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 8 }, new int[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 9 },
115      new int[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 10 }, new int[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 123 },
116      new int[] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 155 },
117      new int[] { 0, 0, 144, 1, 1, 1, 1, 1123, 133, 138, 12, 1444 },
118      new int[] { 0, 0, 144, 1, 0, 4, 1, 1123, 133, 138, 12, 1444 },
119      new int[] { 1538, 1392, 1561, 1557, 1535, 1553, 1385, 1542, 1619 } };
120
121  int [] mockUniformCluster = new int[] { 5, 5, 5, 5, 5 ,0};
122
123  @Rule
124  public TestName name = new TestName();
125
126  /**
127   * Test the load balancing algorithm.
128   *
129   * Invariant is that all servers should be hosting either floor(average) or
130   * ceiling(average) at both table level and cluster level
131   *
132   * @throws Exception
133   */
134  @Test
135  public void testBalanceClusterOverall() throws Exception {
136    Map<TableName, Map<ServerName, List<RegionInfo>>> clusterLoad = new TreeMap<>();
137    for (int[] mockCluster : clusterStateMocks) {
138      Map<ServerName, List<RegionInfo>> clusterServers = mockClusterServers(mockCluster, 50);
139      List<ServerAndLoad> clusterList = convertToList(clusterServers);
140      clusterLoad.put(TableName.valueOf(name.getMethodName()), clusterServers);
141      HashMap<TableName, TreeMap<ServerName, List<RegionInfo>>> result = mockClusterServersWithTables(clusterServers);
142      loadBalancer.setClusterLoad(clusterLoad);
143      List<RegionPlan> clusterplans = new ArrayList<>();
144      List<Pair<TableName, Integer>> regionAmountList = new ArrayList<>();
145      for(TreeMap<ServerName, List<RegionInfo>> servers : result.values()){
146        List<ServerAndLoad> list = convertToList(servers);
147        LOG.info("Mock Cluster : " + printMock(list) + " " + printStats(list));
148        List<RegionPlan> partialplans = loadBalancer.balanceCluster(servers);
149        if(partialplans != null) clusterplans.addAll(partialplans);
150        List<ServerAndLoad> balancedClusterPerTable = reconcile(list, partialplans, servers);
151        LOG.info("Mock Balance : " + printMock(balancedClusterPerTable));
152        assertClusterAsBalanced(balancedClusterPerTable);
153        for (Map.Entry<ServerName, List<RegionInfo>> entry : servers.entrySet()) {
154          returnRegions(entry.getValue());
155          returnServer(entry.getKey());
156        }
157      }
158      List<ServerAndLoad> balancedCluster = reconcile(clusterList, clusterplans, clusterServers);
159      assertTrue(assertClusterOverallAsBalanced(balancedCluster, result.keySet().size()));
160    }
161  }
162
163  /**
164   * Test the load balancing algorithm.
165   *
166   * Invariant is that all servers should be hosting either floor(average) or
167   * ceiling(average) at both table level and cluster level
168   * Deliberately generate a special case to show the overall strategy can achieve cluster
169   * level balance while the bytable strategy cannot
170   * @throws Exception
171   */
172  @Test
173  public void testImpactOfBalanceClusterOverall() throws Exception {
174    Map<TableName, Map<ServerName, List<RegionInfo>>> clusterLoad = new TreeMap<>();
175    Map<ServerName, List<RegionInfo>> clusterServers = mockUniformClusterServers(mockUniformCluster);
176    List<ServerAndLoad> clusterList = convertToList(clusterServers);
177    clusterLoad.put(TableName.valueOf(name.getMethodName()), clusterServers);
178    // use overall can achieve both table and cluster level balance
179    HashMap<TableName, TreeMap<ServerName, List<RegionInfo>>> result1 = mockClusterServersWithTables(clusterServers);
180    loadBalancer.setClusterLoad(clusterLoad);
181    List<RegionPlan> clusterplans1 = new ArrayList<RegionPlan>();
182    List<Pair<TableName, Integer>> regionAmountList = new ArrayList<Pair<TableName, Integer>>();
183    for(TreeMap<ServerName, List<RegionInfo>> servers : result1.values()){
184      List<ServerAndLoad> list = convertToList(servers);
185      LOG.info("Mock Cluster : " + printMock(list) + " " + printStats(list));
186      List<RegionPlan> partialplans = loadBalancer.balanceCluster(servers);
187      if(partialplans != null) clusterplans1.addAll(partialplans);
188      List<ServerAndLoad> balancedClusterPerTable = reconcile(list, partialplans, servers);
189      LOG.info("Mock Balance : " + printMock(balancedClusterPerTable));
190      assertClusterAsBalanced(balancedClusterPerTable);
191      for (Map.Entry<ServerName, List<RegionInfo>> entry : servers.entrySet()) {
192        returnRegions(entry.getValue());
193        returnServer(entry.getKey());
194      }
195    }
196    List<ServerAndLoad> balancedCluster1 = reconcile(clusterList, clusterplans1, clusterServers);
197    assertTrue(assertClusterOverallAsBalanced(balancedCluster1, result1.keySet().size()));
198  }
199}