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