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.HashMap;
26  import java.util.HashSet;
27  import java.util.LinkedList;
28  import java.util.List;
29  import java.util.Map;
30  import java.util.Map.Entry;
31  import java.util.Queue;
32  import java.util.Random;
33  import java.util.Set;
34  import java.util.SortedSet;
35  import java.util.TreeMap;
36  import java.util.TreeSet;
37  
38  import org.apache.commons.logging.Log;
39  import org.apache.commons.logging.LogFactory;
40  import org.apache.hadoop.conf.Configuration;
41  import org.apache.hadoop.hbase.HBaseConfiguration;
42  import org.apache.hadoop.hbase.HRegionInfo;
43  import org.apache.hadoop.hbase.ServerName;
44  import org.apache.hadoop.hbase.TableName;
45  import org.apache.hadoop.hbase.client.RegionReplicaUtil;
46  import org.apache.hadoop.hbase.master.RackManager;
47  import org.apache.hadoop.hbase.master.RegionPlan;
48  import org.apache.hadoop.hbase.util.Bytes;
49  import org.apache.hadoop.net.DNSToSwitchMapping;
50  import org.junit.Assert;
51  import org.junit.BeforeClass;
52  
53  /**
54   * Class used to be the base of unit tests on load balancers. It gives helper
55   * methods to create maps of {@link ServerName} to lists of {@link HRegionInfo}
56   * and to check list of region plans.
57   *
58   */
59  public class BalancerTestBase {
60    private static final Log LOG = LogFactory.getLog(BalancerTestBase.class);
61    protected static Random rand = new Random();
62    static int regionId = 0;
63    protected static Configuration conf;
64    protected static StochasticLoadBalancer loadBalancer;
65  
66    @BeforeClass
67    public static void beforeAllTests() throws Exception {
68      conf = HBaseConfiguration.create();
69      conf.setClass("hbase.util.ip.to.rack.determiner", MockMapping.class, DNSToSwitchMapping.class);
70      conf.setFloat("hbase.master.balancer.stochastic.maxMovePercent", 0.75f);
71      conf.setFloat("hbase.regions.slop", 0.0f);
72      conf.setFloat("hbase.master.balancer.stochastic.localityCost", 0);
73      loadBalancer = new StochasticLoadBalancer();
74      loadBalancer.setConf(conf);
75    }
76  
77    protected int[] largeCluster = new int[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
78        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,
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, 56 };
91  
92    // int[testnum][servernumber] -> numregions
93    protected int[][] clusterStateMocks = new int[][]{
94        // 1 node
95        new int[]{0},
96        new int[]{1},
97        new int[]{10},
98        // 2 node
99        new int[]{0, 0},
100       new int[]{2, 0},
101       new int[]{2, 1},
102       new int[]{2, 2},
103       new int[]{2, 3},
104       new int[]{2, 4},
105       new int[]{1, 1},
106       new int[]{0, 1},
107       new int[]{10, 1},
108       new int[]{514, 1432},
109       new int[]{48, 53},
110       // 3 node
111       new int[]{0, 1, 2},
112       new int[]{1, 2, 3},
113       new int[]{0, 2, 2},
114       new int[]{0, 3, 0},
115       new int[]{0, 4, 0},
116       new int[]{20, 20, 0},
117       // 4 node
118       new int[]{0, 1, 2, 3},
119       new int[]{4, 0, 0, 0},
120       new int[]{5, 0, 0, 0},
121       new int[]{6, 6, 0, 0},
122       new int[]{6, 2, 0, 0},
123       new int[]{6, 1, 0, 0},
124       new int[]{6, 0, 0, 0},
125       new int[]{4, 4, 4, 7},
126       new int[]{4, 4, 4, 8},
127       new int[]{0, 0, 0, 7},
128       // 5 node
129       new int[]{1, 1, 1, 1, 4},
130       // 6 nodes
131       new int[]{1500, 500, 500, 500, 10, 0},
132       new int[]{1500, 500, 500, 500, 500, 0},
133       // more nodes
134       new int[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15},
135       new int[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 10},
136       new int[]{6, 6, 5, 6, 6, 6, 6, 6, 6, 1},
137       new int[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 54},
138       new int[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 55},
139       new int[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 56},
140       new int[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 16},
141       new int[]{1, 1, 1, 1, 1, 1, 1, 1, 1, 8},
142       new int[]{1, 1, 1, 1, 1, 1, 1, 1, 1, 9},
143       new int[]{1, 1, 1, 1, 1, 1, 1, 1, 1, 10},
144       new int[]{1, 1, 1, 1, 1, 1, 1, 1, 1, 123},
145       new int[]{1, 1, 1, 1, 1, 1, 1, 1, 1, 155},
146       new int[]{10, 7, 12, 8, 11, 10, 9, 14},
147       new int[]{13, 14, 6, 10, 10, 10, 8, 10},
148       new int[]{130, 14, 60, 10, 100, 10, 80, 10},
149       new int[]{130, 140, 60, 100, 100, 100, 80, 100},
150       new int[]{0, 5 , 5, 5, 5},
151       largeCluster,
152 
153   };
154 
155 
156   // This class is introduced because IP to rack resolution can be lengthy.
157   public static class MockMapping implements DNSToSwitchMapping {
158     public MockMapping(Configuration conf) {
159     }
160 
161     public List<String> resolve(List<String> names) {
162       List<String> ret = new ArrayList<String>(names.size());
163       for (String name : names) {
164         ret.add("rack");
165       }
166       return ret;
167     }
168 
169     // do not add @Override annotations here. It mighty break compilation with earlier Hadoops
170     public void reloadCachedMappings() {
171     }
172 
173     // do not add @Override annotations here. It mighty break compilation with earlier Hadoops
174     public void reloadCachedMappings(List<String> arg0) {
175     }
176   }
177 
178   /**
179    * Invariant is that all servers have between floor(avg) and ceiling(avg)
180    * number of regions.
181    */
182   public void assertClusterAsBalanced(List<ServerAndLoad> servers) {
183     int numServers = servers.size();
184     int numRegions = 0;
185     int maxRegions = 0;
186     int minRegions = Integer.MAX_VALUE;
187     for (ServerAndLoad server : servers) {
188       int nr = server.getLoad();
189       if (nr > maxRegions) {
190         maxRegions = nr;
191       }
192       if (nr < minRegions) {
193         minRegions = nr;
194       }
195       numRegions += nr;
196     }
197     if (maxRegions - minRegions < 2) {
198       // less than 2 between max and min, can't balance
199       return;
200     }
201     int min = numRegions / numServers;
202     int max = numRegions % numServers == 0 ? min : min + 1;
203 
204     for (ServerAndLoad server : servers) {
205       assertTrue("All servers should have a positive load. " + server, server.getLoad() >= 0);
206       assertTrue("All servers should have load no more than " + max + ". " + server,
207           server.getLoad() <= max);
208       assertTrue("All servers should have load no less than " + min + ". " + server,
209           server.getLoad() >= min);
210     }
211   }
212 
213   /**
214    * Checks whether region replicas are not hosted on the same host.
215    */
216   public void assertRegionReplicaPlacement(Map<ServerName, List<HRegionInfo>> serverMap, RackManager rackManager) {
217     TreeMap<String, Set<HRegionInfo>> regionsPerHost = new TreeMap<String, Set<HRegionInfo>>();
218     TreeMap<String, Set<HRegionInfo>> regionsPerRack = new TreeMap<String, Set<HRegionInfo>>();
219 
220     for (Entry<ServerName, List<HRegionInfo>> entry : serverMap.entrySet()) {
221       String hostname = entry.getKey().getHostname();
222       Set<HRegionInfo> infos = regionsPerHost.get(hostname);
223       if (infos == null) {
224         infos = new HashSet<HRegionInfo>();
225         regionsPerHost.put(hostname, infos);
226       }
227 
228       for (HRegionInfo info : entry.getValue()) {
229         HRegionInfo primaryInfo = RegionReplicaUtil.getRegionInfoForDefaultReplica(info);
230         if (!infos.add(primaryInfo)) {
231           Assert.fail("Two or more region replicas are hosted on the same host after balance");
232         }
233       }
234     }
235 
236     if (rackManager == null) {
237       return;
238     }
239 
240     for (Entry<ServerName, List<HRegionInfo>> entry : serverMap.entrySet()) {
241       String rack = rackManager.getRack(entry.getKey());
242       Set<HRegionInfo> infos = regionsPerRack.get(rack);
243       if (infos == null) {
244         infos = new HashSet<HRegionInfo>();
245         regionsPerRack.put(rack, infos);
246       }
247 
248       for (HRegionInfo info : entry.getValue()) {
249         HRegionInfo primaryInfo = RegionReplicaUtil.getRegionInfoForDefaultReplica(info);
250         if (!infos.add(primaryInfo)) {
251           Assert.fail("Two or more region replicas are hosted on the same rack after balance");
252         }
253       }
254     }
255   }
256 
257   protected String printStats(List<ServerAndLoad> servers) {
258     int numServers = servers.size();
259     int totalRegions = 0;
260     for (ServerAndLoad server : servers) {
261       totalRegions += server.getLoad();
262     }
263     float average = (float) totalRegions / numServers;
264     int max = (int) Math.ceil(average);
265     int min = (int) Math.floor(average);
266     return "[srvr=" + numServers + " rgns=" + totalRegions + " avg=" + average + " max=" + max
267         + " min=" + min + "]";
268   }
269 
270   protected List<ServerAndLoad> convertToList(final Map<ServerName, List<HRegionInfo>> servers) {
271     List<ServerAndLoad> list = new ArrayList<ServerAndLoad>(servers.size());
272     for (Map.Entry<ServerName, List<HRegionInfo>> e : servers.entrySet()) {
273       list.add(new ServerAndLoad(e.getKey(), e.getValue().size()));
274     }
275     return list;
276   }
277 
278   protected String printMock(List<ServerAndLoad> balancedCluster) {
279     SortedSet<ServerAndLoad> sorted = new TreeSet<ServerAndLoad>(balancedCluster);
280     ServerAndLoad[] arr = sorted.toArray(new ServerAndLoad[sorted.size()]);
281     StringBuilder sb = new StringBuilder(sorted.size() * 4 + 4);
282     sb.append("{ ");
283     for (int i = 0; i < arr.length; i++) {
284       if (i != 0) {
285         sb.append(" , ");
286       }
287       sb.append(arr[i].getServerName().getHostname());
288       sb.append(":");
289       sb.append(arr[i].getLoad());
290     }
291     sb.append(" }");
292     return sb.toString();
293   }
294 
295   /**
296    * This assumes the RegionPlan HSI instances are the same ones in the map, so
297    * actually no need to even pass in the map, but I think it's clearer.
298    *
299    * @param list
300    * @param plans
301    * @return a list of all added {@link ServerAndLoad} values.
302    */
303   protected List<ServerAndLoad> reconcile(List<ServerAndLoad> list,
304                                           List<RegionPlan> plans,
305                                           Map<ServerName, List<HRegionInfo>> servers) {
306     List<ServerAndLoad> result = new ArrayList<ServerAndLoad>(list.size());
307 
308     Map<ServerName, ServerAndLoad> map = new HashMap<ServerName, ServerAndLoad>(list.size());
309     for (ServerAndLoad sl : list) {
310       map.put(sl.getServerName(), sl);
311     }
312     if (plans != null) {
313       for (RegionPlan plan : plans) {
314         ServerName source = plan.getSource();
315 
316         updateLoad(map, source, -1);
317         ServerName destination = plan.getDestination();
318         updateLoad(map, destination, +1);
319 
320         servers.get(source).remove(plan.getRegionInfo());
321         servers.get(destination).add(plan.getRegionInfo());
322       }
323     }
324     result.clear();
325     result.addAll(map.values());
326     return result;
327   }
328 
329   protected void updateLoad(final Map<ServerName, ServerAndLoad> map,
330                             final ServerName sn,
331                             final int diff) {
332     ServerAndLoad sal = map.get(sn);
333     if (sal == null) sal = new ServerAndLoad(sn, 0);
334     sal = new ServerAndLoad(sn, sal.getLoad() + diff);
335     map.put(sn, sal);
336   }
337 
338   protected TreeMap<ServerName, List<HRegionInfo>> mockClusterServers(int[] mockCluster) {
339     return mockClusterServers(mockCluster, -1);
340   }
341 
342   protected BaseLoadBalancer.Cluster mockCluster(int[] mockCluster) {
343     return new BaseLoadBalancer.Cluster(
344       mockClusterServers(mockCluster, -1), null, null, null);
345   }
346 
347   protected TreeMap<ServerName, List<HRegionInfo>> mockClusterServers(int[] mockCluster, int numTables) {
348     int numServers = mockCluster.length;
349     TreeMap<ServerName, List<HRegionInfo>> servers = new TreeMap<ServerName, List<HRegionInfo>>();
350     for (int i = 0; i < numServers; i++) {
351       int numRegions = mockCluster[i];
352       ServerAndLoad sal = randomServer(0);
353       List<HRegionInfo> regions = randomRegions(numRegions, numTables);
354       servers.put(sal.getServerName(), regions);
355     }
356     return servers;
357   }
358 
359   private Queue<HRegionInfo> regionQueue = new LinkedList<HRegionInfo>();
360 
361   protected List<HRegionInfo> randomRegions(int numRegions) {
362     return randomRegions(numRegions, -1);
363   }
364 
365   protected List<HRegionInfo> randomRegions(int numRegions, int numTables) {
366     List<HRegionInfo> regions = new ArrayList<HRegionInfo>(numRegions);
367     byte[] start = new byte[16];
368     byte[] end = new byte[16];
369     rand.nextBytes(start);
370     rand.nextBytes(end);
371     for (int i = 0; i < numRegions; i++) {
372       if (!regionQueue.isEmpty()) {
373         regions.add(regionQueue.poll());
374         continue;
375       }
376       Bytes.putInt(start, 0, numRegions << 1);
377       Bytes.putInt(end, 0, (numRegions << 1) + 1);
378       TableName tableName =
379           TableName.valueOf("table" + (numTables > 0 ? rand.nextInt(numTables) : i));
380       HRegionInfo hri = new HRegionInfo(tableName, start, end, false, regionId++);
381       regions.add(hri);
382     }
383     return regions;
384   }
385 
386   protected void returnRegions(List<HRegionInfo> regions) {
387     regionQueue.addAll(regions);
388   }
389 
390   private Queue<ServerName> serverQueue = new LinkedList<ServerName>();
391 
392   protected ServerAndLoad randomServer(final int numRegionsPerServer) {
393     if (!this.serverQueue.isEmpty()) {
394       ServerName sn = this.serverQueue.poll();
395       return new ServerAndLoad(sn, numRegionsPerServer);
396     }
397     String host = "srv" + rand.nextInt(Integer.MAX_VALUE);
398     int port = rand.nextInt(60000);
399     long startCode = rand.nextLong();
400     ServerName sn = ServerName.valueOf(host, port, startCode);
401     return new ServerAndLoad(sn, numRegionsPerServer);
402   }
403 
404   protected List<ServerAndLoad> randomServers(int numServers, int numRegionsPerServer) {
405     List<ServerAndLoad> servers = new ArrayList<ServerAndLoad>(numServers);
406     for (int i = 0; i < numServers; i++) {
407       servers.add(randomServer(numRegionsPerServer));
408     }
409     return servers;
410   }
411 
412   protected void returnServer(ServerName server) {
413     serverQueue.add(server);
414   }
415 
416   protected void returnServers(List<ServerName> servers) {
417     this.serverQueue.addAll(servers);
418   }
419 
420   protected void testWithCluster(int numNodes,
421       int numRegions,
422       int numRegionsPerServer,
423       int replication,
424       int numTables,
425       boolean assertFullyBalanced, boolean assertFullyBalancedForReplicas) {
426     Map<ServerName, List<HRegionInfo>> serverMap =
427         createServerMap(numNodes, numRegions, numRegionsPerServer, replication, numTables);
428     testWithCluster(serverMap, null, assertFullyBalanced, assertFullyBalancedForReplicas);
429   }
430 
431   protected void testWithCluster(Map<ServerName, List<HRegionInfo>> serverMap,
432       RackManager rackManager, boolean assertFullyBalanced, boolean assertFullyBalancedForReplicas) {
433     List<ServerAndLoad> list = convertToList(serverMap);
434     LOG.info("Mock Cluster : " + printMock(list) + " " + printStats(list));
435 
436     loadBalancer.setRackManager(rackManager);
437     // Run the balancer.
438     List<RegionPlan> plans = loadBalancer.balanceCluster(serverMap);
439     assertNotNull("Initial cluster balance should produce plans.", plans);
440 
441     // Check to see that this actually got to a stable place.
442     if (assertFullyBalanced || assertFullyBalancedForReplicas) {
443       // Apply the plan to the mock cluster.
444       List<ServerAndLoad> balancedCluster = reconcile(list, plans, serverMap);
445 
446       // Print out the cluster loads to make debugging easier.
447       LOG.info("Mock Balance : " + printMock(balancedCluster));
448 
449       if (assertFullyBalanced) {
450         assertClusterAsBalanced(balancedCluster);
451         List<RegionPlan> secondPlans =  loadBalancer.balanceCluster(serverMap);
452         assertNull("Given a requirement to be fully balanced, second attempt at plans should " +
453             "produce none.", secondPlans);
454       }
455 
456       if (assertFullyBalancedForReplicas) {
457         assertRegionReplicaPlacement(serverMap, rackManager);
458       }
459     }
460   }
461 
462   protected Map<ServerName, List<HRegionInfo>> createServerMap(int numNodes,
463                                                              int numRegions,
464                                                              int numRegionsPerServer,
465                                                              int replication,
466                                                              int numTables) {
467     //construct a cluster of numNodes, having  a total of numRegions. Each RS will hold
468     //numRegionsPerServer many regions except for the last one, which will host all the
469     //remaining regions
470     int[] cluster = new int[numNodes];
471     for (int i =0; i < numNodes; i++) {
472       cluster[i] = numRegionsPerServer;
473     }
474     cluster[cluster.length - 1] = numRegions - ((cluster.length - 1) * numRegionsPerServer);
475     Map<ServerName, List<HRegionInfo>> clusterState = mockClusterServers(cluster, numTables);
476     if (replication > 0) {
477       // replicate the regions to the same servers
478       for (List<HRegionInfo> regions : clusterState.values()) {
479         int length = regions.size();
480         for (int i = 0; i < length; i++) {
481           for (int r = 1; r < replication ; r++) {
482             regions.add(RegionReplicaUtil.getRegionInfoForReplica(regions.get(i), r));
483           }
484         }
485       }
486     }
487 
488     return clusterState;
489   }
490 
491 }