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;
022
023import java.util.ArrayList;
024import java.util.HashMap;
025import java.util.Iterator;
026import java.util.List;
027import java.util.Map;
028import java.util.Map.Entry;
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.client.RegionInfo;
035import org.apache.hadoop.hbase.client.RegionReplicaUtil;
036import org.apache.hadoop.hbase.master.RackManager;
037import org.apache.hadoop.hbase.master.balancer.BaseLoadBalancer.Cluster;
038import org.apache.hadoop.hbase.testclassification.MasterTests;
039import org.apache.hadoop.hbase.testclassification.MediumTests;
040import org.junit.ClassRule;
041import org.junit.Test;
042import org.junit.experimental.categories.Category;
043
044@Category({ MasterTests.class, MediumTests.class })
045public class TestStochasticLoadBalancerRegionReplica extends BalancerTestBase {
046
047  @ClassRule
048  public static final HBaseClassTestRule CLASS_RULE =
049      HBaseClassTestRule.forClass(TestStochasticLoadBalancerRegionReplica.class);
050
051  @Test
052  public void testReplicaCost() {
053    Configuration conf = HBaseConfiguration.create();
054    StochasticLoadBalancer.CostFunction costFunction =
055        new StochasticLoadBalancer.RegionReplicaHostCostFunction(conf);
056    for (int[] mockCluster : clusterStateMocks) {
057      BaseLoadBalancer.Cluster cluster = mockCluster(mockCluster);
058      costFunction.init(cluster);
059      double cost = costFunction.cost();
060      assertTrue(cost >= 0);
061      assertTrue(cost <= 1.01);
062    }
063  }
064
065  @Test
066  public void testReplicaCostForReplicas() {
067    Configuration conf = HBaseConfiguration.create();
068    StochasticLoadBalancer.CostFunction costFunction =
069        new StochasticLoadBalancer.RegionReplicaHostCostFunction(conf);
070
071    int[] servers = new int[] { 3, 3, 3, 3, 3 };
072    TreeMap<ServerName, List<RegionInfo>> clusterState = mockClusterServers(servers);
073
074    BaseLoadBalancer.Cluster cluster;
075
076    cluster = new BaseLoadBalancer.Cluster(clusterState, null, null, null);
077    costFunction.init(cluster);
078    double costWithoutReplicas = costFunction.cost();
079    assertEquals(0, costWithoutReplicas, 0);
080
081    // replicate the region from first server to the last server
082    RegionInfo replica1 =
083        RegionReplicaUtil.getRegionInfoForReplica(clusterState.firstEntry().getValue().get(0), 1);
084    clusterState.lastEntry().getValue().add(replica1);
085
086    cluster = new BaseLoadBalancer.Cluster(clusterState, null, null, null);
087    costFunction.init(cluster);
088    double costWith1ReplicaDifferentServer = costFunction.cost();
089
090    assertEquals(0, costWith1ReplicaDifferentServer, 0);
091
092    // add a third replica to the last server
093    RegionInfo replica2 = RegionReplicaUtil.getRegionInfoForReplica(replica1, 2);
094    clusterState.lastEntry().getValue().add(replica2);
095
096    cluster = new BaseLoadBalancer.Cluster(clusterState, null, null, null);
097    costFunction.init(cluster);
098    double costWith1ReplicaSameServer = costFunction.cost();
099
100    assertTrue(costWith1ReplicaDifferentServer < costWith1ReplicaSameServer);
101
102    // test with replication = 4 for following:
103
104    RegionInfo replica3;
105    Iterator<Entry<ServerName, List<RegionInfo>>> it;
106    Entry<ServerName, List<RegionInfo>> entry;
107
108    clusterState = mockClusterServers(servers);
109    it = clusterState.entrySet().iterator();
110    entry = it.next(); // first server
111    RegionInfo hri = entry.getValue().get(0);
112    replica1 = RegionReplicaUtil.getRegionInfoForReplica(hri, 1);
113    replica2 = RegionReplicaUtil.getRegionInfoForReplica(hri, 2);
114    replica3 = RegionReplicaUtil.getRegionInfoForReplica(hri, 3);
115    entry.getValue().add(replica1);
116    entry.getValue().add(replica2);
117    it.next().getValue().add(replica3); // 2nd server
118
119    cluster = new BaseLoadBalancer.Cluster(clusterState, null, null, null);
120    costFunction.init(cluster);
121    double costWith3ReplicasSameServer = costFunction.cost();
122
123    clusterState = mockClusterServers(servers);
124    hri = clusterState.firstEntry().getValue().get(0);
125    replica1 = RegionReplicaUtil.getRegionInfoForReplica(hri, 1);
126    replica2 = RegionReplicaUtil.getRegionInfoForReplica(hri, 2);
127    replica3 = RegionReplicaUtil.getRegionInfoForReplica(hri, 3);
128
129    clusterState.firstEntry().getValue().add(replica1);
130    clusterState.lastEntry().getValue().add(replica2);
131    clusterState.lastEntry().getValue().add(replica3);
132
133    cluster = new BaseLoadBalancer.Cluster(clusterState, null, null, null);
134    costFunction.init(cluster);
135    double costWith2ReplicasOnTwoServers = costFunction.cost();
136
137    assertTrue(costWith2ReplicasOnTwoServers < costWith3ReplicasSameServer);
138  }
139
140  @Test
141  public void testNeedsBalanceForColocatedReplicas() {
142    // check for the case where there are two hosts and with one rack, and where
143    // both the replicas are hosted on the same server
144    List<RegionInfo> regions = randomRegions(1);
145    ServerName s1 = ServerName.valueOf("host1", 1000, 11111);
146    ServerName s2 = ServerName.valueOf("host11", 1000, 11111);
147    Map<ServerName, List<RegionInfo>> map = new HashMap<>();
148    map.put(s1, regions);
149    regions.add(RegionReplicaUtil.getRegionInfoForReplica(regions.get(0), 1));
150    // until the step above s1 holds two replicas of a region
151    regions = randomRegions(1);
152    map.put(s2, regions);
153    assertTrue(loadBalancer.needsBalance(new Cluster(map, null, null, null)));
154    // check for the case where there are two hosts on the same rack and there are two racks
155    // and both the replicas are on the same rack
156    map.clear();
157    regions = randomRegions(1);
158    List<RegionInfo> regionsOnS2 = new ArrayList<>(1);
159    regionsOnS2.add(RegionReplicaUtil.getRegionInfoForReplica(regions.get(0), 1));
160    map.put(s1, regions);
161    map.put(s2, regionsOnS2);
162    // add another server so that the cluster has some host on another rack
163    map.put(ServerName.valueOf("host2", 1000, 11111), randomRegions(1));
164    assertTrue(
165      loadBalancer.needsBalance(new Cluster(map, null, null, new ForTestRackManagerOne())));
166  }
167
168  @Test
169  public void testRegionReplicasOnSmallCluster() {
170    int numNodes = 10;
171    int numRegions = 1000;
172    int replication = 3; // 3 replicas per region
173    int numRegionsPerServer = 80; // all regions are mostly balanced
174    int numTables = 10;
175    testWithCluster(numNodes, numRegions, numRegionsPerServer, replication, numTables, true, true);
176  }
177
178  private static class ForTestRackManagerOne extends RackManager {
179    @Override
180    public String getRack(ServerName server) {
181      return server.getHostname().endsWith("1") ? "rack1" : "rack2";
182    }
183  }
184}