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  
23  import java.util.ArrayList;
24  import java.util.List;
25  import java.util.Map;
26  import java.util.Set;
27  import java.util.TreeMap;
28  import java.util.TreeSet;
29  
30  import org.apache.commons.logging.Log;
31  import org.apache.commons.logging.LogFactory;
32  import org.apache.hadoop.conf.Configuration;
33  import org.apache.hadoop.hbase.HBaseConfiguration;
34  import org.apache.hadoop.hbase.HRegionInfo;
35  import org.apache.hadoop.hbase.MediumTests;
36  import org.apache.hadoop.hbase.ServerName;
37  import org.apache.hadoop.hbase.master.LoadBalancer;
38  import org.apache.hadoop.hbase.master.RegionPlan;
39  import org.junit.BeforeClass;
40  import org.junit.Test;
41  import org.junit.experimental.categories.Category;
42  
43  @Category(MediumTests.class)
44  public class TestBaseLoadBalancer extends BalancerTestBase {
45  
46    private static LoadBalancer loadBalancer;
47    private static final Log LOG = LogFactory.getLog(TestStochasticLoadBalancer.class);
48  
49    int[][] regionsAndServersMocks = new int[][] {
50        // { num regions, num servers }
51        new int[] { 0, 0 }, new int[] { 0, 1 }, new int[] { 1, 1 }, new int[] { 2, 1 },
52        new int[] { 10, 1 }, new int[] { 1, 2 }, new int[] { 2, 2 }, new int[] { 3, 2 },
53        new int[] { 1, 3 }, new int[] { 2, 3 }, new int[] { 3, 3 }, new int[] { 25, 3 },
54        new int[] { 2, 10 }, new int[] { 2, 100 }, new int[] { 12, 10 }, new int[] { 12, 100 }, };
55  
56    @BeforeClass
57    public static void beforeAllTests() throws Exception {
58      Configuration conf = HBaseConfiguration.create();
59      loadBalancer = new MockBalancer();
60      loadBalancer.setConf(conf);
61    }
62  
63    public static class MockBalancer extends BaseLoadBalancer {
64  
65      @Override
66      public List<RegionPlan> balanceCluster(Map<ServerName, List<HRegionInfo>> clusterState) {
67        return null;
68      }
69  
70    }
71  
72    /**
73     * Tests immediate assignment.
74     *
75     * Invariant is that all regions have an assignment.
76     *
77     * @throws Exception
78     */
79    @Test
80    public void testImmediateAssignment() throws Exception {
81      for (int[] mock : regionsAndServersMocks) {
82        LOG.debug("testImmediateAssignment with " + mock[0] + " regions and " + mock[1] + " servers");
83        List<HRegionInfo> regions = randomRegions(mock[0]);
84        List<ServerAndLoad> servers = randomServers(mock[1], 0);
85        List<ServerName> list = getListOfServerNames(servers);
86        Map<HRegionInfo, ServerName> assignments = loadBalancer.immediateAssignment(regions, list);
87        assertImmediateAssignment(regions, list, assignments);
88        returnRegions(regions);
89        returnServers(list);
90      }
91    }
92  
93    /**
94     * All regions have an assignment.
95     * @param regions
96     * @param servers
97     * @param assignments
98     */
99    private void assertImmediateAssignment(List<HRegionInfo> regions, List<ServerName> servers,
100       Map<HRegionInfo, ServerName> assignments) {
101     for (HRegionInfo region : regions) {
102       assertTrue(assignments.containsKey(region));
103     }
104   }
105 
106   /**
107    * Tests the bulk assignment used during cluster startup.
108    *
109    * Round-robin. Should yield a balanced cluster so same invariant as the load
110    * balancer holds, all servers holding either floor(avg) or ceiling(avg).
111    *
112    * @throws Exception
113    */
114   @Test
115   public void testBulkAssignment() throws Exception {
116     for (int[] mock : regionsAndServersMocks) {
117       LOG.debug("testBulkAssignment with " + mock[0] + " regions and " + mock[1] + " servers");
118       List<HRegionInfo> regions = randomRegions(mock[0]);
119       List<ServerAndLoad> servers = randomServers(mock[1], 0);
120       List<ServerName> list = getListOfServerNames(servers);
121       Map<ServerName, List<HRegionInfo>> assignments =
122           loadBalancer.roundRobinAssignment(regions, list);
123       float average = (float) regions.size() / servers.size();
124       int min = (int) Math.floor(average);
125       int max = (int) Math.ceil(average);
126       if (assignments != null && !assignments.isEmpty()) {
127         for (List<HRegionInfo> regionList : assignments.values()) {
128           assertTrue(regionList.size() == min || regionList.size() == max);
129         }
130       }
131       returnRegions(regions);
132       returnServers(list);
133     }
134   }
135 
136   /**
137    * Test the cluster startup bulk assignment which attempts to retain
138    * assignment info.
139    * @throws Exception
140    */
141   @Test
142   public void testRetainAssignment() throws Exception {
143     // Test simple case where all same servers are there
144     List<ServerAndLoad> servers = randomServers(10, 10);
145     List<HRegionInfo> regions = randomRegions(100);
146     Map<HRegionInfo, ServerName> existing = new TreeMap<HRegionInfo, ServerName>();
147     for (int i = 0; i < regions.size(); i++) {
148       ServerName sn = servers.get(i % servers.size()).getServerName();
149       // The old server would have had same host and port, but different
150       // start code!
151       ServerName snWithOldStartCode =
152           new ServerName(sn.getHostname(), sn.getPort(), sn.getStartcode() - 10);
153       existing.put(regions.get(i), snWithOldStartCode);
154     }
155     List<ServerName> listOfServerNames = getListOfServerNames(servers);
156     Map<ServerName, List<HRegionInfo>> assignment =
157         loadBalancer.retainAssignment(existing, listOfServerNames);
158     assertRetainedAssignment(existing, listOfServerNames, assignment);
159 
160     // Include two new servers that were not there before
161     List<ServerAndLoad> servers2 = new ArrayList<ServerAndLoad>(servers);
162     servers2.add(randomServer(10));
163     servers2.add(randomServer(10));
164     listOfServerNames = getListOfServerNames(servers2);
165     assignment = loadBalancer.retainAssignment(existing, listOfServerNames);
166     assertRetainedAssignment(existing, listOfServerNames, assignment);
167 
168     // Remove two of the servers that were previously there
169     List<ServerAndLoad> servers3 = new ArrayList<ServerAndLoad>(servers);
170     servers3.remove(0);
171     servers3.remove(0);
172     listOfServerNames = getListOfServerNames(servers3);
173     assignment = loadBalancer.retainAssignment(existing, listOfServerNames);
174     assertRetainedAssignment(existing, listOfServerNames, assignment);
175   }
176 
177   private List<ServerName> getListOfServerNames(final List<ServerAndLoad> sals) {
178     List<ServerName> list = new ArrayList<ServerName>();
179     for (ServerAndLoad e : sals) {
180       list.add(e.getServerName());
181     }
182     return list;
183   }
184 
185   /**
186    * Asserts a valid retained assignment plan.
187    * <p>
188    * Must meet the following conditions:
189    * <ul>
190    * <li>Every input region has an assignment, and to an online server
191    * <li>If a region had an existing assignment to a server with the same
192    * address a a currently online server, it will be assigned to it
193    * </ul>
194    * @param existing
195    * @param servers
196    * @param assignment
197    */
198   private void assertRetainedAssignment(Map<HRegionInfo, ServerName> existing,
199       List<ServerName> servers, Map<ServerName, List<HRegionInfo>> assignment) {
200     // Verify condition 1, every region assigned, and to online server
201     Set<ServerName> onlineServerSet = new TreeSet<ServerName>(servers);
202     Set<HRegionInfo> assignedRegions = new TreeSet<HRegionInfo>();
203     for (Map.Entry<ServerName, List<HRegionInfo>> a : assignment.entrySet()) {
204       assertTrue("Region assigned to server that was not listed as online",
205         onlineServerSet.contains(a.getKey()));
206       for (HRegionInfo r : a.getValue())
207         assignedRegions.add(r);
208     }
209     assertEquals(existing.size(), assignedRegions.size());
210 
211     // Verify condition 2, if server had existing assignment, must have same
212     Set<String> onlineHostNames = new TreeSet<String>();
213     for (ServerName s : servers) {
214       onlineHostNames.add(s.getHostname());
215     }
216 
217     for (Map.Entry<ServerName, List<HRegionInfo>> a : assignment.entrySet()) {
218       ServerName assignedTo = a.getKey();
219       for (HRegionInfo r : a.getValue()) {
220         ServerName address = existing.get(r);
221         if (address != null && onlineHostNames.contains(address.getHostname())) {
222           // this region was prevously assigned somewhere, and that
223           // host is still around, then it should be re-assigned on the
224           // same host
225           assertEquals(address.getHostname(), assignedTo.getHostname());
226         }
227       }
228     }
229   }
230 
231 }