1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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
55
56
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
93 protected int[][] clusterStateMocks = new int[][]{
94
95 new int[]{0},
96 new int[]{1},
97 new int[]{10},
98
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
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
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
129 new int[]{1, 1, 1, 1, 4},
130
131 new int[]{1500, 500, 500, 500, 10, 0},
132 new int[]{1500, 500, 500, 500, 500, 0},
133
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
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
170 public void reloadCachedMappings() {
171 }
172
173
174 public void reloadCachedMappings(List<String> arg0) {
175 }
176 }
177
178
179
180
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
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
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
297
298
299
300
301
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
438 List<RegionPlan> plans = loadBalancer.balanceCluster(serverMap);
439 assertNotNull("Initial cluster balance should produce plans.", plans);
440
441
442 if (assertFullyBalanced || assertFullyBalancedForReplicas) {
443
444 List<ServerAndLoad> balancedCluster = reconcile(list, plans, serverMap);
445
446
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
468
469
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
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 }