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