001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase.master;
019
020import static org.junit.jupiter.api.Assertions.assertTrue;
021
022import java.io.IOException;
023import java.util.ArrayList;
024import java.util.Collection;
025import java.util.List;
026import java.util.Map;
027import java.util.Set;
028import org.apache.hadoop.conf.Configuration;
029import org.apache.hadoop.hbase.HBaseTestingUtil;
030import org.apache.hadoop.hbase.HConstants;
031import org.apache.hadoop.hbase.ServerName;
032import org.apache.hadoop.hbase.TableName;
033import org.apache.hadoop.hbase.client.RegionInfo;
034import org.apache.hadoop.hbase.client.RegionInfoBuilder;
035import org.apache.hadoop.hbase.favored.FavoredNodeAssignmentHelper;
036import org.apache.hadoop.hbase.favored.FavoredNodeLoadBalancer;
037import org.apache.hadoop.hbase.favored.FavoredNodesPlan.Position;
038import org.apache.hadoop.hbase.master.balancer.LoadBalancerFactory;
039import org.apache.hadoop.hbase.master.balancer.MasterClusterInfoProvider;
040import org.apache.hadoop.hbase.testclassification.MasterTests;
041import org.apache.hadoop.hbase.testclassification.MediumTests;
042import org.junit.jupiter.api.AfterAll;
043import org.junit.jupiter.api.BeforeAll;
044import org.junit.jupiter.api.BeforeEach;
045import org.junit.jupiter.api.Tag;
046import org.junit.jupiter.api.Test;
047import org.junit.jupiter.api.TestInfo;
048
049@Tag(MasterTests.TAG)
050@Tag(MediumTests.TAG)
051public class TestRegionPlacement2 {
052
053  private final static HBaseTestingUtil TEST_UTIL = new HBaseTestingUtil();
054  private final static int SLAVES = 7;
055  private final static int PRIMARY = Position.PRIMARY.ordinal();
056  private final static int SECONDARY = Position.SECONDARY.ordinal();
057  private final static int TERTIARY = Position.TERTIARY.ordinal();
058  private String testMethodName;
059
060  @BeforeEach
061  public void setTestMethod(TestInfo testInfo) {
062    testMethodName = testInfo.getTestMethod().get().getName();
063  }
064
065  @BeforeAll
066  public static void setupBeforeClass() throws Exception {
067    Configuration conf = TEST_UTIL.getConfiguration();
068    // Enable the favored nodes based load balancer
069    conf.setClass(HConstants.HBASE_MASTER_LOADBALANCER_CLASS, FavoredNodeLoadBalancer.class,
070      LoadBalancer.class);
071    conf.setBoolean("hbase.tests.use.shortcircuit.reads", false);
072    TEST_UTIL.startMiniCluster(SLAVES);
073  }
074
075  @AfterAll
076  public static void tearDownAfterClass() throws Exception {
077    TEST_UTIL.shutdownMiniCluster();
078  }
079
080  @Test
081  public void testFavoredNodesPresentForRoundRobinAssignment() throws IOException {
082    FavoredNodeLoadBalancer balancer =
083      (FavoredNodeLoadBalancer) LoadBalancerFactory.getLoadBalancer(TEST_UTIL.getConfiguration());
084    balancer.setClusterInfoProvider(
085      new MasterClusterInfoProvider(TEST_UTIL.getMiniHBaseCluster().getMaster()));
086    balancer
087      .setFavoredNodesManager(TEST_UTIL.getMiniHBaseCluster().getMaster().getFavoredNodesManager());
088    balancer.initialize();
089    List<ServerName> servers = new ArrayList<>();
090    for (int i = 0; i < SLAVES; i++) {
091      ServerName server = TEST_UTIL.getMiniHBaseCluster().getRegionServer(i).getServerName();
092      servers.add(server);
093    }
094    List<RegionInfo> regions = new ArrayList<>(1);
095    RegionInfo region = RegionInfoBuilder.newBuilder(TableName.valueOf(testMethodName)).build();
096    regions.add(region);
097    Map<ServerName, List<RegionInfo>> assignmentMap =
098      balancer.roundRobinAssignment(regions, servers);
099    Set<ServerName> serverBefore = assignmentMap.keySet();
100    List<ServerName> favoredNodesBefore =
101      ((FavoredNodeLoadBalancer) balancer).getFavoredNodes(region);
102    assertTrue(favoredNodesBefore.size() == FavoredNodeAssignmentHelper.FAVORED_NODES_NUM);
103    // the primary RS should be the one that the balancer's assignment returns
104    assertTrue(
105      ServerName.isSameAddress(serverBefore.iterator().next(), favoredNodesBefore.get(PRIMARY)));
106    // now remove the primary from the list of available servers
107    List<ServerName> removedServers = removeMatchingServers(serverBefore, servers);
108    // call roundRobinAssignment with the modified servers list
109    assignmentMap = balancer.roundRobinAssignment(regions, servers);
110    List<ServerName> favoredNodesAfter =
111      ((FavoredNodeLoadBalancer) balancer).getFavoredNodes(region);
112    assertTrue(favoredNodesAfter.size() == FavoredNodeAssignmentHelper.FAVORED_NODES_NUM);
113    // We don't expect the favored nodes assignments to change in multiple calls
114    // to the roundRobinAssignment method in the balancer (relevant for AssignmentManager.assign
115    // failures)
116    assertTrue(favoredNodesAfter.containsAll(favoredNodesBefore));
117    Set<ServerName> serverAfter = assignmentMap.keySet();
118    // We expect the new RegionServer assignee to be one of the favored nodes
119    // chosen earlier.
120    assertTrue(ServerName.isSameAddress(serverAfter.iterator().next(),
121      favoredNodesBefore.get(SECONDARY))
122      || ServerName.isSameAddress(serverAfter.iterator().next(), favoredNodesBefore.get(TERTIARY)));
123
124    // put back the primary in the list of available servers
125    servers.addAll(removedServers);
126    // now roundRobinAssignment with the modified servers list should return the primary
127    // as the regionserver assignee
128    assignmentMap = balancer.roundRobinAssignment(regions, servers);
129    Set<ServerName> serverWithPrimary = assignmentMap.keySet();
130    assertTrue(serverBefore.containsAll(serverWithPrimary));
131
132    // Make all the favored nodes unavailable for assignment
133    removeMatchingServers(favoredNodesAfter, servers);
134    // call roundRobinAssignment with the modified servers list
135    assignmentMap = balancer.roundRobinAssignment(regions, servers);
136    List<ServerName> favoredNodesNow = ((FavoredNodeLoadBalancer) balancer).getFavoredNodes(region);
137    assertTrue(favoredNodesNow.size() == FavoredNodeAssignmentHelper.FAVORED_NODES_NUM);
138    assertTrue(!favoredNodesNow.contains(favoredNodesAfter.get(PRIMARY))
139      && !favoredNodesNow.contains(favoredNodesAfter.get(SECONDARY))
140      && !favoredNodesNow.contains(favoredNodesAfter.get(TERTIARY)));
141  }
142
143  @Test
144  public void testFavoredNodesPresentForRandomAssignment() throws IOException {
145    FavoredNodeLoadBalancer balancer =
146      (FavoredNodeLoadBalancer) LoadBalancerFactory.getLoadBalancer(TEST_UTIL.getConfiguration());
147    balancer.setClusterInfoProvider(
148      new MasterClusterInfoProvider(TEST_UTIL.getMiniHBaseCluster().getMaster()));
149    balancer
150      .setFavoredNodesManager(TEST_UTIL.getMiniHBaseCluster().getMaster().getFavoredNodesManager());
151    balancer.initialize();
152    List<ServerName> servers = new ArrayList<>();
153    for (int i = 0; i < SLAVES; i++) {
154      ServerName server = TEST_UTIL.getMiniHBaseCluster().getRegionServer(i).getServerName();
155      servers.add(server);
156    }
157    List<RegionInfo> regions = new ArrayList<>(1);
158    RegionInfo region = RegionInfoBuilder.newBuilder(TableName.valueOf(testMethodName)).build();
159    regions.add(region);
160    ServerName serverBefore = balancer.randomAssignment(region, servers);
161    List<ServerName> favoredNodesBefore =
162      ((FavoredNodeLoadBalancer) balancer).getFavoredNodes(region);
163    assertTrue(favoredNodesBefore.size() == FavoredNodeAssignmentHelper.FAVORED_NODES_NUM);
164    // the primary RS should be the one that the balancer's assignment returns
165    assertTrue(ServerName.isSameAddress(serverBefore, favoredNodesBefore.get(PRIMARY)));
166    // now remove the primary from the list of servers
167    removeMatchingServers(serverBefore, servers);
168    // call randomAssignment with the modified servers list
169    ServerName serverAfter = balancer.randomAssignment(region, servers);
170    List<ServerName> favoredNodesAfter =
171      ((FavoredNodeLoadBalancer) balancer).getFavoredNodes(region);
172    assertTrue(favoredNodesAfter.size() == FavoredNodeAssignmentHelper.FAVORED_NODES_NUM);
173    // We don't expect the favored nodes assignments to change in multiple calls
174    // to the randomAssignment method in the balancer (relevant for AssignmentManager.assign
175    // failures)
176    assertTrue(favoredNodesAfter.containsAll(favoredNodesBefore));
177    // We expect the new RegionServer assignee to be one of the favored nodes
178    // chosen earlier.
179    assertTrue(ServerName.isSameAddress(serverAfter, favoredNodesBefore.get(SECONDARY))
180      || ServerName.isSameAddress(serverAfter, favoredNodesBefore.get(TERTIARY)));
181    // Make all the favored nodes unavailable for assignment
182    removeMatchingServers(favoredNodesAfter, servers);
183    // call randomAssignment with the modified servers list
184    balancer.randomAssignment(region, servers);
185    List<ServerName> favoredNodesNow = ((FavoredNodeLoadBalancer) balancer).getFavoredNodes(region);
186    assertTrue(favoredNodesNow.size() == FavoredNodeAssignmentHelper.FAVORED_NODES_NUM);
187    assertTrue(!favoredNodesNow.contains(favoredNodesAfter.get(PRIMARY))
188      && !favoredNodesNow.contains(favoredNodesAfter.get(SECONDARY))
189      && !favoredNodesNow.contains(favoredNodesAfter.get(TERTIARY)));
190  }
191
192  private List<ServerName> removeMatchingServers(Collection<ServerName> serversWithoutStartCode,
193    List<ServerName> servers) {
194    List<ServerName> serversToRemove = new ArrayList<>();
195    for (ServerName s : serversWithoutStartCode) {
196      serversToRemove.addAll(removeMatchingServers(s, servers));
197    }
198    return serversToRemove;
199  }
200
201  private List<ServerName> removeMatchingServers(ServerName serverWithoutStartCode,
202    List<ServerName> servers) {
203    List<ServerName> serversToRemove = new ArrayList<>();
204    for (ServerName s : servers) {
205      if (ServerName.isSameAddress(s, serverWithoutStartCode)) {
206        serversToRemove.add(s);
207      }
208    }
209    servers.removeAll(serversToRemove);
210    return serversToRemove;
211  }
212}