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