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
044import org.apache.hbase.thirdparty.com.google.common.collect.ImmutableList;
045
046@Category({ MasterTests.class, LargeTests.class })
047public class TestStochasticLoadBalancerRegionReplica extends StochasticBalancerTestBase {
048
049  @ClassRule
050  public static final HBaseClassTestRule CLASS_RULE =
051    HBaseClassTestRule.forClass(TestStochasticLoadBalancerRegionReplica.class);
052
053  @Test
054  public void testReplicaCost() {
055    Configuration conf = HBaseConfiguration.create();
056    CostFunction costFunction = new RegionReplicaHostCostFunction(conf);
057    for (int[] mockCluster : clusterStateMocks) {
058      BalancerClusterState cluster = mockCluster(mockCluster);
059      costFunction.prepare(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    CostFunction costFunction = new RegionReplicaHostCostFunction(conf);
070
071    int[] servers = new int[] { 3, 3, 3, 3, 3 };
072    TreeMap<ServerName, List<RegionInfo>> clusterState = mockClusterServers(servers);
073
074    BalancerClusterState cluster;
075
076    cluster = new BalancerClusterState(clusterState, null, null, null);
077    costFunction.prepare(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 BalancerClusterState(clusterState, null, null, null);
087    costFunction.prepare(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 BalancerClusterState(clusterState, null, null, null);
097    costFunction.prepare(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<Map.Entry<ServerName, List<RegionInfo>>> it;
106    Map.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 BalancerClusterState(clusterState, null, null, null);
120    costFunction.prepare(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 BalancerClusterState(clusterState, null, null, null);
134    costFunction.prepare(cluster);
135    double costWith2ReplicasOnTwoServers = costFunction.cost();
136
137    assertTrue(costWith2ReplicasOnTwoServers < costWith3ReplicasSameServer);
138  }
139
140  @Test
141  public void testNeedsBalanceForColocatedReplicasOnHost() {
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    BalancerClusterState cluster =
154      new BalancerClusterState(map, null, null, new ForTestRackManagerOne());
155    loadBalancer.initCosts(cluster);
156    assertTrue(loadBalancer.needsBalance(HConstants.ENSEMBLE_TABLE_NAME, cluster));
157  }
158
159  @Test
160  public void testNeedsBalanceForColocatedReplicasOnRack() {
161    // Three hosts, two racks, and two replicas for a region. This should be balanced
162    List<RegionInfo> regions = randomRegions(1);
163    ServerName s1 = ServerName.valueOf("host1", 1000, 11111);
164    ServerName s2 = ServerName.valueOf("host11", 1000, 11111);
165    Map<ServerName, List<RegionInfo>> map = new HashMap<>();
166    List<RegionInfo> regionsOnS2 = new ArrayList<>(1);
167    regionsOnS2.add(RegionReplicaUtil.getRegionInfoForReplica(regions.get(0), 1));
168    map.put(s1, regions);
169    map.put(s2, regionsOnS2);
170    // add another server so that the cluster has some host on another rack
171    map.put(ServerName.valueOf("host2", 1000, 11111), randomRegions(1));
172    BalancerClusterState cluster =
173      new BalancerClusterState(map, null, null, new ForTestRackManagerOne());
174    loadBalancer.initCosts(cluster);
175    assertTrue(loadBalancer.needsBalance(HConstants.ENSEMBLE_TABLE_NAME, cluster));
176  }
177
178  @Test
179  public void testNoNeededBalanceForColocatedReplicasTooFewRacks() {
180    // Three hosts, two racks, and three replicas for a region. This cannot be balanced
181    List<RegionInfo> regions = randomRegions(1);
182    ServerName s1 = ServerName.valueOf("host1", 1000, 11111);
183    ServerName s2 = ServerName.valueOf("host11", 1000, 11111);
184    ServerName s3 = ServerName.valueOf("host2", 1000, 11111);
185    Map<ServerName, List<RegionInfo>> map = new HashMap<>();
186    List<RegionInfo> regionsOnS2 = new ArrayList<>(1);
187    regionsOnS2.add(RegionReplicaUtil.getRegionInfoForReplica(regions.get(0), 1));
188    map.put(s1, regions);
189    map.put(s2, regionsOnS2);
190    // there are 3 replicas for region 0, but only add a second rack
191    map.put(s3, ImmutableList.of(RegionReplicaUtil.getRegionInfoForReplica(regions.get(0), 2)));
192    BalancerClusterState cluster =
193      new BalancerClusterState(map, null, null, new ForTestRackManagerOne());
194    loadBalancer.initCosts(cluster);
195    // Should be false because there aren't enough racks
196    assertFalse(loadBalancer.needsBalance(HConstants.ENSEMBLE_TABLE_NAME, cluster));
197  }
198
199  @Test
200  public void testRegionReplicasOnSmallCluster() {
201    int numNodes = 10;
202    int numRegions = 1000;
203    int replication = 3; // 3 replicas per region
204    int numRegionsPerServer = 80; // all regions are mostly balanced
205    int numTables = 10;
206    testWithClusterWithIteration(numNodes, numRegions, numRegionsPerServer, replication, numTables,
207      true, true);
208  }
209
210  private static class ForTestRackManagerOne extends RackManager {
211    @Override
212    public String getRack(ServerName server) {
213      return server.getHostname().endsWith("1") ? "rack1" : "rack2";
214    }
215  }
216}