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.jupiter.api.Assertions.assertEquals;
021import static org.junit.jupiter.api.Assertions.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.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.RegionPlan;
034import org.apache.hadoop.hbase.testclassification.MasterTests;
035import org.apache.hadoop.hbase.testclassification.SmallTests;
036import org.apache.hadoop.net.DNSToSwitchMapping;
037import org.junit.jupiter.api.BeforeAll;
038import org.junit.jupiter.api.BeforeEach;
039import org.junit.jupiter.api.Tag;
040import org.junit.jupiter.api.Test;
041import org.junit.jupiter.api.TestInfo;
042import org.slf4j.Logger;
043import org.slf4j.LoggerFactory;
044
045/**
046 * Test the load balancer that is created by default.
047 */
048@Tag(MasterTests.TAG)
049@Tag(SmallTests.TAG)
050public class TestSimpleLoadBalancer extends BalancerTestBase {
051
052  private static final Logger LOG = LoggerFactory.getLogger(TestSimpleLoadBalancer.class);
053
054  private static SimpleLoadBalancer loadBalancer;
055  private String methodName;
056
057  @BeforeAll
058  public static void beforeAllTests() throws Exception {
059    Configuration conf = HBaseConfiguration.create();
060    conf.setClass("hbase.util.ip.to.rack.determiner", MockMapping.class, DNSToSwitchMapping.class);
061    conf.set("hbase.regions.slop", "0");
062    loadBalancer = new SimpleLoadBalancer();
063    loadBalancer.setClusterInfoProvider(new DummyClusterInfoProvider(conf));
064    loadBalancer.initialize();
065  }
066
067  @BeforeEach
068  public void beforeEach(TestInfo testInfo) {
069    methodName = testInfo.getTestMethod().get().getName();
070  }
071
072  int[] mockUniformCluster = new int[] { 5, 5, 5, 5, 5, 0 };
073
074  /**
075   * Test the load balancing algorithm. Invariant is that all servers should be hosting either
076   * floor(average) or ceiling(average) at both table level and cluster level
077   */
078  @Test
079  public void testBalanceClusterOverall() throws Exception {
080    Map<TableName, Map<ServerName, List<RegionInfo>>> clusterLoad = new TreeMap<>();
081    for (int[] mockCluster : clusterStateMocks) {
082      Map<ServerName, List<RegionInfo>> clusterServers = mockClusterServers(mockCluster, 30);
083      List<ServerAndLoad> clusterList = convertToList(clusterServers);
084      clusterLoad.put(TableName.valueOf(methodName), clusterServers);
085      HashMap<TableName, TreeMap<ServerName, List<RegionInfo>>> result =
086        mockClusterServersWithTables(clusterServers);
087      loadBalancer.setClusterLoad(clusterLoad);
088      List<RegionPlan> clusterplans = new ArrayList<>();
089      for (Map.Entry<TableName, TreeMap<ServerName, List<RegionInfo>>> mapEntry : result
090        .entrySet()) {
091        TableName tableName = mapEntry.getKey();
092        TreeMap<ServerName, List<RegionInfo>> servers = mapEntry.getValue();
093        List<ServerAndLoad> list = convertToList(servers);
094        LOG.info("Mock Cluster : " + printMock(list) + " " + printStats(list));
095        List<RegionPlan> partialplans = loadBalancer.balanceTable(tableName, servers);
096        if (partialplans != null) clusterplans.addAll(partialplans);
097        List<ServerAndLoad> balancedClusterPerTable = reconcile(list, partialplans, servers);
098        LOG.info("Mock Balance : " + printMock(balancedClusterPerTable));
099        assertClusterAsBalanced(balancedClusterPerTable);
100        for (Map.Entry<ServerName, List<RegionInfo>> entry : servers.entrySet()) {
101          returnRegions(entry.getValue());
102          returnServer(entry.getKey());
103        }
104      }
105      List<ServerAndLoad> balancedCluster = reconcile(clusterList, clusterplans, clusterServers);
106      assertTrue(assertClusterOverallAsBalanced(balancedCluster, result.keySet().size()));
107    }
108  }
109
110  /**
111   * Test the load balancing algorithm. Invariant is that all servers should be hosting either
112   * floor(average) or ceiling(average) at both table level and cluster level Deliberately generate
113   * a special case to show the overall strategy can achieve cluster level balance while the bytable
114   * strategy cannot
115   */
116  @Test
117  public void testImpactOfBalanceClusterOverall() throws Exception {
118    testImpactOfBalanceClusterOverall(false);
119  }
120
121  @Test
122  public void testImpactOfBalanceClusterOverallWithLoadOfAllTable() throws Exception {
123    testImpactOfBalanceClusterOverall(true);
124  }
125
126  private void testImpactOfBalanceClusterOverall(boolean useLoadOfAllTable) throws Exception {
127    Map<TableName, Map<ServerName, List<RegionInfo>>> clusterLoad = new TreeMap<>();
128    Map<ServerName, List<RegionInfo>> clusterServers =
129      mockUniformClusterServers(mockUniformCluster);
130    List<ServerAndLoad> clusterList = convertToList(clusterServers);
131    clusterLoad.put(TableName.valueOf(methodName), clusterServers);
132    // use overall can achieve both table and cluster level balance
133    HashMap<TableName, TreeMap<ServerName, List<RegionInfo>>> LoadOfAllTable =
134      mockClusterServersWithTables(clusterServers);
135    if (useLoadOfAllTable) {
136      loadBalancer.setClusterLoad((Map) LoadOfAllTable);
137    } else {
138      loadBalancer.setClusterLoad(clusterLoad);
139    }
140    List<RegionPlan> clusterplans1 = new ArrayList<RegionPlan>();
141    for (Map.Entry<TableName, TreeMap<ServerName, List<RegionInfo>>> mapEntry : LoadOfAllTable
142      .entrySet()) {
143      TableName tableName = mapEntry.getKey();
144      TreeMap<ServerName, List<RegionInfo>> servers = mapEntry.getValue();
145      List<ServerAndLoad> list = convertToList(servers);
146      LOG.info("Mock Cluster : " + printMock(list) + " " + printStats(list));
147      List<RegionPlan> partialplans = loadBalancer.balanceTable(tableName, servers);
148      if (partialplans != null) clusterplans1.addAll(partialplans);
149      List<ServerAndLoad> balancedClusterPerTable = reconcile(list, partialplans, servers);
150      LOG.info("Mock Balance : " + printMock(balancedClusterPerTable));
151      assertClusterAsBalanced(balancedClusterPerTable);
152      for (Map.Entry<ServerName, List<RegionInfo>> entry : servers.entrySet()) {
153        returnRegions(entry.getValue());
154        returnServer(entry.getKey());
155      }
156    }
157    List<ServerAndLoad> balancedCluster1 = reconcile(clusterList, clusterplans1, clusterServers);
158    assertTrue(assertClusterOverallAsBalanced(balancedCluster1, LoadOfAllTable.keySet().size()));
159  }
160
161  @Test
162  public void testBalanceClusterOverallStrictly() {
163    int[][] regionsPerServerPerTable = new int[][] { new int[] { 3, 3, 4, 4, 4, 4, 5, 5, 5 },
164      new int[] { 2, 2, 2, 2, 2, 2, 2, 2, 1 }, };
165    TreeMap<ServerName, List<RegionInfo>> serverRegionInfo =
166      mockClusterServers(regionsPerServerPerTable);
167    List<ServerAndLoad> serverAndLoads = convertToList(serverRegionInfo);
168    Map<TableName, TreeMap<ServerName, List<RegionInfo>>> loadOfAllTable =
169      mockClusterServersWithTables(serverRegionInfo);
170    loadBalancer.setClusterLoad((Map) loadOfAllTable);
171    List<RegionPlan> partialplans = loadBalancer.balanceTable(TableName.valueOf("table0"),
172      loadOfAllTable.get(TableName.valueOf("table0")));
173    List<ServerAndLoad> balancedServerLoads =
174      reconcile(serverAndLoads, partialplans, serverRegionInfo);
175    for (ServerAndLoad serverAndLoad : balancedServerLoads) {
176      assertEquals(6, serverAndLoad.getLoad());
177    }
178  }
179}