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