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;
022import static org.mockito.Mockito.mock;
023import static org.mockito.Mockito.when;
024
025import java.io.IOException;
026import java.util.HashMap;
027import java.util.HashSet;
028import java.util.List;
029import java.util.Map;
030import java.util.Set;
031import java.util.TreeMap;
032import org.apache.hadoop.hbase.ClusterMetrics;
033import org.apache.hadoop.hbase.RegionMetrics;
034import org.apache.hadoop.hbase.ServerMetrics;
035import org.apache.hadoop.hbase.ServerName;
036import org.apache.hadoop.hbase.Size;
037import org.apache.hadoop.hbase.TableName;
038import org.apache.hadoop.hbase.client.RegionInfo;
039import org.apache.hadoop.hbase.master.RegionPlan;
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.hbase.util.Bytes;
044import org.apache.hadoop.net.DNSToSwitchMapping;
045import org.junit.jupiter.api.BeforeAll;
046import org.junit.jupiter.api.Tag;
047import org.junit.jupiter.api.Test;
048
049@Tag(LargeTests.TAG)
050public class TestRSGroupBasedLoadBalancerWithStochasticLoadBalancerAsInternal
051  extends RSGroupableBalancerTestBase {
052  private static RSGroupBasedLoadBalancer loadBalancer;
053
054  @BeforeAll
055  public static void beforeAllTests() throws Exception {
056    groups = new String[] { RSGroupInfo.DEFAULT_GROUP };
057    servers = generateServers(3);
058    groupMap = constructGroupInfo(servers, groups);
059    tableDescs = constructTableDesc(false);
060    conf.set("hbase.regions.slop", "0");
061    conf.setFloat("hbase.master.balancer.stochastic.readRequestCost", 10000f);
062    conf.set("hbase.rsgroup.grouploadbalancer.class",
063      StochasticLoadBalancer.class.getCanonicalName());
064    conf.setClass("hbase.util.ip.to.rack.determiner", MockMapping.class, DNSToSwitchMapping.class);
065    loadBalancer = new RSGroupBasedLoadBalancer();
066    loadBalancer.setMasterServices(getMockedMaster());
067    loadBalancer.initialize();
068  }
069
070  private ServerMetrics mockServerMetricsWithReadRequests(ServerName server,
071    List<RegionInfo> regionsOnServer, long readRequestCount) {
072    ServerMetrics serverMetrics = mock(ServerMetrics.class);
073    Map<byte[], RegionMetrics> regionLoadMap = new TreeMap<>(Bytes.BYTES_COMPARATOR);
074    for (RegionInfo info : regionsOnServer) {
075      RegionMetrics rl = mock(RegionMetrics.class);
076      when(rl.getReadRequestCount()).thenReturn(readRequestCount);
077      when(rl.getCpRequestCount()).thenReturn(0L);
078      when(rl.getWriteRequestCount()).thenReturn(0L);
079      when(rl.getMemStoreSize()).thenReturn(Size.ZERO);
080      when(rl.getStoreFileSize()).thenReturn(Size.ZERO);
081      when(rl.getRegionSizeMB()).thenReturn(Size.ZERO);
082      when(rl.getCurrentRegionCachedRatio()).thenReturn(0.0f);
083      regionLoadMap.put(info.getRegionName(), rl);
084    }
085    when(serverMetrics.getRegionMetrics()).thenReturn(regionLoadMap);
086    return serverMetrics;
087  }
088
089  /**
090   * Test HBASE-20791
091   */
092  @Test
093  public void testBalanceCluster() throws IOException {
094    // mock cluster State
095    Map<ServerName, List<RegionInfo>> clusterState = new HashMap<ServerName, List<RegionInfo>>();
096    ServerName serverA = servers.get(0);
097    ServerName serverB = servers.get(1);
098    ServerName serverC = servers.get(2);
099    List<RegionInfo> regionsOnServerA = randomRegions(3);
100    List<RegionInfo> regionsOnServerB = randomRegions(3);
101    List<RegionInfo> regionsOnServerC = randomRegions(3);
102    clusterState.put(serverA, regionsOnServerA);
103    clusterState.put(serverB, regionsOnServerB);
104    clusterState.put(serverC, regionsOnServerC);
105    // mock ClusterMetrics
106    Map<ServerName, ServerMetrics> serverMetricsMap = new TreeMap<>();
107    serverMetricsMap.put(serverA, mockServerMetricsWithReadRequests(serverA, regionsOnServerA, 0));
108    serverMetricsMap.put(serverB, mockServerMetricsWithReadRequests(serverB, regionsOnServerB, 0));
109    serverMetricsMap.put(serverC, mockServerMetricsWithReadRequests(serverC, regionsOnServerC, 0));
110    ClusterMetrics clusterStatus = mock(ClusterMetrics.class);
111    when(clusterStatus.getLiveServerMetrics()).thenReturn(serverMetricsMap);
112    loadBalancer.updateClusterMetrics(clusterStatus);
113
114    // ReadRequestCostFunction are Rate based, So doing setClusterMetrics again
115    // this time, regions on serverA with more readRequestCount load
116    // serverA : 1000,1000,1000
117    // serverB : 0,0,0
118    // serverC : 0,0,0
119    // so should move two regions from serverA to serverB & serverC
120    serverMetricsMap = new TreeMap<>();
121    serverMetricsMap.put(serverA,
122      mockServerMetricsWithReadRequests(serverA, regionsOnServerA, 1000));
123    serverMetricsMap.put(serverB, mockServerMetricsWithReadRequests(serverB, regionsOnServerB, 0));
124    serverMetricsMap.put(serverC, mockServerMetricsWithReadRequests(serverC, regionsOnServerC, 0));
125    clusterStatus = mock(ClusterMetrics.class);
126    when(clusterStatus.getLiveServerMetrics()).thenReturn(serverMetricsMap);
127    loadBalancer.updateClusterMetrics(clusterStatus);
128
129    Map<TableName, Map<ServerName, List<RegionInfo>>> LoadOfAllTable =
130      (Map) mockClusterServersWithTables(clusterState);
131    List<RegionPlan> plans = loadBalancer.balanceCluster(LoadOfAllTable);
132    Set<RegionInfo> regionsMoveFromServerA = new HashSet<>();
133    Set<ServerName> targetServers = new HashSet<>();
134    for (RegionPlan plan : plans) {
135      if (plan.getSource().equals(serverA)) {
136        regionsMoveFromServerA.add(plan.getRegionInfo());
137        targetServers.add(plan.getDestination());
138      }
139    }
140    // should move 2 regions from serverA, one moves to serverB, the other moves to serverC
141    assertEquals(2, regionsMoveFromServerA.size());
142    assertEquals(2, targetServers.size());
143    assertTrue(regionsOnServerA.containsAll(regionsMoveFromServerA));
144  }
145}