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.assertTrue;
21  
22  import java.util.ArrayList;
23  import java.util.HashMap;
24  import java.util.HashSet;
25  import java.util.LinkedList;
26  import java.util.List;
27  import java.util.Map;
28  import java.util.Map.Entry;
29  import java.util.Queue;
30  import java.util.Random;
31  import java.util.Set;
32  import java.util.SortedSet;
33  import java.util.TreeMap;
34  import java.util.TreeSet;
35  
36  import org.apache.hadoop.conf.Configuration;
37  import org.apache.hadoop.hbase.HRegionInfo;
38  import org.apache.hadoop.hbase.ServerName;
39  import org.apache.hadoop.hbase.TableName;
40  import org.apache.hadoop.hbase.client.RegionReplicaUtil;
41  import org.apache.hadoop.hbase.master.RackManager;
42  import org.apache.hadoop.hbase.master.RegionPlan;
43  import org.apache.hadoop.hbase.util.Bytes;
44  import org.apache.hadoop.net.DNSToSwitchMapping;
45  import org.junit.Assert;
46  
47  /**
48   * Class used to be the base of unit tests on load balancers. It gives helper
49   * methods to create maps of {@link ServerName} to lists of {@link HRegionInfo}
50   * and to check list of region plans.
51   *
52   */
53  public class BalancerTestBase {
54  
55    protected static Random rand = new Random();
56    static int regionId = 0;
57  
58    // This class is introduced because IP to rack resolution can be lengthy.
59    public static class MockMapping implements DNSToSwitchMapping {
60      public MockMapping(Configuration conf) {
61      }
62  
63      public List<String> resolve(List<String> names) {
64        List<String> ret = new ArrayList<String>(names.size());
65        for (String name : names) {
66          ret.add("rack");
67        }
68        return ret;
69      }
70  
71      // do not add @Override annotations here. It mighty break compilation with earlier Hadoops
72      public void reloadCachedMappings() {
73      }
74  
75      // do not add @Override annotations here. It mighty break compilation with earlier Hadoops
76      public void reloadCachedMappings(List<String> arg0) {
77      }
78    }
79  
80    /**
81     * Invariant is that all servers have between floor(avg) and ceiling(avg)
82     * number of regions.
83     */
84    public void assertClusterAsBalanced(List<ServerAndLoad> servers) {
85      int numServers = servers.size();
86      int numRegions = 0;
87      int maxRegions = 0;
88      int minRegions = Integer.MAX_VALUE;
89      for (ServerAndLoad server : servers) {
90        int nr = server.getLoad();
91        if (nr > maxRegions) {
92          maxRegions = nr;
93        }
94        if (nr < minRegions) {
95          minRegions = nr;
96        }
97        numRegions += nr;
98      }
99      if (maxRegions - minRegions < 2) {
100       // less than 2 between max and min, can't balance
101       return;
102     }
103     int min = numRegions / numServers;
104     int max = numRegions % numServers == 0 ? min : min + 1;
105 
106     for (ServerAndLoad server : servers) {
107       assertTrue(server.getLoad() >= 0);
108       assertTrue(server.getLoad() <= max);
109       assertTrue(server.getLoad() >= min);
110     }
111   }
112 
113   /**
114    * Checks whether region replicas are not hosted on the same host.
115    */
116   public void assertRegionReplicaPlacement(Map<ServerName, List<HRegionInfo>> serverMap, RackManager rackManager) {
117     TreeMap<String, Set<HRegionInfo>> regionsPerHost = new TreeMap<String, Set<HRegionInfo>>();
118     TreeMap<String, Set<HRegionInfo>> regionsPerRack = new TreeMap<String, Set<HRegionInfo>>();
119 
120     for (Entry<ServerName, List<HRegionInfo>> entry : serverMap.entrySet()) {
121       String hostname = entry.getKey().getHostname();
122       Set<HRegionInfo> infos = regionsPerHost.get(hostname);
123       if (infos == null) {
124         infos = new HashSet<HRegionInfo>();
125         regionsPerHost.put(hostname, infos);
126       }
127 
128       for (HRegionInfo info : entry.getValue()) {
129         HRegionInfo primaryInfo = RegionReplicaUtil.getRegionInfoForDefaultReplica(info);
130         if (!infos.add(primaryInfo)) {
131           Assert.fail("Two or more region replicas are hosted on the same host after balance");
132         }
133       }
134     }
135 
136     if (rackManager == null) {
137       return;
138     }
139 
140     for (Entry<ServerName, List<HRegionInfo>> entry : serverMap.entrySet()) {
141       String rack = rackManager.getRack(entry.getKey());
142       Set<HRegionInfo> infos = regionsPerRack.get(rack);
143       if (infos == null) {
144         infos = new HashSet<HRegionInfo>();
145         regionsPerRack.put(rack, infos);
146       }
147 
148       for (HRegionInfo info : entry.getValue()) {
149         HRegionInfo primaryInfo = RegionReplicaUtil.getRegionInfoForDefaultReplica(info);
150         if (!infos.add(primaryInfo)) {
151           Assert.fail("Two or more region replicas are hosted on the same rack after balance");
152         }
153       }
154     }
155   }
156 
157   protected String printStats(List<ServerAndLoad> servers) {
158     int numServers = servers.size();
159     int totalRegions = 0;
160     for (ServerAndLoad server : servers) {
161       totalRegions += server.getLoad();
162     }
163     float average = (float) totalRegions / numServers;
164     int max = (int) Math.ceil(average);
165     int min = (int) Math.floor(average);
166     return "[srvr=" + numServers + " rgns=" + totalRegions + " avg=" + average + " max=" + max
167         + " min=" + min + "]";
168   }
169 
170   protected List<ServerAndLoad> convertToList(final Map<ServerName, List<HRegionInfo>> servers) {
171     List<ServerAndLoad> list = new ArrayList<ServerAndLoad>(servers.size());
172     for (Map.Entry<ServerName, List<HRegionInfo>> e : servers.entrySet()) {
173       list.add(new ServerAndLoad(e.getKey(), e.getValue().size()));
174     }
175     return list;
176   }
177 
178   protected String printMock(List<ServerAndLoad> balancedCluster) {
179     SortedSet<ServerAndLoad> sorted = new TreeSet<ServerAndLoad>(balancedCluster);
180     ServerAndLoad[] arr = sorted.toArray(new ServerAndLoad[sorted.size()]);
181     StringBuilder sb = new StringBuilder(sorted.size() * 4 + 4);
182     sb.append("{ ");
183     for (int i = 0; i < arr.length; i++) {
184       if (i != 0) {
185         sb.append(" , ");
186       }
187       sb.append(arr[i].getServerName().getHostname());
188       sb.append(":");
189       sb.append(arr[i].getLoad());
190     }
191     sb.append(" }");
192     return sb.toString();
193   }
194 
195   /**
196    * This assumes the RegionPlan HSI instances are the same ones in the map, so
197    * actually no need to even pass in the map, but I think it's clearer.
198    *
199    * @param list
200    * @param plans
201    * @return
202    */
203   protected List<ServerAndLoad> reconcile(List<ServerAndLoad> list,
204                                           List<RegionPlan> plans,
205                                           Map<ServerName, List<HRegionInfo>> servers) {
206     List<ServerAndLoad> result = new ArrayList<ServerAndLoad>(list.size());
207 
208     Map<ServerName, ServerAndLoad> map = new HashMap<ServerName, ServerAndLoad>(list.size());
209     for (ServerAndLoad sl : list) {
210       map.put(sl.getServerName(), sl);
211     }
212     if (plans != null) {
213       for (RegionPlan plan : plans) {
214         ServerName source = plan.getSource();
215 
216         updateLoad(map, source, -1);
217         ServerName destination = plan.getDestination();
218         updateLoad(map, destination, +1);
219 
220         servers.get(source).remove(plan.getRegionInfo());
221         servers.get(destination).add(plan.getRegionInfo());
222       }
223     }
224     result.clear();
225     result.addAll(map.values());
226     return result;
227   }
228 
229   protected void updateLoad(final Map<ServerName, ServerAndLoad> map,
230                             final ServerName sn,
231                             final int diff) {
232     ServerAndLoad sal = map.get(sn);
233     if (sal == null) sal = new ServerAndLoad(sn, 0);
234     sal = new ServerAndLoad(sn, sal.getLoad() + diff);
235     map.put(sn, sal);
236   }
237 
238   protected TreeMap<ServerName, List<HRegionInfo>> mockClusterServers(int[] mockCluster) {
239     return mockClusterServers(mockCluster, -1);
240   }
241 
242   protected BaseLoadBalancer.Cluster mockCluster(int[] mockCluster) {
243     return new BaseLoadBalancer.Cluster(
244       mockClusterServers(mockCluster, -1), null, null, null);
245   }
246 
247   protected TreeMap<ServerName, List<HRegionInfo>> mockClusterServers(int[] mockCluster, int numTables) {
248     int numServers = mockCluster.length;
249     TreeMap<ServerName, List<HRegionInfo>> servers = new TreeMap<ServerName, List<HRegionInfo>>();
250     for (int i = 0; i < numServers; i++) {
251       int numRegions = mockCluster[i];
252       ServerAndLoad sal = randomServer(0);
253       List<HRegionInfo> regions = randomRegions(numRegions, numTables);
254       servers.put(sal.getServerName(), regions);
255     }
256     return servers;
257   }
258 
259   private Queue<HRegionInfo> regionQueue = new LinkedList<HRegionInfo>();
260 
261   protected List<HRegionInfo> randomRegions(int numRegions) {
262     return randomRegions(numRegions, -1);
263   }
264 
265   protected List<HRegionInfo> randomRegions(int numRegions, int numTables) {
266     List<HRegionInfo> regions = new ArrayList<HRegionInfo>(numRegions);
267     byte[] start = new byte[16];
268     byte[] end = new byte[16];
269     rand.nextBytes(start);
270     rand.nextBytes(end);
271     for (int i = 0; i < numRegions; i++) {
272       if (!regionQueue.isEmpty()) {
273         regions.add(regionQueue.poll());
274         continue;
275       }
276       Bytes.putInt(start, 0, numRegions << 1);
277       Bytes.putInt(end, 0, (numRegions << 1) + 1);
278       TableName tableName =
279           TableName.valueOf("table" + (numTables > 0 ? rand.nextInt(numTables) : i));
280       HRegionInfo hri = new HRegionInfo(tableName, start, end, false, regionId++);
281       regions.add(hri);
282     }
283     return regions;
284   }
285 
286   protected void returnRegions(List<HRegionInfo> regions) {
287     regionQueue.addAll(regions);
288   }
289 
290   private Queue<ServerName> serverQueue = new LinkedList<ServerName>();
291 
292   protected ServerAndLoad randomServer(final int numRegionsPerServer) {
293     if (!this.serverQueue.isEmpty()) {
294       ServerName sn = this.serverQueue.poll();
295       return new ServerAndLoad(sn, numRegionsPerServer);
296     }
297     String host = "srv" + rand.nextInt(Integer.MAX_VALUE);
298     int port = rand.nextInt(60000);
299     long startCode = rand.nextLong();
300     ServerName sn = ServerName.valueOf(host, port, startCode);
301     return new ServerAndLoad(sn, numRegionsPerServer);
302   }
303 
304   protected List<ServerAndLoad> randomServers(int numServers, int numRegionsPerServer) {
305     List<ServerAndLoad> servers = new ArrayList<ServerAndLoad>(numServers);
306     for (int i = 0; i < numServers; i++) {
307       servers.add(randomServer(numRegionsPerServer));
308     }
309     return servers;
310   }
311 
312   protected void returnServer(ServerName server) {
313     serverQueue.add(server);
314   }
315 
316   protected void returnServers(List<ServerName> servers) {
317     this.serverQueue.addAll(servers);
318   }
319 
320 }