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.assertEquals;
21  import static org.junit.Assert.assertTrue;
22  import static org.mockito.Mockito.mock;
23  import static org.mockito.Mockito.when;
24  
25  import java.util.ArrayList;
26  import java.util.HashMap;
27  import java.util.List;
28  import java.util.Map;
29  import java.util.Set;
30  import java.util.TreeMap;
31  import java.util.TreeSet;
32  
33  import org.apache.commons.lang.ArrayUtils;
34  import org.apache.commons.logging.Log;
35  import org.apache.commons.logging.LogFactory;
36  import org.apache.hadoop.conf.Configuration;
37  import org.apache.hadoop.hbase.HBaseConfiguration;
38  import org.apache.hadoop.hbase.HRegionInfo;
39  import org.apache.hadoop.hbase.MediumTests;
40  import org.apache.hadoop.hbase.ServerName;
41  import org.apache.hadoop.hbase.master.LoadBalancer;
42  import org.apache.hadoop.hbase.master.RegionPlan;
43  import org.apache.hadoop.hbase.master.balancer.BaseLoadBalancer.Cluster;
44  import org.junit.BeforeClass;
45  import org.junit.Test;
46  import org.junit.experimental.categories.Category;
47  
48  import com.google.common.collect.Lists;
49  
50  @Category(MediumTests.class)
51  public class TestBaseLoadBalancer extends BalancerTestBase {
52  
53    private static LoadBalancer loadBalancer;
54    private static final Log LOG = LogFactory.getLog(TestStochasticLoadBalancer.class);
55  
56    int[][] regionsAndServersMocks = new int[][] {
57        // { num regions, num servers }
58        new int[] { 0, 0 }, new int[] { 0, 1 }, new int[] { 1, 1 }, new int[] { 2, 1 },
59        new int[] { 10, 1 }, new int[] { 1, 2 }, new int[] { 2, 2 }, new int[] { 3, 2 },
60        new int[] { 1, 3 }, new int[] { 2, 3 }, new int[] { 3, 3 }, new int[] { 25, 3 },
61        new int[] { 2, 10 }, new int[] { 2, 100 }, new int[] { 12, 10 }, new int[] { 12, 100 }, };
62  
63    @BeforeClass
64    public static void beforeAllTests() throws Exception {
65      Configuration conf = HBaseConfiguration.create();
66      loadBalancer = new MockBalancer();
67      loadBalancer.setConf(conf);
68    }
69  
70    public static class MockBalancer extends BaseLoadBalancer {
71  
72      @Override
73      public List<RegionPlan> balanceCluster(Map<ServerName, List<HRegionInfo>> clusterState) {
74        return null;
75      }
76  
77    }
78  
79    /**
80     * Tests immediate assignment.
81     *
82     * Invariant is that all regions have an assignment.
83     *
84     * @throws Exception
85     */
86    @Test
87    public void testImmediateAssignment() throws Exception {
88      for (int[] mock : regionsAndServersMocks) {
89        LOG.debug("testImmediateAssignment with " + mock[0] + " regions and " + mock[1] + " servers");
90        List<HRegionInfo> regions = randomRegions(mock[0]);
91        List<ServerAndLoad> servers = randomServers(mock[1], 0);
92        List<ServerName> list = getListOfServerNames(servers);
93        Map<HRegionInfo, ServerName> assignments = loadBalancer.immediateAssignment(regions, list);
94        assertImmediateAssignment(regions, list, assignments);
95        returnRegions(regions);
96        returnServers(list);
97      }
98    }
99  
100   /**
101    * All regions have an assignment.
102    * @param regions
103    * @param servers
104    * @param assignments
105    */
106   private void assertImmediateAssignment(List<HRegionInfo> regions, List<ServerName> servers,
107       Map<HRegionInfo, ServerName> assignments) {
108     for (HRegionInfo region : regions) {
109       assertTrue(assignments.containsKey(region));
110     }
111   }
112 
113   /**
114    * Tests the bulk assignment used during cluster startup.
115    *
116    * Round-robin. Should yield a balanced cluster so same invariant as the load
117    * balancer holds, all servers holding either floor(avg) or ceiling(avg).
118    *
119    * @throws Exception
120    */
121   @Test
122   public void testBulkAssignment() throws Exception {
123     for (int[] mock : regionsAndServersMocks) {
124       LOG.debug("testBulkAssignment with " + mock[0] + " regions and " + mock[1] + " servers");
125       List<HRegionInfo> regions = randomRegions(mock[0]);
126       List<ServerAndLoad> servers = randomServers(mock[1], 0);
127       List<ServerName> list = getListOfServerNames(servers);
128       Map<ServerName, List<HRegionInfo>> assignments =
129           loadBalancer.roundRobinAssignment(regions, list);
130       float average = (float) regions.size() / servers.size();
131       int min = (int) Math.floor(average);
132       int max = (int) Math.ceil(average);
133       if (assignments != null && !assignments.isEmpty()) {
134         for (List<HRegionInfo> regionList : assignments.values()) {
135           assertTrue(regionList.size() == min || regionList.size() == max);
136         }
137       }
138       returnRegions(regions);
139       returnServers(list);
140     }
141   }
142 
143   /**
144    * Test the cluster startup bulk assignment which attempts to retain
145    * assignment info.
146    * @throws Exception
147    */
148   @Test
149   public void testRetainAssignment() throws Exception {
150     // Test simple case where all same servers are there
151     List<ServerAndLoad> servers = randomServers(10, 10);
152     List<HRegionInfo> regions = randomRegions(100);
153     Map<HRegionInfo, ServerName> existing = new TreeMap<HRegionInfo, ServerName>();
154     for (int i = 0; i < regions.size(); i++) {
155       ServerName sn = servers.get(i % servers.size()).getServerName();
156       // The old server would have had same host and port, but different
157       // start code!
158       ServerName snWithOldStartCode =
159           ServerName.valueOf(sn.getHostname(), sn.getPort(), sn.getStartcode() - 10);
160       existing.put(regions.get(i), snWithOldStartCode);
161     }
162     List<ServerName> listOfServerNames = getListOfServerNames(servers);
163     Map<ServerName, List<HRegionInfo>> assignment =
164         loadBalancer.retainAssignment(existing, listOfServerNames);
165     assertRetainedAssignment(existing, listOfServerNames, assignment);
166 
167     // Include two new servers that were not there before
168     List<ServerAndLoad> servers2 = new ArrayList<ServerAndLoad>(servers);
169     servers2.add(randomServer(10));
170     servers2.add(randomServer(10));
171     listOfServerNames = getListOfServerNames(servers2);
172     assignment = loadBalancer.retainAssignment(existing, listOfServerNames);
173     assertRetainedAssignment(existing, listOfServerNames, assignment);
174 
175     // Remove two of the servers that were previously there
176     List<ServerAndLoad> servers3 = new ArrayList<ServerAndLoad>(servers);
177     servers3.remove(0);
178     servers3.remove(0);
179     listOfServerNames = getListOfServerNames(servers3);
180     assignment = loadBalancer.retainAssignment(existing, listOfServerNames);
181     assertRetainedAssignment(existing, listOfServerNames, assignment);
182   }
183 
184   private List<ServerName> getListOfServerNames(final List<ServerAndLoad> sals) {
185     List<ServerName> list = new ArrayList<ServerName>();
186     for (ServerAndLoad e : sals) {
187       list.add(e.getServerName());
188     }
189     return list;
190   }
191 
192   /**
193    * Asserts a valid retained assignment plan.
194    * <p>
195    * Must meet the following conditions:
196    * <ul>
197    * <li>Every input region has an assignment, and to an online server
198    * <li>If a region had an existing assignment to a server with the same
199    * address a a currently online server, it will be assigned to it
200    * </ul>
201    * @param existing
202    * @param servers
203    * @param assignment
204    */
205   private void assertRetainedAssignment(Map<HRegionInfo, ServerName> existing,
206       List<ServerName> servers, Map<ServerName, List<HRegionInfo>> assignment) {
207     // Verify condition 1, every region assigned, and to online server
208     Set<ServerName> onlineServerSet = new TreeSet<ServerName>(servers);
209     Set<HRegionInfo> assignedRegions = new TreeSet<HRegionInfo>();
210     for (Map.Entry<ServerName, List<HRegionInfo>> a : assignment.entrySet()) {
211       assertTrue("Region assigned to server that was not listed as online",
212         onlineServerSet.contains(a.getKey()));
213       for (HRegionInfo r : a.getValue())
214         assignedRegions.add(r);
215     }
216     assertEquals(existing.size(), assignedRegions.size());
217 
218     // Verify condition 2, if server had existing assignment, must have same
219     Set<String> onlineHostNames = new TreeSet<String>();
220     for (ServerName s : servers) {
221       onlineHostNames.add(s.getHostname());
222     }
223 
224     for (Map.Entry<ServerName, List<HRegionInfo>> a : assignment.entrySet()) {
225       ServerName assignedTo = a.getKey();
226       for (HRegionInfo r : a.getValue()) {
227         ServerName address = existing.get(r);
228         if (address != null && onlineHostNames.contains(address.getHostname())) {
229           // this region was prevously assigned somewhere, and that
230           // host is still around, then it should be re-assigned on the
231           // same host
232           assertEquals(address.getHostname(), assignedTo.getHostname());
233         }
234       }
235     }
236   }
237 
238   @Test
239   public void testClusterServersWithSameHostPort() {
240     // tests whether the BaseLoadBalancer.Cluster can be constructed with servers
241     // sharing same host and port
242     List<ServerName> servers = getListOfServerNames(randomServers(10, 10));
243     List<HRegionInfo> regions = randomRegions(101);
244     Map<ServerName, List<HRegionInfo>> clusterState = new HashMap<ServerName, List<HRegionInfo>>();
245 
246     assignRegions(regions, servers, clusterState);
247 
248     // construct another list of servers, but sharing same hosts and ports
249     List<ServerName> oldServers = new ArrayList<ServerName>(servers.size());
250     for (ServerName sn : servers) {
251       // The old server would have had same host and port, but different start code!
252       oldServers.add(ServerName.valueOf(sn.getHostname(), sn.getPort(), sn.getStartcode() - 10));
253     }
254 
255     regions = randomRegions(9); // some more regions
256     assignRegions(regions, oldServers, clusterState);
257 
258     // should not throw exception:
259     BaseLoadBalancer.Cluster cluster = new Cluster(clusterState, null, null);
260     assertEquals(101 + 9, cluster.numRegions);
261     assertEquals(10, cluster.numServers); // only 10 servers because they share the same host + port
262   }
263 
264   private void assignRegions(List<HRegionInfo> regions, List<ServerName> servers,
265       Map<ServerName, List<HRegionInfo>> clusterState) {
266     for (int i = 0; i < regions.size(); i++) {
267       ServerName sn = servers.get(i % servers.size());
268       List<HRegionInfo> regionsOfServer = clusterState.get(sn);
269       if (regionsOfServer == null) {
270         regionsOfServer = new ArrayList<HRegionInfo>(10);
271         clusterState.put(sn, regionsOfServer);
272       }
273 
274       regionsOfServer.add(regions.get(i));
275     }
276   }
277 
278   @Test
279   public void testClusterRegionLocations() {
280     // tests whether region locations are handled correctly in Cluster
281     List<ServerName> servers = getListOfServerNames(randomServers(10, 10));
282     List<HRegionInfo> regions = randomRegions(101);
283     Map<ServerName, List<HRegionInfo>> clusterState = new HashMap<ServerName, List<HRegionInfo>>();
284 
285     assignRegions(regions, servers, clusterState);
286 
287     // mock block locality for some regions
288     RegionLocationFinder locationFinder = mock(RegionLocationFinder.class);
289     // block locality: region:0   => {server:0}
290     //                 region:1   => {server:0, server:1}
291     //                 region:42 => {server:4, server:9, server:5}
292     when(locationFinder.getTopBlockLocations(regions.get(0))).thenReturn(
293       Lists.newArrayList(servers.get(0)));
294     when(locationFinder.getTopBlockLocations(regions.get(1))).thenReturn(
295       Lists.newArrayList(servers.get(0), servers.get(1)));
296     when(locationFinder.getTopBlockLocations(regions.get(42))).thenReturn(
297       Lists.newArrayList(servers.get(4), servers.get(9), servers.get(5)));
298     when(locationFinder.getTopBlockLocations(regions.get(43))).thenReturn(
299       Lists.newArrayList(ServerName.valueOf("foo", 0, 0))); // this server does not exists in clusterStatus
300 
301     BaseLoadBalancer.Cluster cluster = new Cluster(clusterState, null, locationFinder);
302 
303     int r0 = ArrayUtils.indexOf(cluster.regions, regions.get(0)); // this is ok, it is just a test
304     int r1 = ArrayUtils.indexOf(cluster.regions, regions.get(1));
305     int r10 = ArrayUtils.indexOf(cluster.regions, regions.get(10));
306     int r42 = ArrayUtils.indexOf(cluster.regions, regions.get(42));
307     int r43 = ArrayUtils.indexOf(cluster.regions, regions.get(43));
308 
309     int s0 = cluster.serversToIndex.get(servers.get(0).getHostAndPort());
310     int s1 = cluster.serversToIndex.get(servers.get(1).getHostAndPort());
311     int s4 = cluster.serversToIndex.get(servers.get(4).getHostAndPort());
312     int s5 = cluster.serversToIndex.get(servers.get(5).getHostAndPort());
313     int s9 = cluster.serversToIndex.get(servers.get(9).getHostAndPort());
314 
315     // region 0 locations
316     assertEquals(1, cluster.regionLocations[r0].length);
317     assertEquals(s0, cluster.regionLocations[r0][0]);
318 
319     // region 1 locations
320     assertEquals(2, cluster.regionLocations[r1].length);
321     assertEquals(s0, cluster.regionLocations[r1][0]);
322     assertEquals(s1, cluster.regionLocations[r1][1]);
323 
324     // region 10 locations
325     assertEquals(0, cluster.regionLocations[r10].length);
326 
327     // region 42 locations
328     assertEquals(3, cluster.regionLocations[r42].length);
329     assertEquals(s4, cluster.regionLocations[r42][0]);
330     assertEquals(s9, cluster.regionLocations[r42][1]);
331     assertEquals(s5, cluster.regionLocations[r42][2]);
332 
333     // region 43 locations
334     assertEquals(1, cluster.regionLocations[r43].length);
335     assertEquals(-1, cluster.regionLocations[r43][0]);
336   }
337 
338 }