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.HashMap;
025import java.util.List;
026import java.util.Map;
027import java.util.TreeMap;
028import org.apache.hadoop.conf.Configuration;
029import org.apache.hadoop.hbase.HBaseClassTestRule;
030import org.apache.hadoop.hbase.HBaseConfiguration;
031import org.apache.hadoop.hbase.ServerName;
032import org.apache.hadoop.hbase.TableName;
033import org.apache.hadoop.hbase.client.RegionInfo;
034import org.apache.hadoop.hbase.master.RegionPlan;
035import org.apache.hadoop.hbase.testclassification.MasterTests;
036import org.apache.hadoop.hbase.testclassification.SmallTests;
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, SmallTests.class})
052public class TestSimpleLoadBalancer extends BalancerTestBase {
053
054  @ClassRule
055  public static final HBaseClassTestRule CLASS_RULE =
056      HBaseClassTestRule.forClass(TestSimpleLoadBalancer.class);
057
058  private static final Logger LOG = LoggerFactory.getLogger(TestSimpleLoadBalancer.class);
059
060  private static SimpleLoadBalancer 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   */
133  @Test
134  public void testBalanceClusterOverall() throws Exception {
135    Map<TableName, Map<ServerName, List<RegionInfo>>> clusterLoad = new TreeMap<>();
136    for (int[] mockCluster : clusterStateMocks) {
137      Map<ServerName, List<RegionInfo>> clusterServers = mockClusterServers(mockCluster, 30);
138      List<ServerAndLoad> clusterList = convertToList(clusterServers);
139      clusterLoad.put(TableName.valueOf(name.getMethodName()), clusterServers);
140      HashMap<TableName, TreeMap<ServerName, List<RegionInfo>>> result =
141          mockClusterServersWithTables(clusterServers);
142      loadBalancer.setClusterLoad(clusterLoad);
143      List<RegionPlan> clusterplans = new ArrayList<>();
144      List<Pair<TableName, Integer>> regionAmountList = new ArrayList<>();
145      for (Map.Entry<TableName, TreeMap<ServerName, List<RegionInfo>>> mapEntry : result
146          .entrySet()) {
147        TableName tableName = mapEntry.getKey();
148        TreeMap<ServerName, List<RegionInfo>> servers = mapEntry.getValue();
149        List<ServerAndLoad> list = convertToList(servers);
150        LOG.info("Mock Cluster : " + printMock(list) + " " + printStats(list));
151        List<RegionPlan> partialplans = loadBalancer.balanceTable(tableName, servers);
152        if(partialplans != null) clusterplans.addAll(partialplans);
153        List<ServerAndLoad> balancedClusterPerTable = reconcile(list, partialplans, servers);
154        LOG.info("Mock Balance : " + printMock(balancedClusterPerTable));
155        assertClusterAsBalanced(balancedClusterPerTable);
156        for (Map.Entry<ServerName, List<RegionInfo>> entry : servers.entrySet()) {
157          returnRegions(entry.getValue());
158          returnServer(entry.getKey());
159        }
160      }
161      List<ServerAndLoad> balancedCluster = reconcile(clusterList, clusterplans, clusterServers);
162      assertTrue(assertClusterOverallAsBalanced(balancedCluster, result.keySet().size()));
163    }
164  }
165
166  /**
167   * Test the load balancing algorithm.
168   *
169   * Invariant is that all servers should be hosting either floor(average) or
170   * ceiling(average) at both table level and cluster level
171   * Deliberately generate a special case to show the overall strategy can achieve cluster
172   * level balance while the bytable strategy cannot
173   * @throws Exception
174   */
175  @Test
176  public void testImpactOfBalanceClusterOverall() throws Exception {
177    testImpactOfBalanceClusterOverall(false);
178  }
179
180  @Test
181  public void testImpactOfBalanceClusterOverallWithLoadOfAllTable() throws Exception {
182    testImpactOfBalanceClusterOverall(true);
183  }
184
185  private void testImpactOfBalanceClusterOverall(boolean useLoadOfAllTable) throws Exception {
186    Map<TableName, Map<ServerName, List<RegionInfo>>> clusterLoad = new TreeMap<>();
187    Map<ServerName, List<RegionInfo>> clusterServers =
188        mockUniformClusterServers(mockUniformCluster);
189    List<ServerAndLoad> clusterList = convertToList(clusterServers);
190    clusterLoad.put(TableName.valueOf(name.getMethodName()), clusterServers);
191    // use overall can achieve both table and cluster level balance
192    HashMap<TableName, TreeMap<ServerName, List<RegionInfo>>> LoadOfAllTable =
193        mockClusterServersWithTables(clusterServers);
194    if (useLoadOfAllTable) {
195      loadBalancer.setClusterLoad((Map) LoadOfAllTable);
196    } else {
197      loadBalancer.setClusterLoad(clusterLoad);
198    }
199    List<RegionPlan> clusterplans1 = new ArrayList<RegionPlan>();
200    List<Pair<TableName, Integer>> regionAmountList = new ArrayList<Pair<TableName, Integer>>();
201    for (Map.Entry<TableName, TreeMap<ServerName, List<RegionInfo>>> mapEntry : LoadOfAllTable
202        .entrySet()) {
203      TableName tableName = mapEntry.getKey();
204      TreeMap<ServerName, List<RegionInfo>> servers = mapEntry.getValue();
205      List<ServerAndLoad> list = convertToList(servers);
206      LOG.info("Mock Cluster : " + printMock(list) + " " + printStats(list));
207      List<RegionPlan> partialplans = loadBalancer.balanceTable(tableName, servers);
208      if (partialplans != null) clusterplans1.addAll(partialplans);
209      List<ServerAndLoad> balancedClusterPerTable = reconcile(list, partialplans, servers);
210      LOG.info("Mock Balance : " + printMock(balancedClusterPerTable));
211      assertClusterAsBalanced(balancedClusterPerTable);
212      for (Map.Entry<ServerName, List<RegionInfo>> entry : servers.entrySet()) {
213        returnRegions(entry.getValue());
214        returnServer(entry.getKey());
215      }
216    }
217    List<ServerAndLoad> balancedCluster1 = reconcile(clusterList, clusterplans1, clusterServers);
218    assertTrue(assertClusterOverallAsBalanced(balancedCluster1, LoadOfAllTable.keySet().size()));
219  }
220
221  @Test
222  public void testBalanceClusterOverallStrictly() throws Exception {
223    int[] regionNumOfTable1PerServer = { 3, 3, 4, 4, 4, 4, 5, 5, 5 };
224    int[] regionNumOfTable2PerServer = { 2, 2, 2, 2, 2, 2, 2, 2, 1 };
225    TreeMap<ServerName, List<RegionInfo>> serverRegionInfo = new TreeMap<>();
226    List<ServerAndLoad> serverAndLoads = new ArrayList<>();
227    for (int i = 0; i < regionNumOfTable1PerServer.length; i++) {
228      ServerName serverName = ServerName.valueOf("server" + i, 1000, -1);
229      List<RegionInfo> regions1 =
230          createRegions(regionNumOfTable1PerServer[i], TableName.valueOf("table1"));
231      List<RegionInfo> regions2 =
232          createRegions(regionNumOfTable2PerServer[i], TableName.valueOf("table2"));
233      regions1.addAll(regions2);
234      serverRegionInfo.put(serverName, regions1);
235      ServerAndLoad serverAndLoad = new ServerAndLoad(serverName,
236          regionNumOfTable1PerServer[i] + regionNumOfTable2PerServer[i]);
237      serverAndLoads.add(serverAndLoad);
238    }
239    HashMap<TableName, TreeMap<ServerName, List<RegionInfo>>> LoadOfAllTable =
240        mockClusterServersWithTables(serverRegionInfo);
241    loadBalancer.setClusterLoad((Map) LoadOfAllTable);
242    List<RegionPlan> partialplans = loadBalancer.balanceTable(TableName.valueOf("table1"),
243      LoadOfAllTable.get(TableName.valueOf("table1")));
244    List<ServerAndLoad> balancedServerLoads =
245        reconcile(serverAndLoads, partialplans, serverRegionInfo);
246    for (ServerAndLoad serverAndLoad : balancedServerLoads) {
247      assertEquals(6, serverAndLoad.getLoad());
248    }
249  }
250
251}