View Javadoc

1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  package org.apache.hadoop.hbase.master.balancer;
19  
20  import static org.junit.Assert.assertNotNull;
21  import static org.junit.Assert.assertNull;
22  import static org.junit.Assert.assertTrue;
23  
24  import java.util.ArrayList;
25  import java.util.Arrays;
26  import java.util.HashMap;
27  import java.util.HashSet;
28  import java.util.LinkedList;
29  import java.util.List;
30  import java.util.Map;
31  import java.util.Map.Entry;
32  import java.util.Queue;
33  import java.util.Random;
34  import java.util.Set;
35  import java.util.SortedSet;
36  import java.util.TreeMap;
37  import java.util.TreeSet;
38  
39  import org.apache.commons.logging.Log;
40  import org.apache.commons.logging.LogFactory;
41  import org.apache.hadoop.conf.Configuration;
42  import org.apache.hadoop.hbase.HBaseConfiguration;
43  import org.apache.hadoop.hbase.HRegionInfo;
44  import org.apache.hadoop.hbase.ServerName;
45  import org.apache.hadoop.hbase.TableName;
46  import org.apache.hadoop.hbase.client.RegionReplicaUtil;
47  import org.apache.hadoop.hbase.master.RackManager;
48  import org.apache.hadoop.hbase.master.RegionPlan;
49  import org.apache.hadoop.hbase.util.Bytes;
50  import org.apache.hadoop.net.DNSToSwitchMapping;
51  import org.junit.Assert;
52  import org.junit.BeforeClass;
53  
54  /**
55   * Class used to be the base of unit tests on load balancers. It gives helper
56   * methods to create maps of {@link ServerName} to lists of {@link HRegionInfo}
57   * and to check list of region plans.
58   *
59   */
60  public class BalancerTestBase {
61    private static final Log LOG = LogFactory.getLog(BalancerTestBase.class);
62    protected static Random rand = new Random();
63    static int regionId = 0;
64    protected static Configuration conf;
65    protected static StochasticLoadBalancer loadBalancer;
66  
67    @BeforeClass
68    public static void beforeAllTests() throws Exception {
69      conf = HBaseConfiguration.create();
70      conf.setClass("hbase.util.ip.to.rack.determiner", MockMapping.class, DNSToSwitchMapping.class);
71      conf.setFloat("hbase.master.balancer.stochastic.maxMovePercent", 0.75f);
72      conf.setFloat("hbase.regions.slop", 0.0f);
73      conf.setFloat("hbase.master.balancer.stochastic.localityCost", 0);
74      loadBalancer = new StochasticLoadBalancer();
75      loadBalancer.setConf(conf);
76    }
77  
78    protected int[] largeCluster = new int[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
79        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
80        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
81        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
82        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
83        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
84        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
85        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
86        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
87        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
88        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
89        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
90        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
91        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 56 };
92  
93    // int[testnum][servernumber] -> numregions
94    protected int[][] clusterStateMocks = new int[][]{
95        // 1 node
96        new int[]{0},
97        new int[]{1},
98        new int[]{10},
99        // 2 node
100       new int[]{0, 0},
101       new int[]{2, 0},
102       new int[]{2, 1},
103       new int[]{2, 2},
104       new int[]{2, 3},
105       new int[]{2, 4},
106       new int[]{1, 1},
107       new int[]{0, 1},
108       new int[]{10, 1},
109       new int[]{514, 1432},
110       new int[]{48, 53},
111       // 3 node
112       new int[]{0, 1, 2},
113       new int[]{1, 2, 3},
114       new int[]{0, 2, 2},
115       new int[]{0, 3, 0},
116       new int[]{0, 4, 0},
117       new int[]{20, 20, 0},
118       // 4 node
119       new int[]{0, 1, 2, 3},
120       new int[]{4, 0, 0, 0},
121       new int[]{5, 0, 0, 0},
122       new int[]{6, 6, 0, 0},
123       new int[]{6, 2, 0, 0},
124       new int[]{6, 1, 0, 0},
125       new int[]{6, 0, 0, 0},
126       new int[]{4, 4, 4, 7},
127       new int[]{4, 4, 4, 8},
128       new int[]{0, 0, 0, 7},
129       // 5 node
130       new int[]{1, 1, 1, 1, 4},
131       // more nodes
132       new int[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15},
133       new int[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 10},
134       new int[]{6, 6, 5, 6, 6, 6, 6, 6, 6, 1},
135       new int[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 54},
136       new int[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 55},
137       new int[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 56},
138       new int[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 16},
139       new int[]{1, 1, 1, 1, 1, 1, 1, 1, 1, 8},
140       new int[]{1, 1, 1, 1, 1, 1, 1, 1, 1, 9},
141       new int[]{1, 1, 1, 1, 1, 1, 1, 1, 1, 10},
142       new int[]{1, 1, 1, 1, 1, 1, 1, 1, 1, 123},
143       new int[]{1, 1, 1, 1, 1, 1, 1, 1, 1, 155},
144       new int[]{10, 7, 12, 8, 11, 10, 9, 14},
145       new int[]{13, 14, 6, 10, 10, 10, 8, 10},
146       new int[]{130, 14, 60, 10, 100, 10, 80, 10},
147       new int[]{130, 140, 60, 100, 100, 100, 80, 100},
148       new int[]{0, 5 , 5, 5, 5},
149       largeCluster,
150 
151   };
152 
153   /**
154    * Data set for testLocalityCost:
155    *
156    * [test][regions][0] = [serverIndex] -> number of regions
157    * [test][regions][regionIndex+1] = {server hosting region, locality percentage, datanodes}
158    *
159    * For each [test], there is a list of cluster config information grouped by [regions].
160    * - [0] - the first element of the [regions] list is a list of servers with the value
161    *         indicating the number of regions it hosts.
162    * - [regionIndex+1] - the remaining elements of the array are regions, where the index value
163    *         is 1 greater than the regionIndex.  This element holds an array that identifies:
164    *     [0] - the serverIndex of the server hosting this region
165    *     [1] - the locality percentage returned by getLocalityOfRegion(region, server) when the
166    *           server is hosting both region and the hdfs blocks.
167    *     [.] - the serverIndex of servers hosting the hdfs blocks, where a value of -1 indicates
168    *         a dfs server not in the list of region servers.
169    */
170   protected int[][][] clusterRegionLocationMocks = new int[][][]{
171       // Test 1: Basic region placement with 1 region server not hosting dfs block
172       //     Locality Calculation:
173       //        region[0] = 1 - 80/100 = (.2)  - server[2] hosts both the region and dfs blocks
174       //        region[1] = 1.0                - server[0] only hosts the region, not dfs blocks
175       //        region[2] = 1 - 70/100 = (.3)  - server[1] hosts both the region and dfs blocks
176       //
177       //      RESULT = 0.2 + 1.0 + 0.3 / 3.0 (3.0 is max value)
178       //             = 1.5 / 3.0
179       //             = 0.5
180       new int[][]{
181           new int[]{1, 1, 1},         // 3 region servers with 1 region each
182           new int[]{2, 80, 1, 2, 0},  // region[0] on server[2] w/ 80% locality
183           new int[]{0, 50, 1, 2},     // region[1] on server[0] w/ 50% , but no local dfs blocks
184           new int[]{1, 70, 2, 0, 1},  // region[2] on server[1] w/ 70% locality
185       },
186 
187       // Test 2: Sames as Test 1, but the last region has a datanode that isn't a region server
188       new int[][]{
189           new int[]{1, 1, 1},
190           new int[]{2, 80, 1, 2, 0},
191           new int[]{0, 50, 1, 2},
192           new int[]{1, 70, -1, 2, 0, 1},  // the first region location is not on a region server
193       },
194   };
195 
196   // This mock allows us to test the LocalityCostFunction
197   protected class MockCluster extends BaseLoadBalancer.Cluster {
198 
199     protected int[][] localityValue = null;   // [region][server] = percent of blocks
200 
201     protected MockCluster(int[][] regions) {
202 
203       // regions[0] is an array where index = serverIndex an value = number of regions
204       super(mockClusterServers(regions[0], 1), null, null, null);
205 
206       localityValue = new int[regions.length-1][];
207       // the remaining elements in the regions array contain values for:
208       //   [0] - the serverIndex of the server hosting this region
209       //   [1] - the locality percentage (in whole numbers) for the hosting region server
210       //   [.] - a list of servers hosting dfs blocks for the region (-1 means its not one
211       //         of our region servers.
212       for (int i = 1; i < regions.length; i++){
213         int regionIndex = i - 1;
214         int serverIndex = regions[i][0];
215         int locality = regions[i][1];
216         int[] locations = Arrays.copyOfRange(regions[i], 2, regions[i].length);
217 
218         regionIndexToServerIndex[regionIndex] = serverIndex;
219         localityValue[regionIndex] = new int[servers.length];
220         localityValue[regionIndex][serverIndex] = (locality > 100)? locality % 100 : locality;
221         regionLocations[regionIndex] = locations;
222       }
223     }
224 
225     @Override
226     float getLocalityOfRegion(int region, int server) {
227       // convert the locality percentage to a fraction
228       return localityValue[region][server] / 100.0f;
229     }
230   }
231 
232   // This class is introduced because IP to rack resolution can be lengthy.
233   public static class MockMapping implements DNSToSwitchMapping {
234     public MockMapping(Configuration conf) {
235     }
236 
237     public List<String> resolve(List<String> names) {
238       List<String> ret = new ArrayList<String>(names.size());
239       for (String name : names) {
240         ret.add("rack");
241       }
242       return ret;
243     }
244 
245     // do not add @Override annotations here. It mighty break compilation with earlier Hadoops
246     public void reloadCachedMappings() {
247     }
248 
249     // do not add @Override annotations here. It mighty break compilation with earlier Hadoops
250     public void reloadCachedMappings(List<String> arg0) {
251     }
252   }
253 
254   /**
255    * Invariant is that all servers have between floor(avg) and ceiling(avg)
256    * number of regions.
257    */
258   public void assertClusterAsBalanced(List<ServerAndLoad> servers) {
259     int numServers = servers.size();
260     int numRegions = 0;
261     int maxRegions = 0;
262     int minRegions = Integer.MAX_VALUE;
263     for (ServerAndLoad server : servers) {
264       int nr = server.getLoad();
265       if (nr > maxRegions) {
266         maxRegions = nr;
267       }
268       if (nr < minRegions) {
269         minRegions = nr;
270       }
271       numRegions += nr;
272     }
273     if (maxRegions - minRegions < 2) {
274       // less than 2 between max and min, can't balance
275       return;
276     }
277     int min = numRegions / numServers;
278     int max = numRegions % numServers == 0 ? min : min + 1;
279 
280     for (ServerAndLoad server : servers) {
281       assertTrue(server.getLoad() >= 0);
282       assertTrue(server.getLoad() <= max);
283       assertTrue(server.getLoad() >= min);
284     }
285   }
286 
287   /**
288    * Checks whether region replicas are not hosted on the same host.
289    */
290   public void assertRegionReplicaPlacement(Map<ServerName, List<HRegionInfo>> serverMap, RackManager rackManager) {
291     TreeMap<String, Set<HRegionInfo>> regionsPerHost = new TreeMap<String, Set<HRegionInfo>>();
292     TreeMap<String, Set<HRegionInfo>> regionsPerRack = new TreeMap<String, Set<HRegionInfo>>();
293 
294     for (Entry<ServerName, List<HRegionInfo>> entry : serverMap.entrySet()) {
295       String hostname = entry.getKey().getHostname();
296       Set<HRegionInfo> infos = regionsPerHost.get(hostname);
297       if (infos == null) {
298         infos = new HashSet<HRegionInfo>();
299         regionsPerHost.put(hostname, infos);
300       }
301 
302       for (HRegionInfo info : entry.getValue()) {
303         HRegionInfo primaryInfo = RegionReplicaUtil.getRegionInfoForDefaultReplica(info);
304         if (!infos.add(primaryInfo)) {
305           Assert.fail("Two or more region replicas are hosted on the same host after balance");
306         }
307       }
308     }
309 
310     if (rackManager == null) {
311       return;
312     }
313 
314     for (Entry<ServerName, List<HRegionInfo>> entry : serverMap.entrySet()) {
315       String rack = rackManager.getRack(entry.getKey());
316       Set<HRegionInfo> infos = regionsPerRack.get(rack);
317       if (infos == null) {
318         infos = new HashSet<HRegionInfo>();
319         regionsPerRack.put(rack, infos);
320       }
321 
322       for (HRegionInfo info : entry.getValue()) {
323         HRegionInfo primaryInfo = RegionReplicaUtil.getRegionInfoForDefaultReplica(info);
324         if (!infos.add(primaryInfo)) {
325           Assert.fail("Two or more region replicas are hosted on the same rack after balance");
326         }
327       }
328     }
329   }
330 
331   protected String printStats(List<ServerAndLoad> servers) {
332     int numServers = servers.size();
333     int totalRegions = 0;
334     for (ServerAndLoad server : servers) {
335       totalRegions += server.getLoad();
336     }
337     float average = (float) totalRegions / numServers;
338     int max = (int) Math.ceil(average);
339     int min = (int) Math.floor(average);
340     return "[srvr=" + numServers + " rgns=" + totalRegions + " avg=" + average + " max=" + max
341         + " min=" + min + "]";
342   }
343 
344   protected List<ServerAndLoad> convertToList(final Map<ServerName, List<HRegionInfo>> servers) {
345     List<ServerAndLoad> list = new ArrayList<ServerAndLoad>(servers.size());
346     for (Map.Entry<ServerName, List<HRegionInfo>> e : servers.entrySet()) {
347       list.add(new ServerAndLoad(e.getKey(), e.getValue().size()));
348     }
349     return list;
350   }
351 
352   protected String printMock(List<ServerAndLoad> balancedCluster) {
353     SortedSet<ServerAndLoad> sorted = new TreeSet<ServerAndLoad>(balancedCluster);
354     ServerAndLoad[] arr = sorted.toArray(new ServerAndLoad[sorted.size()]);
355     StringBuilder sb = new StringBuilder(sorted.size() * 4 + 4);
356     sb.append("{ ");
357     for (int i = 0; i < arr.length; i++) {
358       if (i != 0) {
359         sb.append(" , ");
360       }
361       sb.append(arr[i].getServerName().getHostname());
362       sb.append(":");
363       sb.append(arr[i].getLoad());
364     }
365     sb.append(" }");
366     return sb.toString();
367   }
368 
369   /**
370    * This assumes the RegionPlan HSI instances are the same ones in the map, so
371    * actually no need to even pass in the map, but I think it's clearer.
372    *
373    * @param list
374    * @param plans
375    * @return
376    */
377   protected List<ServerAndLoad> reconcile(List<ServerAndLoad> list,
378                                           List<RegionPlan> plans,
379                                           Map<ServerName, List<HRegionInfo>> servers) {
380     List<ServerAndLoad> result = new ArrayList<ServerAndLoad>(list.size());
381 
382     Map<ServerName, ServerAndLoad> map = new HashMap<ServerName, ServerAndLoad>(list.size());
383     for (ServerAndLoad sl : list) {
384       map.put(sl.getServerName(), sl);
385     }
386     if (plans != null) {
387       for (RegionPlan plan : plans) {
388         ServerName source = plan.getSource();
389 
390         updateLoad(map, source, -1);
391         ServerName destination = plan.getDestination();
392         updateLoad(map, destination, +1);
393 
394         servers.get(source).remove(plan.getRegionInfo());
395         servers.get(destination).add(plan.getRegionInfo());
396       }
397     }
398     result.clear();
399     result.addAll(map.values());
400     return result;
401   }
402 
403   protected void updateLoad(final Map<ServerName, ServerAndLoad> map,
404                             final ServerName sn,
405                             final int diff) {
406     ServerAndLoad sal = map.get(sn);
407     if (sal == null) sal = new ServerAndLoad(sn, 0);
408     sal = new ServerAndLoad(sn, sal.getLoad() + diff);
409     map.put(sn, sal);
410   }
411 
412   protected TreeMap<ServerName, List<HRegionInfo>> mockClusterServers(int[] mockCluster) {
413     return mockClusterServers(mockCluster, -1);
414   }
415 
416   protected BaseLoadBalancer.Cluster mockCluster(int[] mockCluster) {
417     return new BaseLoadBalancer.Cluster(
418       mockClusterServers(mockCluster, -1), null, null, null);
419   }
420 
421   protected TreeMap<ServerName, List<HRegionInfo>> mockClusterServers(int[] mockCluster, int numTables) {
422     int numServers = mockCluster.length;
423     TreeMap<ServerName, List<HRegionInfo>> servers = new TreeMap<ServerName, List<HRegionInfo>>();
424     for (int i = 0; i < numServers; i++) {
425       int numRegions = mockCluster[i];
426       ServerAndLoad sal = randomServer(0);
427       List<HRegionInfo> regions = randomRegions(numRegions, numTables);
428       servers.put(sal.getServerName(), regions);
429     }
430     return servers;
431   }
432 
433   private Queue<HRegionInfo> regionQueue = new LinkedList<HRegionInfo>();
434 
435   protected List<HRegionInfo> randomRegions(int numRegions) {
436     return randomRegions(numRegions, -1);
437   }
438 
439   protected List<HRegionInfo> randomRegions(int numRegions, int numTables) {
440     List<HRegionInfo> regions = new ArrayList<HRegionInfo>(numRegions);
441     byte[] start = new byte[16];
442     byte[] end = new byte[16];
443     rand.nextBytes(start);
444     rand.nextBytes(end);
445     for (int i = 0; i < numRegions; i++) {
446       if (!regionQueue.isEmpty()) {
447         regions.add(regionQueue.poll());
448         continue;
449       }
450       Bytes.putInt(start, 0, numRegions << 1);
451       Bytes.putInt(end, 0, (numRegions << 1) + 1);
452       TableName tableName =
453           TableName.valueOf("table" + (numTables > 0 ? rand.nextInt(numTables) : i));
454       HRegionInfo hri = new HRegionInfo(tableName, start, end, false, regionId++);
455       regions.add(hri);
456     }
457     return regions;
458   }
459 
460   protected void returnRegions(List<HRegionInfo> regions) {
461     regionQueue.addAll(regions);
462   }
463 
464   private Queue<ServerName> serverQueue = new LinkedList<ServerName>();
465 
466   protected ServerAndLoad randomServer(final int numRegionsPerServer) {
467     if (!this.serverQueue.isEmpty()) {
468       ServerName sn = this.serverQueue.poll();
469       return new ServerAndLoad(sn, numRegionsPerServer);
470     }
471     String host = "srv" + rand.nextInt(Integer.MAX_VALUE);
472     int port = rand.nextInt(60000);
473     long startCode = rand.nextLong();
474     ServerName sn = ServerName.valueOf(host, port, startCode);
475     return new ServerAndLoad(sn, numRegionsPerServer);
476   }
477 
478   protected List<ServerAndLoad> randomServers(int numServers, int numRegionsPerServer) {
479     List<ServerAndLoad> servers = new ArrayList<ServerAndLoad>(numServers);
480     for (int i = 0; i < numServers; i++) {
481       servers.add(randomServer(numRegionsPerServer));
482     }
483     return servers;
484   }
485 
486   protected void returnServer(ServerName server) {
487     serverQueue.add(server);
488   }
489 
490   protected void returnServers(List<ServerName> servers) {
491     this.serverQueue.addAll(servers);
492   }
493 
494   protected void testWithCluster(int numNodes,
495       int numRegions,
496       int numRegionsPerServer,
497       int replication,
498       int numTables,
499       boolean assertFullyBalanced, boolean assertFullyBalancedForReplicas) {
500     Map<ServerName, List<HRegionInfo>> serverMap =
501         createServerMap(numNodes, numRegions, numRegionsPerServer, replication, numTables);
502     testWithCluster(serverMap, null, assertFullyBalanced, assertFullyBalancedForReplicas);
503   }
504 
505   protected void testWithCluster(Map<ServerName, List<HRegionInfo>> serverMap,
506       RackManager rackManager, boolean assertFullyBalanced, boolean assertFullyBalancedForReplicas) {
507     List<ServerAndLoad> list = convertToList(serverMap);
508     LOG.info("Mock Cluster : " + printMock(list) + " " + printStats(list));
509 
510     loadBalancer.setRackManager(rackManager);
511     // Run the balancer.
512     List<RegionPlan> plans = loadBalancer.balanceCluster(serverMap);
513     assertNotNull(plans);
514 
515     // Check to see that this actually got to a stable place.
516     if (assertFullyBalanced || assertFullyBalancedForReplicas) {
517       // Apply the plan to the mock cluster.
518       List<ServerAndLoad> balancedCluster = reconcile(list, plans, serverMap);
519 
520       // Print out the cluster loads to make debugging easier.
521       LOG.info("Mock Balance : " + printMock(balancedCluster));
522 
523       if (assertFullyBalanced) {
524         assertClusterAsBalanced(balancedCluster);
525         List<RegionPlan> secondPlans =  loadBalancer.balanceCluster(serverMap);
526         assertNull(secondPlans);
527       }
528 
529       if (assertFullyBalancedForReplicas) {
530         assertRegionReplicaPlacement(serverMap, rackManager);
531       }
532     }
533   }
534 
535   protected Map<ServerName, List<HRegionInfo>> createServerMap(int numNodes,
536                                                              int numRegions,
537                                                              int numRegionsPerServer,
538                                                              int replication,
539                                                              int numTables) {
540     //construct a cluster of numNodes, having  a total of numRegions. Each RS will hold
541     //numRegionsPerServer many regions except for the last one, which will host all the
542     //remaining regions
543     int[] cluster = new int[numNodes];
544     for (int i =0; i < numNodes; i++) {
545       cluster[i] = numRegionsPerServer;
546     }
547     cluster[cluster.length - 1] = numRegions - ((cluster.length - 1) * numRegionsPerServer);
548     Map<ServerName, List<HRegionInfo>> clusterState = mockClusterServers(cluster, numTables);
549     if (replication > 0) {
550       // replicate the regions to the same servers
551       for (List<HRegionInfo> regions : clusterState.values()) {
552         int length = regions.size();
553         for (int i = 0; i < length; i++) {
554           for (int r = 1; r < replication ; r++) {
555             regions.add(RegionReplicaUtil.getRegionInfoForReplica(regions.get(i), r));
556           }
557         }
558       }
559     }
560 
561     return clusterState;
562   }
563 
564 }