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 StochasticBalancerTestBase {
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 =
055        new RegionReplicaHostCostFunction(conf);
056    for (int[] mockCluster : clusterStateMocks) {
057      BalancerClusterState cluster = mockCluster(mockCluster);
058      costFunction.prepare(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    CostFunction costFunction =
069        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 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(HConstants.ENSEMBLE_TABLE_NAME,
154      new BalancerClusterState(map, null, null, null)));
155    // check for the case where there are two hosts on the same rack and there are two racks
156    // and both the replicas are on the same rack
157    map.clear();
158    regions = randomRegions(1);
159    List<RegionInfo> regionsOnS2 = new ArrayList<>(1);
160    regionsOnS2.add(RegionReplicaUtil.getRegionInfoForReplica(regions.get(0), 1));
161    map.put(s1, regions);
162    map.put(s2, regionsOnS2);
163    // add another server so that the cluster has some host on another rack
164    map.put(ServerName.valueOf("host2", 1000, 11111), randomRegions(1));
165    assertFalse(loadBalancer.needsBalance(HConstants.ENSEMBLE_TABLE_NAME,
166        new BalancerClusterState(map, null, null, new ForTestRackManagerOne())));
167  }
168
169  @Test
170  public void testRegionReplicasOnSmallCluster() {
171    int numNodes = 10;
172    int numRegions = 1000;
173    int replication = 3; // 3 replicas per region
174    int numRegionsPerServer = 80; // all regions are mostly balanced
175    int numTables = 10;
176    testWithCluster(numNodes, numRegions, numRegionsPerServer, replication, numTables, true, true);
177  }
178
179  private static class ForTestRackManagerOne extends RackManager {
180    @Override
181    public String getRack(ServerName server) {
182      return server.getHostname().endsWith("1") ? "rack1" : "rack2";
183    }
184  }
185}