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.balancer;
019
020import static org.junit.Assert.assertEquals;
021import static org.junit.Assert.assertNotEquals;
022import static org.junit.Assert.assertNotNull;
023import static org.junit.Assert.assertTrue;
024
025import java.io.IOException;
026import java.util.Arrays;
027import java.util.EnumSet;
028import java.util.List;
029import java.util.Map;
030import org.apache.hadoop.conf.Configuration;
031import org.apache.hadoop.hbase.ClusterMetrics;
032import org.apache.hadoop.hbase.ClusterMetrics.Option;
033import org.apache.hadoop.hbase.HBaseClassTestRule;
034import org.apache.hadoop.hbase.HBaseTestingUtility;
035import org.apache.hadoop.hbase.HConstants;
036import org.apache.hadoop.hbase.MiniHBaseCluster;
037import org.apache.hadoop.hbase.ServerName;
038import org.apache.hadoop.hbase.TableName;
039import org.apache.hadoop.hbase.Waiter;
040import org.apache.hadoop.hbase.client.Admin;
041import org.apache.hadoop.hbase.client.ColumnFamilyDescriptor;
042import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder;
043import org.apache.hadoop.hbase.client.RegionInfo;
044import org.apache.hadoop.hbase.client.TableDescriptor;
045import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
046import org.apache.hadoop.hbase.favored.FavoredNodesManager;
047import org.apache.hadoop.hbase.master.HMaster;
048import org.apache.hadoop.hbase.master.LoadBalancer;
049import org.apache.hadoop.hbase.master.RackManager;
050import org.apache.hadoop.hbase.master.assignment.RegionStates;
051import org.apache.hadoop.hbase.master.balancer.BaseLoadBalancer.Cluster;
052import org.apache.hadoop.hbase.regionserver.HRegion;
053import org.apache.hadoop.hbase.regionserver.HRegionServer;
054import org.apache.hadoop.hbase.regionserver.Region;
055import org.apache.hadoop.hbase.testclassification.LargeTests;
056import org.apache.hadoop.hbase.util.Bytes;
057import org.apache.hadoop.hbase.util.JVMClusterUtil;
058import org.junit.After;
059import org.junit.Before;
060import org.junit.BeforeClass;
061import org.junit.ClassRule;
062import org.junit.Rule;
063import org.junit.Test;
064import org.junit.experimental.categories.Category;
065import org.junit.rules.TestName;
066import org.slf4j.Logger;
067import org.slf4j.LoggerFactory;
068
069import org.apache.hbase.thirdparty.com.google.common.collect.Lists;
070import org.apache.hbase.thirdparty.com.google.common.collect.Maps;
071
072@Category(LargeTests.class)
073public class TestFavoredStochasticBalancerPickers extends BalancerTestBase {
074
075  @ClassRule
076  public static final HBaseClassTestRule CLASS_RULE =
077      HBaseClassTestRule.forClass(TestFavoredStochasticBalancerPickers.class);
078
079  private static final Logger LOG =
080      LoggerFactory.getLogger(TestFavoredStochasticBalancerPickers.class);
081
082  private static final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
083  private static final int SLAVES = 6;
084  private static final int REGIONS = SLAVES * 3;
085  private static Configuration conf;
086
087  private Admin admin;
088  private MiniHBaseCluster cluster;
089
090  @Rule
091  public TestName name = new TestName();
092
093  @BeforeClass
094  public static void setupBeforeClass() throws Exception {
095    conf = TEST_UTIL.getConfiguration();
096    // Enable favored nodes based load balancer
097    conf.setClass(HConstants.HBASE_MASTER_LOADBALANCER_CLASS,
098        LoadOnlyFavoredStochasticBalancer.class, LoadBalancer.class);
099    conf.setLong("hbase.master.balancer.stochastic.maxRunningTime", 30000);
100    conf.setInt("hbase.master.balancer.stochastic.moveCost", 0);
101    conf.setBoolean("hbase.master.balancer.stochastic.execute.maxSteps", true);
102    conf.set(BaseLoadBalancer.TABLES_ON_MASTER, "none");
103  }
104
105  @Before
106  public void startCluster() throws Exception {
107    TEST_UTIL.startMiniCluster(SLAVES);
108    TEST_UTIL.getDFSCluster().waitClusterUp();
109    TEST_UTIL.getHBaseCluster().waitForActiveAndReadyMaster(120*1000);
110    cluster = TEST_UTIL.getMiniHBaseCluster();
111    admin = TEST_UTIL.getAdmin();
112    admin.setBalancerRunning(false, true);
113  }
114
115  @After
116  public void stopCluster() throws Exception {
117    TEST_UTIL.cleanupTestDir();
118    TEST_UTIL.shutdownMiniCluster();
119  }
120
121
122  @Test
123  public void testPickers() throws Exception {
124    TableName tableName = TableName.valueOf(name.getMethodName());
125    ColumnFamilyDescriptor columnFamilyDescriptor =
126        ColumnFamilyDescriptorBuilder.newBuilder(HConstants.CATALOG_FAMILY).build();
127    TableDescriptor desc = TableDescriptorBuilder
128        .newBuilder(tableName)
129        .setColumnFamily(columnFamilyDescriptor)
130        .build();
131    admin.createTable(desc, Bytes.toBytes("aaa"), Bytes.toBytes("zzz"), REGIONS);
132    TEST_UTIL.waitUntilAllRegionsAssigned(tableName);
133    TEST_UTIL.loadTable(admin.getConnection().getTable(tableName), HConstants.CATALOG_FAMILY);
134    admin.flush(tableName);
135
136    HMaster master = cluster.getMaster();
137    FavoredNodesManager fnm = master.getFavoredNodesManager();
138    ServerName masterServerName = master.getServerName();
139    List<ServerName> excludedServers = Lists.newArrayList(masterServerName);
140    final ServerName mostLoadedServer = getRSWithMaxRegions(tableName, excludedServers);
141    assertNotNull(mostLoadedServer);
142    int numRegions = getTableRegionsFromServer(tableName, mostLoadedServer).size();
143    excludedServers.add(mostLoadedServer);
144    // Lets find another server with more regions to calculate number of regions to move
145    ServerName source = getRSWithMaxRegions(tableName, excludedServers);
146    assertNotNull(source);
147    int regionsToMove = getTableRegionsFromServer(tableName, source).size()/2;
148
149    // Since move only works if the target is part of favored nodes of the region, lets get all
150    // regions that are movable to mostLoadedServer
151    List<RegionInfo> hris = getRegionsThatCanBeMoved(tableName, mostLoadedServer);
152    RegionStates rst = master.getAssignmentManager().getRegionStates();
153    for (int i = 0; i < regionsToMove; i++) {
154      final RegionInfo regionInfo = hris.get(i);
155      admin.move(regionInfo.getEncodedNameAsBytes(),
156          Bytes.toBytes(mostLoadedServer.getServerName()));
157      LOG.info("Moving region: " + hris.get(i).getRegionNameAsString() + " to " + mostLoadedServer);
158      TEST_UTIL.waitFor(60000, new Waiter.Predicate<Exception>() {
159        @Override
160        public boolean evaluate() throws Exception {
161          return ServerName.isSameAddress(
162              rst.getRegionServerOfRegion(regionInfo), mostLoadedServer);
163        }
164      });
165    }
166    final int finalRegions = numRegions + regionsToMove;
167    TEST_UTIL.waitUntilNoRegionsInTransition(60000);
168    TEST_UTIL.waitFor(60000, new Waiter.Predicate<Exception>() {
169      @Override
170      public boolean evaluate() throws Exception {
171        int numRegions = getTableRegionsFromServer(tableName, mostLoadedServer).size();
172        return (numRegions == finalRegions);
173      }
174    });
175    TEST_UTIL.getHBaseCluster().startRegionServerAndWait(60000);
176
177    Map<ServerName, List<RegionInfo>> serverAssignments = Maps.newHashMap();
178    ClusterMetrics status = admin.getClusterMetrics(EnumSet.of(Option.LIVE_SERVERS));
179    for (ServerName sn : status.getLiveServerMetrics().keySet()) {
180      if (!ServerName.isSameAddress(sn, masterServerName)) {
181        serverAssignments.put(sn, getTableRegionsFromServer(tableName, sn));
182      }
183    }
184    RegionLocationFinder regionFinder = new RegionLocationFinder();
185    regionFinder.setClusterMetrics(admin.getClusterMetrics(EnumSet.of(Option.LIVE_SERVERS)));
186    regionFinder.setConf(conf);
187    regionFinder.setServices(TEST_UTIL.getMiniHBaseCluster().getMaster());
188    Cluster cluster = new Cluster(serverAssignments, null, regionFinder, new RackManager(conf));
189    LoadOnlyFavoredStochasticBalancer balancer = (LoadOnlyFavoredStochasticBalancer) TEST_UTIL
190        .getMiniHBaseCluster().getMaster().getLoadBalancer();
191
192    cluster.sortServersByRegionCount();
193    Integer[] servers = cluster.serverIndicesSortedByRegionCount;
194    LOG.info("Servers sorted by region count:" + Arrays.toString(servers));
195    LOG.info("Cluster dump: " + cluster);
196    if (!mostLoadedServer.equals(cluster.servers[servers[servers.length -1]])) {
197      LOG.error("Most loaded server: " + mostLoadedServer + " does not match: "
198          + cluster.servers[servers[servers.length -1]]);
199    }
200    assertEquals(mostLoadedServer, cluster.servers[servers[servers.length -1]]);
201    FavoredStochasticBalancer.FavoredNodeLoadPicker loadPicker = balancer.new FavoredNodeLoadPicker();
202    boolean userRegionPicked = false;
203    for (int i = 0; i < 100; i++) {
204      if (userRegionPicked) {
205        break;
206      } else {
207        Cluster.Action action = loadPicker.generate(cluster);
208        if (action.type == Cluster.Action.Type.MOVE_REGION) {
209          Cluster.MoveRegionAction moveRegionAction = (Cluster.MoveRegionAction) action;
210          RegionInfo region = cluster.regions[moveRegionAction.region];
211          assertNotEquals(-1, moveRegionAction.toServer);
212          ServerName destinationServer = cluster.servers[moveRegionAction.toServer];
213          assertEquals(cluster.servers[moveRegionAction.fromServer], mostLoadedServer);
214          if (!region.getTable().isSystemTable()) {
215            List<ServerName> favNodes = fnm.getFavoredNodes(region);
216            assertTrue(favNodes.contains(ServerName.valueOf(destinationServer.getHostAndPort(), -1)));
217            userRegionPicked = true;
218          }
219        }
220      }
221    }
222    assertTrue("load picker did not pick expected regions in 100 iterations.", userRegionPicked);
223  }
224
225  /*
226   * A region can only be moved to one of its favored node. Hence this method helps us to
227   * get that list which makes it easy to write non-flaky tests.
228   */
229  private List<RegionInfo> getRegionsThatCanBeMoved(TableName tableName,
230      ServerName serverName) {
231    List<RegionInfo> regions = Lists.newArrayList();
232    RegionStates rst = cluster.getMaster().getAssignmentManager().getRegionStates();
233    FavoredNodesManager fnm = cluster.getMaster().getFavoredNodesManager();
234    for (RegionInfo regionInfo : fnm.getRegionsOfFavoredNode(serverName)) {
235      if (regionInfo.getTable().equals(tableName) &&
236          !ServerName.isSameAddress(rst.getRegionServerOfRegion(regionInfo), serverName)) {
237        regions.add(regionInfo);
238      }
239    }
240    return regions;
241  }
242
243  private List<RegionInfo> getTableRegionsFromServer(TableName tableName, ServerName source)
244      throws IOException {
245    List<RegionInfo> regionInfos = Lists.newArrayList();
246    HRegionServer regionServer = cluster.getRegionServer(source);
247    for (Region region : regionServer.getRegions(tableName)) {
248      regionInfos.add(region.getRegionInfo());
249    }
250    return regionInfos;
251  }
252
253  private ServerName getRSWithMaxRegions(TableName tableName, List<ServerName> excludeNodes)
254      throws IOException {
255
256    int maxRegions = 0;
257    ServerName maxLoadedServer = null;
258
259    for (JVMClusterUtil.RegionServerThread rst : cluster.getLiveRegionServerThreads()) {
260      List<HRegion> regions = rst.getRegionServer().getRegions(tableName);
261      LOG.debug("Server: " + rst.getRegionServer().getServerName() + " regions: " + regions.size());
262      if (regions.size() > maxRegions) {
263        if (excludeNodes == null ||
264            !doesMatchExcludeNodes(excludeNodes, rst.getRegionServer().getServerName())) {
265          maxRegions = regions.size();
266          maxLoadedServer = rst.getRegionServer().getServerName();
267        }
268      }
269    }
270    return maxLoadedServer;
271  }
272
273  private boolean doesMatchExcludeNodes(List<ServerName> excludeNodes, ServerName sn) {
274    for (ServerName excludeSN : excludeNodes) {
275      if (ServerName.isSameAddress(sn, excludeSN)) {
276        return true;
277      }
278    }
279    return false;
280  }
281}