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