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  
19  package org.apache.hadoop.hbase.master.balancer;
20  
21  import static org.junit.Assert.assertFalse;
22  import static org.junit.Assert.assertTrue;
23  
24  import java.util.ArrayList;
25  import java.util.HashMap;
26  import java.util.List;
27  import java.util.Map;
28  import java.util.SortedMap;
29  import java.util.TreeMap;
30  
31  import org.apache.hadoop.conf.Configuration;
32  import org.apache.hadoop.hbase.TableName;
33  import org.apache.hadoop.hbase.HRegionInfo;
34  import org.apache.hadoop.hbase.ServerName;
35  import org.apache.hadoop.hbase.master.RackManager;
36  import org.apache.hadoop.hbase.testclassification.SmallTests;
37  import org.apache.hadoop.hbase.util.Bytes;
38  import org.apache.hadoop.hbase.util.Triple;
39  import org.junit.BeforeClass;
40  import org.junit.Test;
41  import org.junit.experimental.categories.Category;
42  import org.mockito.Mockito;
43  
44  @Category(SmallTests.class)
45  public class TestFavoredNodeAssignmentHelper {
46  
47    private static List<ServerName> servers = new ArrayList<ServerName>();
48    private static Map<String, List<ServerName>> rackToServers = new HashMap<String,
49        List<ServerName>>();
50    private static RackManager rackManager = Mockito.mock(RackManager.class);
51  
52    @BeforeClass
53    public static void setupBeforeClass() throws Exception {
54      // Set up some server -> rack mappings
55      // Have three racks in the cluster with 10 hosts each.
56      for (int i = 0; i < 40; i++) {
57        ServerName server = ServerName.valueOf("foo" + i + ":1234", -1);
58        if (i < 10) {
59          Mockito.when(rackManager.getRack(server)).thenReturn("rack1");
60          if (rackToServers.get("rack1") == null) {
61            List<ServerName> servers = new ArrayList<ServerName>();
62            rackToServers.put("rack1", servers);
63          }
64          rackToServers.get("rack1").add(server);
65        }
66        if (i >= 10 && i < 20) {
67          Mockito.when(rackManager.getRack(server)).thenReturn("rack2");
68          if (rackToServers.get("rack2") == null) {
69            List<ServerName> servers = new ArrayList<ServerName>();
70            rackToServers.put("rack2", servers);
71          }
72          rackToServers.get("rack2").add(server);
73        }
74        if (i >= 20 && i < 30) {
75          Mockito.when(rackManager.getRack(server)).thenReturn("rack3");
76          if (rackToServers.get("rack3") == null) {
77            List<ServerName> servers = new ArrayList<ServerName>();
78            rackToServers.put("rack3", servers);
79          }
80          rackToServers.get("rack3").add(server);
81        }
82        servers.add(server);
83      }
84    }
85  
86    // The tests decide which racks to work with, and how many machines to
87    // work with from any given rack
88    // Return a rondom 'count' number of servers from 'rack'
89    private static List<ServerName> getServersFromRack(Map<String, Integer> rackToServerCount) {
90      List<ServerName> chosenServers = new ArrayList<ServerName>();
91      for (Map.Entry<String, Integer> entry : rackToServerCount.entrySet()) {
92        List<ServerName> servers = rackToServers.get(entry.getKey());
93        for (int i = 0; i < entry.getValue(); i++) {
94          chosenServers.add(servers.get(i));
95        }
96      }
97      return chosenServers;
98    }
99  
100   @Test
101   public void testSmallCluster() {
102     // Test the case where we cannot assign favored nodes (because the number
103     // of nodes in the cluster is too less)
104     Map<String,Integer> rackToServerCount = new HashMap<String,Integer>();
105     rackToServerCount.put("rack1", 2);
106     List<ServerName> servers = getServersFromRack(rackToServerCount);
107     FavoredNodeAssignmentHelper helper = new FavoredNodeAssignmentHelper(servers,
108         new Configuration());
109     assertFalse(helper.canPlaceFavoredNodes());
110   }
111 
112   @Test
113   public void testPlacePrimaryRSAsRoundRobin() {
114     // Test the regular case where there are many servers in different racks
115     // Test once for few regions and once for many regions
116     primaryRSPlacement(6, null, 10, 10, 10);
117     // now create lots of regions and try to place them on the limited number of machines
118     primaryRSPlacement(600, null, 10, 10, 10);
119   }
120   
121   @Test
122   public void testRoundRobinAssignmentsWithUnevenSizedRacks() {
123     //In the case of uneven racks, the regions should be distributed 
124     //proportionately to the rack sizes
125     primaryRSPlacement(6, null, 10, 10, 10);
126     primaryRSPlacement(600, null, 10, 10, 5);
127     primaryRSPlacement(600, null, 10, 5, 10);
128     primaryRSPlacement(600, null, 5, 10, 10);
129     primaryRSPlacement(500, null, 10, 10, 5);
130     primaryRSPlacement(500, null, 10, 5, 10);
131     primaryRSPlacement(500, null, 5, 10, 10);
132     primaryRSPlacement(500, null, 9, 7, 8);
133     primaryRSPlacement(500, null, 8, 7, 9);
134     primaryRSPlacement(500, null, 7, 9, 8);
135     primaryRSPlacement(459, null, 7, 9, 8);
136   }
137 
138   @Test
139   public void testSecondaryAndTertiaryPlacementWithSingleRack() {
140     // Test the case where there is a single rack and we need to choose
141     // Primary/Secondary/Tertiary from a single rack.
142     Map<String,Integer> rackToServerCount = new HashMap<String,Integer>();
143     rackToServerCount.put("rack1", 10);
144     // have lots of regions to test with
145     Triple<Map<HRegionInfo, ServerName>, FavoredNodeAssignmentHelper, List<HRegionInfo>>
146       primaryRSMapAndHelper = secondaryAndTertiaryRSPlacementHelper(60000, rackToServerCount);
147     FavoredNodeAssignmentHelper helper = primaryRSMapAndHelper.getSecond();
148     Map<HRegionInfo, ServerName> primaryRSMap = primaryRSMapAndHelper.getFirst();
149     List<HRegionInfo> regions = primaryRSMapAndHelper.getThird();
150     Map<HRegionInfo, ServerName[]> secondaryAndTertiaryMap =
151         helper.placeSecondaryAndTertiaryRS(primaryRSMap);
152     // although we created lots of regions we should have no overlap on the
153     // primary/secondary/tertiary for any given region
154     for (HRegionInfo region : regions) {
155       ServerName[] secondaryAndTertiaryServers = secondaryAndTertiaryMap.get(region);
156       assertTrue(!secondaryAndTertiaryServers[0].equals(primaryRSMap.get(region)));
157       assertTrue(!secondaryAndTertiaryServers[1].equals(primaryRSMap.get(region)));
158       assertTrue(!secondaryAndTertiaryServers[0].equals(secondaryAndTertiaryServers[1]));
159     }
160   }
161 
162   @Test
163   public void testSecondaryAndTertiaryPlacementWithSingleServer() {
164     // Test the case where we have a single node in the cluster. In this case
165     // the primary can be assigned but the secondary/tertiary would be null
166     Map<String,Integer> rackToServerCount = new HashMap<String,Integer>();
167     rackToServerCount.put("rack1", 1);
168     Triple<Map<HRegionInfo, ServerName>, FavoredNodeAssignmentHelper, List<HRegionInfo>>
169       primaryRSMapAndHelper = secondaryAndTertiaryRSPlacementHelper(1, rackToServerCount);
170     FavoredNodeAssignmentHelper helper = primaryRSMapAndHelper.getSecond();
171     Map<HRegionInfo, ServerName> primaryRSMap = primaryRSMapAndHelper.getFirst();
172     List<HRegionInfo> regions = primaryRSMapAndHelper.getThird();
173 
174     Map<HRegionInfo, ServerName[]> secondaryAndTertiaryMap =
175         helper.placeSecondaryAndTertiaryRS(primaryRSMap);
176     // no secondary/tertiary placement in case of a single RegionServer
177     assertTrue(secondaryAndTertiaryMap.get(regions.get(0)) == null);
178   }
179 
180   @Test
181   public void testSecondaryAndTertiaryPlacementWithMultipleRacks() {
182     // Test the case where we have multiple racks and the region servers
183     // belong to multiple racks
184     Map<String,Integer> rackToServerCount = new HashMap<String,Integer>();
185     rackToServerCount.put("rack1", 10);
186     rackToServerCount.put("rack2", 10);
187 
188     Triple<Map<HRegionInfo, ServerName>, FavoredNodeAssignmentHelper, List<HRegionInfo>>
189       primaryRSMapAndHelper = secondaryAndTertiaryRSPlacementHelper(60000, rackToServerCount);
190     FavoredNodeAssignmentHelper helper = primaryRSMapAndHelper.getSecond();
191     Map<HRegionInfo, ServerName> primaryRSMap = primaryRSMapAndHelper.getFirst();
192 
193     assertTrue(primaryRSMap.size() == 60000);
194     Map<HRegionInfo, ServerName[]> secondaryAndTertiaryMap =
195         helper.placeSecondaryAndTertiaryRS(primaryRSMap);
196     assertTrue(secondaryAndTertiaryMap.size() == 60000);
197     // for every region, the primary should be on one rack and the secondary/tertiary
198     // on another (we create a lot of regions just to increase probability of failure)
199     for (Map.Entry<HRegionInfo, ServerName[]> entry : secondaryAndTertiaryMap.entrySet()) {
200       ServerName[] allServersForRegion = entry.getValue();
201       String primaryRSRack = rackManager.getRack(primaryRSMap.get(entry.getKey()));
202       String secondaryRSRack = rackManager.getRack(allServersForRegion[0]);
203       String tertiaryRSRack = rackManager.getRack(allServersForRegion[1]);
204       assertTrue(!primaryRSRack.equals(secondaryRSRack));
205       assertTrue(secondaryRSRack.equals(tertiaryRSRack));
206     }
207   }
208 
209   @Test
210   public void testSecondaryAndTertiaryPlacementWithLessThanTwoServersInRacks() {
211     // Test the case where we have two racks but with less than two servers in each
212     // We will not have enough machines to select secondary/tertiary
213     Map<String,Integer> rackToServerCount = new HashMap<String,Integer>();
214     rackToServerCount.put("rack1", 1);
215     rackToServerCount.put("rack2", 1);
216     Triple<Map<HRegionInfo, ServerName>, FavoredNodeAssignmentHelper, List<HRegionInfo>>
217       primaryRSMapAndHelper = secondaryAndTertiaryRSPlacementHelper(6, rackToServerCount);
218     FavoredNodeAssignmentHelper helper = primaryRSMapAndHelper.getSecond();
219     Map<HRegionInfo, ServerName> primaryRSMap = primaryRSMapAndHelper.getFirst();
220     List<HRegionInfo> regions = primaryRSMapAndHelper.getThird();
221     assertTrue(primaryRSMap.size() == 6);
222     Map<HRegionInfo, ServerName[]> secondaryAndTertiaryMap =
223           helper.placeSecondaryAndTertiaryRS(primaryRSMap);
224     for (HRegionInfo region : regions) {
225       // not enough secondary/tertiary room to place the regions
226       assertTrue(secondaryAndTertiaryMap.get(region) == null);
227     }
228   }
229 
230   @Test
231   public void testSecondaryAndTertiaryPlacementWithMoreThanOneServerInPrimaryRack() {
232     // Test the case where there is only one server in one rack and another rack
233     // has more servers. We try to choose secondary/tertiary on different
234     // racks than what the primary is on. But if the other rack doesn't have
235     // enough nodes to have both secondary/tertiary RSs, the tertiary is placed
236     // on the same rack as the primary server is on
237     Map<String,Integer> rackToServerCount = new HashMap<String,Integer>();
238     rackToServerCount.put("rack1", 2);
239     rackToServerCount.put("rack2", 1);
240     Triple<Map<HRegionInfo, ServerName>, FavoredNodeAssignmentHelper, List<HRegionInfo>>
241       primaryRSMapAndHelper = secondaryAndTertiaryRSPlacementHelper(6, rackToServerCount);
242     FavoredNodeAssignmentHelper helper = primaryRSMapAndHelper.getSecond();
243     Map<HRegionInfo, ServerName> primaryRSMap = primaryRSMapAndHelper.getFirst();
244     List<HRegionInfo> regions = primaryRSMapAndHelper.getThird();
245     assertTrue(primaryRSMap.size() == 6);
246     Map<HRegionInfo, ServerName[]> secondaryAndTertiaryMap =
247           helper.placeSecondaryAndTertiaryRS(primaryRSMap);
248     for (HRegionInfo region : regions) {
249       ServerName s = primaryRSMap.get(region);
250       ServerName secondaryRS = secondaryAndTertiaryMap.get(region)[0];
251       ServerName tertiaryRS = secondaryAndTertiaryMap.get(region)[1];
252       if (rackManager.getRack(s).equals("rack1")) {
253         assertTrue(rackManager.getRack(secondaryRS).equals("rack2") &&
254             rackManager.getRack(tertiaryRS).equals("rack1"));
255       }
256       if (rackManager.getRack(s).equals("rack2")) {
257         assertTrue(rackManager.getRack(secondaryRS).equals("rack1") &&
258             rackManager.getRack(tertiaryRS).equals("rack1"));
259       }
260     }
261   }
262 
263   private Triple<Map<HRegionInfo, ServerName>, FavoredNodeAssignmentHelper, List<HRegionInfo>>
264   secondaryAndTertiaryRSPlacementHelper(
265       int regionCount, Map<String, Integer> rackToServerCount) {
266     Map<HRegionInfo, ServerName> primaryRSMap = new HashMap<HRegionInfo, ServerName>();
267     List<ServerName> servers = getServersFromRack(rackToServerCount);
268     FavoredNodeAssignmentHelper helper = new FavoredNodeAssignmentHelper(servers, rackManager);
269     Map<ServerName, List<HRegionInfo>> assignmentMap =
270         new HashMap<ServerName, List<HRegionInfo>>();
271     helper.initialize();
272     // create regions
273     List<HRegionInfo> regions = new ArrayList<HRegionInfo>(regionCount);
274     for (int i = 0; i < regionCount; i++) {
275       HRegionInfo region = new HRegionInfo(TableName.valueOf("foobar"),
276           Bytes.toBytes(i), Bytes.toBytes(i + 1));
277       regions.add(region);
278     }
279     // place the regions
280     helper.placePrimaryRSAsRoundRobin(assignmentMap, primaryRSMap, regions);
281     return new Triple<Map<HRegionInfo, ServerName>, FavoredNodeAssignmentHelper, List<HRegionInfo>>
282                    (primaryRSMap, helper, regions);
283   }
284 
285   private void primaryRSPlacement(int regionCount, Map<HRegionInfo, ServerName> primaryRSMap,
286       int firstRackSize, int secondRackSize, int thirdRackSize) {
287     Map<String,Integer> rackToServerCount = new HashMap<String,Integer>();
288     rackToServerCount.put("rack1", firstRackSize);
289     rackToServerCount.put("rack2", secondRackSize);
290     rackToServerCount.put("rack3", thirdRackSize);
291     List<ServerName> servers = getServersFromRack(rackToServerCount);
292     FavoredNodeAssignmentHelper helper = new FavoredNodeAssignmentHelper(servers,
293         rackManager);
294     helper.initialize();
295 
296     assertTrue(helper.canPlaceFavoredNodes());
297 
298     Map<ServerName, List<HRegionInfo>> assignmentMap =
299         new HashMap<ServerName, List<HRegionInfo>>();
300     if (primaryRSMap == null) primaryRSMap = new HashMap<HRegionInfo, ServerName>();
301     // create some regions
302     List<HRegionInfo> regions = new ArrayList<HRegionInfo>(regionCount);
303     for (int i = 0; i < regionCount; i++) {
304       HRegionInfo region = new HRegionInfo(TableName.valueOf("foobar"),
305           Bytes.toBytes(i), Bytes.toBytes(i + 1));
306       regions.add(region);
307     }
308     // place those regions in primary RSs
309     helper.placePrimaryRSAsRoundRobin(assignmentMap, primaryRSMap, regions);
310 
311     // we should have all the regions nicely spread across the racks
312     int regionsOnRack1 = 0;
313     int regionsOnRack2 = 0;
314     int regionsOnRack3 = 0;
315     for (HRegionInfo region : regions) {
316       if (rackManager.getRack(primaryRSMap.get(region)).equals("rack1")) {
317         regionsOnRack1++;
318       } else if (rackManager.getRack(primaryRSMap.get(region)).equals("rack2")) {
319         regionsOnRack2++;
320       } else if (rackManager.getRack(primaryRSMap.get(region)).equals("rack3")) {
321         regionsOnRack3++;
322       }
323     }
324     // Verify that the regions got placed in the way we expect (documented in
325     // FavoredNodeAssignmentHelper#placePrimaryRSAsRoundRobin)
326     checkNumRegions(regionCount, firstRackSize, secondRackSize, thirdRackSize, regionsOnRack1,
327         regionsOnRack2, regionsOnRack3, assignmentMap);
328   }
329 
330   private void checkNumRegions(int regionCount, int firstRackSize, int secondRackSize,
331       int thirdRackSize, int regionsOnRack1, int regionsOnRack2, int regionsOnRack3,
332       Map<ServerName, List<HRegionInfo>> assignmentMap) {
333     //The regions should be distributed proportionately to the racksizes
334     //Verify the ordering was as expected by inserting the racks and regions
335     //in sorted maps. The keys being the racksize and numregions; values are
336     //the relative positions of the racksizes and numregions respectively
337     SortedMap<Integer, Integer> rackMap = new TreeMap<Integer, Integer>();
338     rackMap.put(firstRackSize, 1);
339     rackMap.put(secondRackSize, 2);
340     rackMap.put(thirdRackSize, 3);
341     SortedMap<Integer, Integer> regionMap = new TreeMap<Integer, Integer>();
342     regionMap.put(regionsOnRack1, 1);
343     regionMap.put(regionsOnRack2, 2);
344     regionMap.put(regionsOnRack3, 3);
345     assertTrue(printProportions(firstRackSize, secondRackSize, thirdRackSize,
346         regionsOnRack1, regionsOnRack2, regionsOnRack3),
347         rackMap.get(firstRackSize) == regionMap.get(regionsOnRack1));
348     assertTrue(printProportions(firstRackSize, secondRackSize, thirdRackSize,
349         regionsOnRack1, regionsOnRack2, regionsOnRack3),
350         rackMap.get(secondRackSize) == regionMap.get(regionsOnRack2));
351     assertTrue(printProportions(firstRackSize, secondRackSize, thirdRackSize,
352         regionsOnRack1, regionsOnRack2, regionsOnRack3),
353         rackMap.get(thirdRackSize) == regionMap.get(regionsOnRack3));
354   }
355 
356   private String printProportions(int firstRackSize, int secondRackSize,
357       int thirdRackSize, int regionsOnRack1, int regionsOnRack2, int regionsOnRack3) {
358     return "The rack sizes " + firstRackSize + " " + secondRackSize
359         + " " + thirdRackSize + " " + regionsOnRack1 + " " + regionsOnRack2 +
360         " " + regionsOnRack3;
361   }
362 }