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