/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hadoop.hbase.master.balancer;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.HRegionInfo;
import org.apache.hadoop.hbase.testclassification.SmallTests;
import org.apache.hadoop.hbase.ServerName;
import org.apache.hadoop.hbase.master.RackManager;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.hbase.util.Triple;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.mockito.Mockito;
@Category(SmallTests.class)
public class TestFavoredNodeAssignmentHelper {
private static List<ServerName> servers = new ArrayList<ServerName>();
private static Map<String, List<ServerName>> rackToServers = new HashMap<String,
List<ServerName>>();
private static RackManager rackManager = Mockito.mock(RackManager.class);
@BeforeClass
public static void setupBeforeClass() throws Exception {
// Set up some server -> rack mappings
// Have three racks in the cluster with 10 hosts each.
for (int i = 0; i < 40; i++) {
ServerName server = ServerName.valueOf("foo" + i + ":1234", -1);
if (i < 10) {
Mockito.when(rackManager.getRack(server)).thenReturn("rack1");
if (rackToServers.get("rack1") == null) {
List<ServerName> servers = new ArrayList<ServerName>();
rackToServers.put("rack1", servers);
}
rackToServers.get("rack1").add(server);
}
if (i >= 10 && i < 20) {
Mockito.when(rackManager.getRack(server)).thenReturn("rack2");
if (rackToServers.get("rack2") == null) {
List<ServerName> servers = new ArrayList<ServerName>();
rackToServers.put("rack2", servers);
}
rackToServers.get("rack2").add(server);
}
if (i >= 20 && i < 30) {
Mockito.when(rackManager.getRack(server)).thenReturn("rack3");
if (rackToServers.get("rack3") == null) {
List<ServerName> servers = new ArrayList<ServerName>();
rackToServers.put("rack3", servers);
}
rackToServers.get("rack3").add(server);
}
servers.add(server);
}
}
// The tests decide which racks to work with, and how many machines to
// work with from any given rack
// Return a rondom 'count' number of servers from 'rack'
private static List<ServerName> getServersFromRack(Map<String, Integer> rackToServerCount) {
List<ServerName> chosenServers = new ArrayList<ServerName>();
for (Map.Entry<String, Integer> entry : rackToServerCount.entrySet()) {
List<ServerName> servers = rackToServers.get(entry.getKey());
for (int i = 0; i < entry.getValue(); i++) {
chosenServers.add(servers.get(i));
}
}
return chosenServers;
}
@Test
public void testSmallCluster() {
// Test the case where we cannot assign favored nodes (because the number
// of nodes in the cluster is too less)
Map<String,Integer> rackToServerCount = new HashMap<String,Integer>();
rackToServerCount.put("rack1", 2);
List<ServerName> servers = getServersFromRack(rackToServerCount);
FavoredNodeAssignmentHelper helper = new FavoredNodeAssignmentHelper(servers,
new Configuration());
assertFalse(helper.canPlaceFavoredNodes());
}
@Test
public void testPlacePrimaryRSAsRoundRobin() {
// Test the regular case where there are many servers in different racks
// Test once for few regions and once for many regions
primaryRSPlacement(6, null, 10, 10, 10);
// now create lots of regions and try to place them on the limited number of machines
primaryRSPlacement(600, null, 10, 10, 10);
}
@Test
public void testRoundRobinAssignmentsWithUnevenSizedRacks() {
//In the case of uneven racks, the regions should be distributed
//proportionately to the rack sizes
primaryRSPlacement(6, null, 10, 10, 10);
primaryRSPlacement(600, null, 10, 10, 5);
primaryRSPlacement(600, null, 10, 5, 10);
primaryRSPlacement(600, null, 5, 10, 10);
primaryRSPlacement(500, null, 10, 10, 5);
primaryRSPlacement(500, null, 10, 5, 10);
primaryRSPlacement(500, null, 5, 10, 10);
primaryRSPlacement(500, null, 9, 7, 8);
primaryRSPlacement(500, null, 8, 7, 9);
primaryRSPlacement(500, null, 7, 9, 8);
primaryRSPlacement(459, null, 7, 9, 8);
}
@Test
public void testSecondaryAndTertiaryPlacementWithSingleRack() {
// Test the case where there is a single rack and we need to choose
// Primary/Secondary/Tertiary from a single rack.
Map<String,Integer> rackToServerCount = new HashMap<String,Integer>();
rackToServerCount.put("rack1", 10);
// have lots of regions to test with
Triple<Map<HRegionInfo, ServerName>, FavoredNodeAssignmentHelper, List<HRegionInfo>>
primaryRSMapAndHelper = secondaryAndTertiaryRSPlacementHelper(60000, rackToServerCount);
FavoredNodeAssignmentHelper helper = primaryRSMapAndHelper.getSecond();
Map<HRegionInfo, ServerName> primaryRSMap = primaryRSMapAndHelper.getFirst();
List<HRegionInfo> regions = primaryRSMapAndHelper.getThird();
Map<HRegionInfo, ServerName[]> secondaryAndTertiaryMap =
helper.placeSecondaryAndTertiaryRS(primaryRSMap);
// although we created lots of regions we should have no overlap on the
// primary/secondary/tertiary for any given region
for (HRegionInfo region : regions) {
ServerName[] secondaryAndTertiaryServers = secondaryAndTertiaryMap.get(region);
assertTrue(!secondaryAndTertiaryServers[0].equals(primaryRSMap.get(region)));
assertTrue(!secondaryAndTertiaryServers[1].equals(primaryRSMap.get(region)));
assertTrue(!secondaryAndTertiaryServers[0].equals(secondaryAndTertiaryServers[1]));
}
}
@Test
public void testSecondaryAndTertiaryPlacementWithSingleServer() {
// Test the case where we have a single node in the cluster. In this case
// the primary can be assigned but the secondary/tertiary would be null
Map<String,Integer> rackToServerCount = new HashMap<String,Integer>();
rackToServerCount.put("rack1", 1);
Triple<Map<HRegionInfo, ServerName>, FavoredNodeAssignmentHelper, List<HRegionInfo>>
primaryRSMapAndHelper = secondaryAndTertiaryRSPlacementHelper(1, rackToServerCount);
FavoredNodeAssignmentHelper helper = primaryRSMapAndHelper.getSecond();
Map<HRegionInfo, ServerName> primaryRSMap = primaryRSMapAndHelper.getFirst();
List<HRegionInfo> regions = primaryRSMapAndHelper.getThird();
Map<HRegionInfo, ServerName[]> secondaryAndTertiaryMap =
helper.placeSecondaryAndTertiaryRS(primaryRSMap);
// no secondary/tertiary placement in case of a single RegionServer
assertTrue(secondaryAndTertiaryMap.get(regions.get(0)) == null);
}
@Test
public void testSecondaryAndTertiaryPlacementWithMultipleRacks() {
// Test the case where we have multiple racks and the region servers
// belong to multiple racks
Map<String,Integer> rackToServerCount = new HashMap<String,Integer>();
rackToServerCount.put("rack1", 10);
rackToServerCount.put("rack2", 10);
Triple<Map<HRegionInfo, ServerName>, FavoredNodeAssignmentHelper, List<HRegionInfo>>
primaryRSMapAndHelper = secondaryAndTertiaryRSPlacementHelper(60000, rackToServerCount);
FavoredNodeAssignmentHelper helper = primaryRSMapAndHelper.getSecond();
Map<HRegionInfo, ServerName> primaryRSMap = primaryRSMapAndHelper.getFirst();
assertTrue(primaryRSMap.size() == 60000);
Map<HRegionInfo, ServerName[]> secondaryAndTertiaryMap =
helper.placeSecondaryAndTertiaryRS(primaryRSMap);
assertTrue(secondaryAndTertiaryMap.size() == 60000);
// for every region, the primary should be on one rack and the secondary/tertiary
// on another (we create a lot of regions just to increase probability of failure)
for (Map.Entry<HRegionInfo, ServerName[]> entry : secondaryAndTertiaryMap.entrySet()) {
ServerName[] allServersForRegion = entry.getValue();
String primaryRSRack = rackManager.getRack(primaryRSMap.get(entry.getKey()));
String secondaryRSRack = rackManager.getRack(allServersForRegion[0]);
String tertiaryRSRack = rackManager.getRack(allServersForRegion[1]);
assertTrue(!primaryRSRack.equals(secondaryRSRack));
assertTrue(secondaryRSRack.equals(tertiaryRSRack));
}
}
@Test
public void testSecondaryAndTertiaryPlacementWithLessThanTwoServersInRacks() {
// Test the case where we have two racks but with less than two servers in each
// We will not have enough machines to select secondary/tertiary
Map<String,Integer> rackToServerCount = new HashMap<String,Integer>();
rackToServerCount.put("rack1", 1);
rackToServerCount.put("rack2", 1);
Triple<Map<HRegionInfo, ServerName>, FavoredNodeAssignmentHelper, List<HRegionInfo>>
primaryRSMapAndHelper = secondaryAndTertiaryRSPlacementHelper(6, rackToServerCount);
FavoredNodeAssignmentHelper helper = primaryRSMapAndHelper.getSecond();
Map<HRegionInfo, ServerName> primaryRSMap = primaryRSMapAndHelper.getFirst();
List<HRegionInfo> regions = primaryRSMapAndHelper.getThird();
assertTrue(primaryRSMap.size() == 6);
Map<HRegionInfo, ServerName[]> secondaryAndTertiaryMap =
helper.placeSecondaryAndTertiaryRS(primaryRSMap);
for (HRegionInfo region : regions) {
// not enough secondary/tertiary room to place the regions
assertTrue(secondaryAndTertiaryMap.get(region) == null);
}
}
@Test
public void testSecondaryAndTertiaryPlacementWithMoreThanOneServerInPrimaryRack() {
// Test the case where there is only one server in one rack and another rack
// has more servers. We try to choose secondary/tertiary on different
// racks than what the primary is on. But if the other rack doesn't have
// enough nodes to have both secondary/tertiary RSs, the tertiary is placed
// on the same rack as the primary server is on
Map<String,Integer> rackToServerCount = new HashMap<String,Integer>();
rackToServerCount.put("rack1", 2);
rackToServerCount.put("rack2", 1);
Triple<Map<HRegionInfo, ServerName>, FavoredNodeAssignmentHelper, List<HRegionInfo>>
primaryRSMapAndHelper = secondaryAndTertiaryRSPlacementHelper(6, rackToServerCount);
FavoredNodeAssignmentHelper helper = primaryRSMapAndHelper.getSecond();
Map<HRegionInfo, ServerName> primaryRSMap = primaryRSMapAndHelper.getFirst();
List<HRegionInfo> regions = primaryRSMapAndHelper.getThird();
assertTrue(primaryRSMap.size() == 6);
Map<HRegionInfo, ServerName[]> secondaryAndTertiaryMap =
helper.placeSecondaryAndTertiaryRS(primaryRSMap);
for (HRegionInfo region : regions) {
ServerName s = primaryRSMap.get(region);
ServerName secondaryRS = secondaryAndTertiaryMap.get(region)[0];
ServerName tertiaryRS = secondaryAndTertiaryMap.get(region)[1];
if (rackManager.getRack(s).equals("rack1")) {
assertTrue(rackManager.getRack(secondaryRS).equals("rack2") &&
rackManager.getRack(tertiaryRS).equals("rack1"));
}
if (rackManager.getRack(s).equals("rack2")) {
assertTrue(rackManager.getRack(secondaryRS).equals("rack1") &&
rackManager.getRack(tertiaryRS).equals("rack1"));
}
}
}
private Triple<Map<HRegionInfo, ServerName>, FavoredNodeAssignmentHelper, List<HRegionInfo>>
secondaryAndTertiaryRSPlacementHelper(
int regionCount, Map<String, Integer> rackToServerCount) {
Map<HRegionInfo, ServerName> primaryRSMap = new HashMap<HRegionInfo, ServerName>();
List<ServerName> servers = getServersFromRack(rackToServerCount);
FavoredNodeAssignmentHelper helper = new FavoredNodeAssignmentHelper(servers, rackManager);
Map<ServerName, List<HRegionInfo>> assignmentMap =
new HashMap<ServerName, List<HRegionInfo>>();
helper.initialize();
// create regions
List<HRegionInfo> regions = new ArrayList<HRegionInfo>(regionCount);
for (int i = 0; i < regionCount; i++) {
HRegionInfo region = new HRegionInfo(TableName.valueOf("foobar"),
Bytes.toBytes(i), Bytes.toBytes(i + 1));
regions.add(region);
}
// place the regions
helper.placePrimaryRSAsRoundRobin(assignmentMap, primaryRSMap, regions);
return new Triple<Map<HRegionInfo, ServerName>, FavoredNodeAssignmentHelper, List<HRegionInfo>>
(primaryRSMap, helper, regions);
}
private void primaryRSPlacement(int regionCount, Map<HRegionInfo, ServerName> primaryRSMap,
int firstRackSize, int secondRackSize, int thirdRackSize) {
Map<String,Integer> rackToServerCount = new HashMap<String,Integer>();
rackToServerCount.put("rack1", firstRackSize);
rackToServerCount.put("rack2", secondRackSize);
rackToServerCount.put("rack3", thirdRackSize);
List<ServerName> servers = getServersFromRack(rackToServerCount);
FavoredNodeAssignmentHelper helper = new FavoredNodeAssignmentHelper(servers,
rackManager);
helper.initialize();
assertTrue(helper.canPlaceFavoredNodes());
Map<ServerName, List<HRegionInfo>> assignmentMap =
new HashMap<ServerName, List<HRegionInfo>>();
if (primaryRSMap == null) primaryRSMap = new HashMap<HRegionInfo, ServerName>();
// create some regions
List<HRegionInfo> regions = new ArrayList<HRegionInfo>(regionCount);
for (int i = 0; i < regionCount; i++) {
HRegionInfo region = new HRegionInfo(TableName.valueOf("foobar"),
Bytes.toBytes(i), Bytes.toBytes(i + 1));
regions.add(region);
}
// place those regions in primary RSs
helper.placePrimaryRSAsRoundRobin(assignmentMap, primaryRSMap, regions);
// we should have all the regions nicely spread across the racks
int regionsOnRack1 = 0;
int regionsOnRack2 = 0;
int regionsOnRack3 = 0;
for (HRegionInfo region : regions) {
if (rackManager.getRack(primaryRSMap.get(region)).equals("rack1")) {
regionsOnRack1++;
} else if (rackManager.getRack(primaryRSMap.get(region)).equals("rack2")) {
regionsOnRack2++;
} else if (rackManager.getRack(primaryRSMap.get(region)).equals("rack3")) {
regionsOnRack3++;
}
}
// Verify that the regions got placed in the way we expect (documented in
// FavoredNodeAssignmentHelper#placePrimaryRSAsRoundRobin)
checkNumRegions(regionCount, firstRackSize, secondRackSize, thirdRackSize, regionsOnRack1,
regionsOnRack2, regionsOnRack3, assignmentMap);
}
private void checkNumRegions(int regionCount, int firstRackSize, int secondRackSize,
int thirdRackSize, int regionsOnRack1, int regionsOnRack2, int regionsOnRack3,
Map<ServerName, List<HRegionInfo>> assignmentMap) {
//The regions should be distributed proportionately to the racksizes
//Verify the ordering was as expected by inserting the racks and regions
//in sorted maps. The keys being the racksize and numregions; values are
//the relative positions of the racksizes and numregions respectively
SortedMap<Integer, Integer> rackMap = new TreeMap<Integer, Integer>();
rackMap.put(firstRackSize, 1);
rackMap.put(secondRackSize, 2);
rackMap.put(thirdRackSize, 3);
SortedMap<Integer, Integer> regionMap = new TreeMap<Integer, Integer>();
regionMap.put(regionsOnRack1, 1);
regionMap.put(regionsOnRack2, 2);
regionMap.put(regionsOnRack3, 3);
assertTrue(printProportions(firstRackSize, secondRackSize, thirdRackSize,
regionsOnRack1, regionsOnRack2, regionsOnRack3),
rackMap.get(firstRackSize) == regionMap.get(regionsOnRack1));
assertTrue(printProportions(firstRackSize, secondRackSize, thirdRackSize,
regionsOnRack1, regionsOnRack2, regionsOnRack3),
rackMap.get(secondRackSize) == regionMap.get(regionsOnRack2));
assertTrue(printProportions(firstRackSize, secondRackSize, thirdRackSize,
regionsOnRack1, regionsOnRack2, regionsOnRack3),
rackMap.get(thirdRackSize) == regionMap.get(regionsOnRack3));
}
private String printProportions(int firstRackSize, int secondRackSize,
int thirdRackSize, int regionsOnRack1, int regionsOnRack2, int regionsOnRack3) {
return "The rack sizes " + firstRackSize + " " + secondRackSize
+ " " + thirdRackSize + " " + regionsOnRack1 + " " + regionsOnRack2 +
" " + regionsOnRack3;
}
}