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.HashMap;
026import java.util.Iterator;
027import java.util.List;
028import java.util.Map;
029import java.util.TreeMap;
030import org.apache.hadoop.conf.Configuration;
031import org.apache.hadoop.hbase.HBaseConfiguration;
032import org.apache.hadoop.hbase.HConstants;
033import org.apache.hadoop.hbase.ServerName;
034import org.apache.hadoop.hbase.client.RegionInfo;
035import org.apache.hadoop.hbase.client.RegionReplicaUtil;
036import org.apache.hadoop.hbase.master.RackManager;
037import org.apache.hadoop.hbase.testclassification.LargeTests;
038import org.apache.hadoop.hbase.testclassification.MasterTests;
039import org.junit.jupiter.api.Tag;
040import org.junit.jupiter.api.Test;
041
042import org.apache.hbase.thirdparty.com.google.common.collect.ImmutableList;
043
044@Tag(MasterTests.TAG)
045@Tag(LargeTests.TAG)
046public class TestStochasticLoadBalancerRegionReplica extends StochasticBalancerTestBase {
047
048  @Test
049  public void testReplicaCost() {
050    Configuration conf = HBaseConfiguration.create();
051    CostFunction costFunction = new RegionReplicaHostCostFunction(conf);
052    for (int[] mockCluster : clusterStateMocks) {
053      BalancerClusterState cluster = mockCluster(mockCluster);
054      costFunction.prepare(cluster);
055      double cost = costFunction.cost();
056      assertTrue(cost >= 0);
057      assertTrue(cost <= 1.01);
058    }
059  }
060
061  @Test
062  public void testReplicaCostForReplicas() {
063    Configuration conf = HBaseConfiguration.create();
064    CostFunction costFunction = new RegionReplicaHostCostFunction(conf);
065
066    int[] servers = new int[] { 3, 3, 3, 3, 3 };
067    TreeMap<ServerName, List<RegionInfo>> clusterState = mockClusterServers(servers);
068
069    BalancerClusterState cluster;
070
071    cluster = new BalancerClusterState(clusterState, null, null, null);
072    costFunction.prepare(cluster);
073    double costWithoutReplicas = costFunction.cost();
074    assertEquals(0, costWithoutReplicas, 0);
075
076    // replicate the region from first server to the last server
077    RegionInfo replica1 =
078      RegionReplicaUtil.getRegionInfoForReplica(clusterState.firstEntry().getValue().get(0), 1);
079    clusterState.lastEntry().getValue().add(replica1);
080
081    cluster = new BalancerClusterState(clusterState, null, null, null);
082    costFunction.prepare(cluster);
083    double costWith1ReplicaDifferentServer = costFunction.cost();
084
085    assertEquals(0, costWith1ReplicaDifferentServer, 0);
086
087    // add a third replica to the last server
088    RegionInfo replica2 = RegionReplicaUtil.getRegionInfoForReplica(replica1, 2);
089    clusterState.lastEntry().getValue().add(replica2);
090
091    cluster = new BalancerClusterState(clusterState, null, null, null);
092    costFunction.prepare(cluster);
093    double costWith1ReplicaSameServer = costFunction.cost();
094
095    assertTrue(costWith1ReplicaDifferentServer < costWith1ReplicaSameServer);
096
097    // test with replication = 4 for following:
098
099    RegionInfo replica3;
100    Iterator<Map.Entry<ServerName, List<RegionInfo>>> it;
101    Map.Entry<ServerName, List<RegionInfo>> entry;
102
103    clusterState = mockClusterServers(servers);
104    it = clusterState.entrySet().iterator();
105    entry = it.next(); // first server
106    RegionInfo hri = entry.getValue().get(0);
107    replica1 = RegionReplicaUtil.getRegionInfoForReplica(hri, 1);
108    replica2 = RegionReplicaUtil.getRegionInfoForReplica(hri, 2);
109    replica3 = RegionReplicaUtil.getRegionInfoForReplica(hri, 3);
110    entry.getValue().add(replica1);
111    entry.getValue().add(replica2);
112    it.next().getValue().add(replica3); // 2nd server
113
114    cluster = new BalancerClusterState(clusterState, null, null, null);
115    costFunction.prepare(cluster);
116    double costWith3ReplicasSameServer = costFunction.cost();
117
118    clusterState = mockClusterServers(servers);
119    hri = clusterState.firstEntry().getValue().get(0);
120    replica1 = RegionReplicaUtil.getRegionInfoForReplica(hri, 1);
121    replica2 = RegionReplicaUtil.getRegionInfoForReplica(hri, 2);
122    replica3 = RegionReplicaUtil.getRegionInfoForReplica(hri, 3);
123
124    clusterState.firstEntry().getValue().add(replica1);
125    clusterState.lastEntry().getValue().add(replica2);
126    clusterState.lastEntry().getValue().add(replica3);
127
128    cluster = new BalancerClusterState(clusterState, null, null, null);
129    costFunction.prepare(cluster);
130    double costWith2ReplicasOnTwoServers = costFunction.cost();
131
132    assertTrue(costWith2ReplicasOnTwoServers < costWith3ReplicasSameServer);
133  }
134
135  @Test
136  public void testNeedsBalanceForColocatedReplicasOnHost() {
137    // check for the case where there are two hosts and with one rack, and where
138    // both the replicas are hosted on the same server
139    List<RegionInfo> regions = randomRegions(1);
140    ServerName s1 = ServerName.valueOf("host1", 1000, 11111);
141    ServerName s2 = ServerName.valueOf("host11", 1000, 11111);
142    Map<ServerName, List<RegionInfo>> map = new HashMap<>();
143    map.put(s1, regions);
144    regions.add(RegionReplicaUtil.getRegionInfoForReplica(regions.get(0), 1));
145    // until the step above s1 holds two replicas of a region
146    regions = randomRegions(1);
147    map.put(s2, regions);
148    BalancerClusterState cluster =
149      new BalancerClusterState(map, null, null, new ForTestRackManagerOne());
150    loadBalancer.initCosts(cluster);
151    assertTrue(loadBalancer.needsBalance(HConstants.ENSEMBLE_TABLE_NAME, cluster));
152  }
153
154  @Test
155  public void testNeedsBalanceForColocatedReplicasOnRack() {
156    // Three hosts, two racks, and two replicas for a region. This should be balanced
157    List<RegionInfo> regions = randomRegions(1);
158    ServerName s1 = ServerName.valueOf("host1", 1000, 11111);
159    ServerName s2 = ServerName.valueOf("host11", 1000, 11111);
160    Map<ServerName, List<RegionInfo>> map = new HashMap<>();
161    List<RegionInfo> regionsOnS2 = new ArrayList<>(1);
162    regionsOnS2.add(RegionReplicaUtil.getRegionInfoForReplica(regions.get(0), 1));
163    map.put(s1, regions);
164    map.put(s2, regionsOnS2);
165    // add another server so that the cluster has some host on another rack
166    map.put(ServerName.valueOf("host2", 1000, 11111), randomRegions(1));
167    BalancerClusterState cluster =
168      new BalancerClusterState(map, null, null, new ForTestRackManagerOne());
169    loadBalancer.initCosts(cluster);
170    assertTrue(loadBalancer.needsBalance(HConstants.ENSEMBLE_TABLE_NAME, cluster));
171  }
172
173  @Test
174  public void testNoNeededBalanceForColocatedReplicasTooFewRacks() {
175    // Three hosts, two racks, and three replicas for a region. This cannot be balanced
176    List<RegionInfo> regions = randomRegions(1);
177    ServerName s1 = ServerName.valueOf("host1", 1000, 11111);
178    ServerName s2 = ServerName.valueOf("host11", 1000, 11111);
179    ServerName s3 = ServerName.valueOf("host2", 1000, 11111);
180    Map<ServerName, List<RegionInfo>> map = new HashMap<>();
181    List<RegionInfo> regionsOnS2 = new ArrayList<>(1);
182    regionsOnS2.add(RegionReplicaUtil.getRegionInfoForReplica(regions.get(0), 1));
183    map.put(s1, regions);
184    map.put(s2, regionsOnS2);
185    // there are 3 replicas for region 0, but only add a second rack
186    map.put(s3, ImmutableList.of(RegionReplicaUtil.getRegionInfoForReplica(regions.get(0), 2)));
187    BalancerClusterState cluster =
188      new BalancerClusterState(map, null, null, new ForTestRackManagerOne());
189    loadBalancer.initCosts(cluster);
190    // Should be false because there aren't enough racks
191    assertFalse(loadBalancer.needsBalance(HConstants.ENSEMBLE_TABLE_NAME, cluster));
192  }
193
194  @Test
195  public void testRegionReplicasOnSmallCluster() {
196    int numNodes = 10;
197    int numRegions = 1000;
198    int replication = 3; // 3 replicas per region
199    int numRegionsPerServer = 80; // all regions are mostly balanced
200    int numTables = 10;
201    testWithClusterWithIteration(numNodes, numRegions, numRegionsPerServer, replication, numTables,
202      true, true);
203  }
204
205  private static class ForTestRackManagerOne extends RackManager {
206    @Override
207    public String getRack(ServerName server) {
208      return server.getHostname().endsWith("1") ? "rack1" : "rack2";
209    }
210  }
211}