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.apache.hadoop.hbase.master.balancer.HeterogeneousCostRulesTestHelper.DEFAULT_RULES_FILE_NAME;
021import static org.apache.hadoop.hbase.master.balancer.HeterogeneousCostRulesTestHelper.createRulesFile;
022import static org.junit.Assert.assertNull;
023import static org.junit.Assert.assertTrue;
024
025import java.io.IOException;
026import java.util.ArrayDeque;
027import java.util.Arrays;
028import java.util.Collections;
029import java.util.List;
030import java.util.Map;
031import java.util.Queue;
032import java.util.Random;
033import java.util.TreeMap;
034import java.util.concurrent.ThreadLocalRandom;
035import org.apache.hadoop.fs.FileSystem;
036import org.apache.hadoop.hbase.HBaseClassTestRule;
037import org.apache.hadoop.hbase.HBaseCommonTestingUtil;
038import org.apache.hadoop.hbase.HConstants;
039import org.apache.hadoop.hbase.ServerName;
040import org.apache.hadoop.hbase.client.RegionInfo;
041import org.apache.hadoop.hbase.client.RegionReplicaUtil;
042import org.apache.hadoop.hbase.master.RegionPlan;
043import org.apache.hadoop.hbase.testclassification.MasterTests;
044import org.apache.hadoop.hbase.testclassification.MediumTests;
045import org.junit.BeforeClass;
046import org.junit.ClassRule;
047import org.junit.Test;
048import org.junit.experimental.categories.Category;
049
050@Category({ MasterTests.class, MediumTests.class })
051public class TestStochasticLoadBalancerHeterogeneousCost extends StochasticBalancerTestBase {
052
053  @ClassRule
054  public static final HBaseClassTestRule CLASS_RULE =
055    HBaseClassTestRule.forClass(TestStochasticLoadBalancerHeterogeneousCost.class);
056
057  private static final HBaseCommonTestingUtil HTU = new HBaseCommonTestingUtil();
058  private static String RULES_FILE;
059
060  @BeforeClass
061  public static void beforeAllTests() throws IOException {
062    conf = HTU.getConfiguration();
063    conf.setFloat("hbase.master.balancer.stochastic.regionCountCost", 0);
064    conf.setFloat("hbase.master.balancer.stochastic.primaryRegionCountCost", 0);
065    conf.setFloat("hbase.master.balancer.stochastic.tableSkewCost", 0);
066    conf.set(StochasticLoadBalancer.COST_FUNCTIONS_COST_FUNCTIONS_KEY,
067      HeterogeneousRegionCountCostFunction.class.getName());
068    // Need to ensure test dir has been created.
069    assertTrue(FileSystem.get(HTU.getConfiguration()).mkdirs(HTU.getDataTestDir()));
070    RULES_FILE = HTU.getDataTestDir(DEFAULT_RULES_FILE_NAME).toString();
071    conf.set(HeterogeneousRegionCountCostFunction.HBASE_MASTER_BALANCER_HETEROGENEOUS_RULES_FILE,
072      RULES_FILE);
073    loadBalancer = new StochasticLoadTestBalancer();
074    loadBalancer.setClusterInfoProvider(new DummyClusterInfoProvider(conf));
075    loadBalancer.initialize();
076  }
077
078  @Test
079  public void testDefault() throws IOException {
080    final List<String> rules = Collections.emptyList();
081
082    final int numNodes = 2;
083    final int numRegions = 300;
084    final int numRegionsPerServer = 250;
085
086    // Initial state: { rs1:50 , rs0:250 }
087    // Cluster can hold 300/400 regions (75%)
088    // Expected balanced Cluster: { rs0:150 , rs1:150 }
089    this.testHeterogeneousWithCluster(numNodes, numRegions, numRegionsPerServer, rules);
090  }
091
092  @Test
093  public void testOneGroup() throws IOException {
094    final List<String> rules = Collections.singletonList("rs.* 100");
095
096    final int numNodes = 4;
097    final int numRegions = 300;
098    final int numRegionsPerServer = 30;
099
100    // Initial state: { rs0:30 , rs1:30 , rs2:30 , rs3:210 }.
101    // The cluster can hold 300/400 regions (75%)
102    // Expected balanced Cluster: { rs0:75 , rs1:75 , rs2:75 , rs3:75 }
103    this.testHeterogeneousWithCluster(numNodes, numRegions, numRegionsPerServer, rules);
104  }
105
106  @Test
107  public void testTwoGroups() throws IOException {
108    final List<String> rules = Arrays.asList("rs[0-4] 200", "rs[5-9] 50");
109
110    final int numNodes = 10;
111    final int numRegions = 500;
112    final int numRegionsPerServer = 50;
113
114    // Initial state: { rs0:50 , rs1:50 , rs2:50 , rs3:50 , rs4:50 , rs5:50 , rs6:50 , rs7:50 ,
115    // rs8:50 , rs9:50 }
116    // the cluster can hold 500/1250 regions (40%)
117    // Expected balanced Cluster: { rs5:20 , rs6:20 , rs7:20 , rs8:20 , rs9:20 , rs0:80 , rs1:80 ,
118    // rs2:80 , rs3:80 , rs4:80 }
119    this.testHeterogeneousWithCluster(numNodes, numRegions, numRegionsPerServer, rules);
120  }
121
122  @Test
123  public void testFourGroups() throws IOException {
124    final List<String> rules = Arrays.asList("rs[1-3] 200", "rs[4-7] 250", "rs[8-9] 100");
125
126    final int numNodes = 10;
127    final int numRegions = 800;
128    final int numRegionsPerServer = 80;
129
130    // Initial state: { rs0:80 , rs1:80 , rs2:80 , rs3:80 , rs4:80 , rs5:80 , rs6:80 , rs7:80 ,
131    // rs8:80 , rs9:80 }
132    // Cluster can hold 800/2000 regions (40%)
133    // Expected balanced Cluster: { rs8:40 , rs9:40 , rs2:80 , rs3:80 , rs1:82 , rs0:94 , rs4:96 ,
134    // rs5:96 , rs6:96 , rs7:96 }
135    this.testHeterogeneousWithCluster(numNodes, numRegions, numRegionsPerServer, rules);
136  }
137
138  @Test
139  public void testOverloaded() throws IOException {
140    final int numNodes = 2;
141    final int numRegions = 120;
142    final int numRegionsPerServer = 60;
143
144    createRulesFile(RULES_FILE);
145    final Map<ServerName, List<RegionInfo>> serverMap =
146      this.createServerMap(numNodes, numRegions, numRegionsPerServer, 1, 1);
147    final List<RegionPlan> plans =
148      loadBalancer.balanceTable(HConstants.ENSEMBLE_TABLE_NAME, serverMap);
149    // As we disabled all the other cost functions, balancing only according to
150    // the heterogeneous cost function should return nothing.
151    assertNull(plans);
152  }
153
154  private void testHeterogeneousWithCluster(final int numNodes, final int numRegions,
155    final int numRegionsPerServer, final List<String> rules) throws IOException {
156
157    createRulesFile(RULES_FILE, rules);
158    final Map<ServerName, List<RegionInfo>> serverMap =
159      this.createServerMap(numNodes, numRegions, numRegionsPerServer, 1, 1);
160    this.testWithClusterWithIteration(serverMap, null, true, false);
161  }
162
163  @Override
164  protected Map<ServerName, List<RegionInfo>> createServerMap(int numNodes, int numRegions,
165    int numRegionsPerServer, int replication, int numTables) {
166    // construct a cluster of numNodes, having a total of numRegions. Each RS will hold
167    // numRegionsPerServer many regions except for the last one, which will host all the
168    // remaining regions
169    int[] cluster = new int[numNodes];
170    for (int i = 0; i < numNodes; i++) {
171      cluster[i] = numRegionsPerServer;
172    }
173    cluster[cluster.length - 1] = numRegions - ((cluster.length - 1) * numRegionsPerServer);
174    Map<ServerName, List<RegionInfo>> clusterState = mockClusterServers(cluster, numTables);
175    if (replication > 0) {
176      // replicate the regions to the same servers
177      for (List<RegionInfo> regions : clusterState.values()) {
178        int length = regions.size();
179        for (int i = 0; i < length; i++) {
180          for (int r = 1; r < replication; r++) {
181            regions.add(RegionReplicaUtil.getRegionInfoForReplica(regions.get(i), r));
182          }
183        }
184      }
185    }
186
187    return clusterState;
188  }
189
190  @Override
191  protected TreeMap<ServerName, List<RegionInfo>> mockClusterServers(int[] mockCluster,
192    int numTables) {
193    int numServers = mockCluster.length;
194    TreeMap<ServerName, List<RegionInfo>> servers = new TreeMap<>();
195    for (int i = 0; i < numServers; i++) {
196      int numRegions = mockCluster[i];
197      ServerAndLoad sal = createServer("rs" + i);
198      List<RegionInfo> regions = randomRegions(numRegions, numTables);
199      servers.put(sal.getServerName(), regions);
200    }
201    return servers;
202  }
203
204  private Queue<ServerName> serverQueue = new ArrayDeque<>();
205
206  private ServerAndLoad createServer(final String host) {
207    if (!this.serverQueue.isEmpty()) {
208      ServerName sn = this.serverQueue.poll();
209      return new ServerAndLoad(sn, 0);
210    }
211    Random rand = ThreadLocalRandom.current();
212    int port = rand.nextInt(60000);
213    long startCode = rand.nextLong();
214    ServerName sn = ServerName.valueOf(host, port, startCode);
215    return new ServerAndLoad(sn, 0);
216  }
217
218  static class FairRandomCandidateGenerator extends RandomCandidateGenerator {
219
220    @Override
221    public BalanceAction pickRandomRegions(BalancerClusterState cluster, int thisServer,
222      int otherServer) {
223      if (thisServer < 0 || otherServer < 0) {
224        return BalanceAction.NULL_ACTION;
225      }
226
227      int thisRegion = pickRandomRegion(cluster, thisServer, 0.5);
228      int otherRegion = pickRandomRegion(cluster, otherServer, 0.5);
229
230      return getAction(thisServer, thisRegion, otherServer, otherRegion);
231    }
232
233    @Override
234    BalanceAction generate(BalancerClusterState cluster) {
235      return super.generate(cluster);
236    }
237  }
238
239  static class StochasticLoadTestBalancer extends StochasticLoadBalancer {
240    private FairRandomCandidateGenerator fairRandomCandidateGenerator =
241      new FairRandomCandidateGenerator();
242
243    StochasticLoadTestBalancer() {
244      super(new DummyMetricsStochasticBalancer());
245    }
246
247    @Override
248    protected CandidateGenerator getRandomGenerator() {
249      return fairRandomCandidateGenerator;
250    }
251  }
252}