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.client; 019 020import static org.apache.hadoop.hbase.favored.FavoredNodesPlan.Position.PRIMARY; 021import static org.apache.hadoop.hbase.favored.FavoredNodesPlan.Position.SECONDARY; 022import static org.apache.hadoop.hbase.favored.FavoredNodesPlan.Position.TERTIARY; 023import static org.junit.Assert.assertArrayEquals; 024import static org.junit.Assert.assertEquals; 025import static org.junit.Assert.assertNotNull; 026import static org.junit.Assert.assertNull; 027import static org.junit.Assert.assertTrue; 028 029import java.io.IOException; 030import java.net.InetSocketAddress; 031import java.util.List; 032import java.util.Map; 033import java.util.concurrent.TimeUnit; 034import org.apache.hadoop.conf.Configuration; 035import org.apache.hadoop.hbase.HBaseClassTestRule; 036import org.apache.hadoop.hbase.HBaseTestingUtility; 037import org.apache.hadoop.hbase.HConstants; 038import org.apache.hadoop.hbase.HRegionInfo; 039import org.apache.hadoop.hbase.HRegionLocation; 040import org.apache.hadoop.hbase.NamespaceDescriptor; 041import org.apache.hadoop.hbase.ServerName; 042import org.apache.hadoop.hbase.TableName; 043import org.apache.hadoop.hbase.Waiter; 044import org.apache.hadoop.hbase.favored.FavoredNodeAssignmentHelper; 045import org.apache.hadoop.hbase.favored.FavoredNodesManager; 046import org.apache.hadoop.hbase.master.LoadBalancer; 047import org.apache.hadoop.hbase.master.ServerManager; 048import org.apache.hadoop.hbase.master.balancer.BaseLoadBalancer; 049import org.apache.hadoop.hbase.master.balancer.LoadOnlyFavoredStochasticBalancer; 050import org.apache.hadoop.hbase.procedure2.ProcedureTestingUtility; 051import org.apache.hadoop.hbase.regionserver.HRegionServer; 052import org.apache.hadoop.hbase.testclassification.ClientTests; 053import org.apache.hadoop.hbase.testclassification.MediumTests; 054import org.apache.hadoop.hbase.util.Bytes; 055import org.apache.hadoop.hbase.util.JVMClusterUtil; 056import org.apache.hadoop.hbase.util.Threads; 057import org.junit.AfterClass; 058import org.junit.Before; 059import org.junit.BeforeClass; 060import org.junit.ClassRule; 061import org.junit.Rule; 062import org.junit.Test; 063import org.junit.experimental.categories.Category; 064import org.junit.rules.TestName; 065import org.slf4j.Logger; 066import org.slf4j.LoggerFactory; 067 068import org.apache.hbase.thirdparty.com.google.common.collect.Lists; 069import org.apache.hbase.thirdparty.com.google.common.collect.Maps; 070 071@Category({ ClientTests.class, MediumTests.class }) 072public class TestTableFavoredNodes { 073 074 @ClassRule 075 public static final HBaseClassTestRule CLASS_RULE = 076 HBaseClassTestRule.forClass(TestTableFavoredNodes.class); 077 078 private static final Logger LOG = LoggerFactory.getLogger(TestTableFavoredNodes.class); 079 080 private final static HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility(); 081 private final static int WAIT_TIMEOUT = 60000; 082 private final static int SLAVES = 8; 083 private FavoredNodesManager fnm; 084 private Admin admin; 085 086 private final byte[][] splitKeys = new byte[][] { Bytes.toBytes(1), Bytes.toBytes(9) }; 087 private final int NUM_REGIONS = splitKeys.length + 1; 088 089 @Rule 090 public TestName name = new TestName(); 091 092 @BeforeClass 093 public static void setupBeforeClass() throws Exception { 094 Configuration conf = TEST_UTIL.getConfiguration(); 095 // Setting FavoredNodeBalancer will enable favored nodes 096 conf.setClass(HConstants.HBASE_MASTER_LOADBALANCER_CLASS, 097 LoadOnlyFavoredStochasticBalancer.class, LoadBalancer.class); 098 conf.set(ServerManager.WAIT_ON_REGIONSERVERS_MINTOSTART, "" + SLAVES); 099 100 // This helps test if RS get the appropriate FN updates. 101 conf.set(BaseLoadBalancer.TABLES_ON_MASTER, "none"); 102 TEST_UTIL.startMiniCluster(SLAVES); 103 TEST_UTIL.getMiniHBaseCluster().waitForActiveAndReadyMaster(WAIT_TIMEOUT); 104 } 105 106 @AfterClass 107 public static void tearDownAfterClass() throws Exception { 108 TEST_UTIL.shutdownMiniCluster(); 109 TEST_UTIL.cleanupTestDir(); 110 } 111 112 @Before 113 public void setup() throws IOException { 114 fnm = TEST_UTIL.getMiniHBaseCluster().getMaster().getFavoredNodesManager(); 115 admin = TEST_UTIL.getAdmin(); 116 admin.setBalancerRunning(false, true); 117 admin.enableCatalogJanitor(false); 118 } 119 120 /* 121 * Create a table with FN enabled and check if all its regions have favored nodes set. 122 */ 123 @Test 124 public void testCreateTable() throws Exception { 125 final TableName tableName = TableName.valueOf(name.getMethodName()); 126 TEST_UTIL.createTable(tableName, Bytes.toBytes("f"), splitKeys); 127 TEST_UTIL.waitUntilAllRegionsAssigned(tableName); 128 129 // All regions should have favored nodes 130 checkIfFavoredNodeInformationIsCorrect(tableName); 131 132 List<HRegionInfo> regions = admin.getTableRegions(tableName); 133 134 TEST_UTIL.deleteTable(tableName); 135 136 checkNoFNForDeletedTable(regions); 137 } 138 139 /* 140 * Checks if favored node information is removed on table truncation. 141 */ 142 @Test 143 public void testTruncateTable() throws Exception { 144 final TableName tableName = TableName.valueOf(name.getMethodName()); 145 TEST_UTIL.createTable(tableName, Bytes.toBytes("f"), splitKeys); 146 TEST_UTIL.waitUntilAllRegionsAssigned(tableName); 147 148 // All regions should have favored nodes 149 checkIfFavoredNodeInformationIsCorrect(tableName); 150 151 List<HRegionInfo> regions = admin.getTableRegions(tableName); 152 TEST_UTIL.truncateTable(tableName, true); 153 154 checkNoFNForDeletedTable(regions); 155 checkIfFavoredNodeInformationIsCorrect(tableName); 156 157 regions = admin.getTableRegions(tableName); 158 TEST_UTIL.truncateTable(tableName, false); 159 checkNoFNForDeletedTable(regions); 160 161 TEST_UTIL.deleteTable(tableName); 162 } 163 164 /* 165 * Check if daughters inherit at-least 2 FN from parent after region split. 166 */ 167 @Test 168 public void testSplitTable() throws Exception { 169 final TableName tableName = TableName.valueOf(name.getMethodName()); 170 Table t = TEST_UTIL.createTable(tableName, Bytes.toBytes("f"), splitKeys); 171 TEST_UTIL.waitUntilAllRegionsAssigned(tableName); 172 final int numberOfRegions = admin.getTableRegions(t.getName()).size(); 173 174 checkIfFavoredNodeInformationIsCorrect(tableName); 175 176 byte[] splitPoint = Bytes.toBytes(0); 177 RegionLocator locator = TEST_UTIL.getConnection().getRegionLocator(tableName); 178 HRegionInfo parent = locator.getRegionLocation(splitPoint).getRegionInfo(); 179 List<ServerName> parentFN = fnm.getFavoredNodes(parent); 180 assertNotNull("FN should not be null for region: " + parent, parentFN); 181 182 LOG.info("SPLITTING TABLE"); 183 admin.split(tableName, splitPoint); 184 185 TEST_UTIL.waitUntilNoRegionsInTransition(WAIT_TIMEOUT); 186 LOG.info("FINISHED WAITING ON RIT"); 187 waitUntilTableRegionCountReached(tableName, numberOfRegions + 1); 188 189 // All regions should have favored nodes checkIfFavoredNodeInformationIsCorrect(tableName); 190 191 // Get the daughters of parent. 192 HRegionInfo daughter1 = locator.getRegionLocation(parent.getStartKey(), true).getRegionInfo(); 193 List<ServerName> daughter1FN = fnm.getFavoredNodes(daughter1); 194 195 HRegionInfo daughter2 = locator.getRegionLocation(splitPoint, true).getRegionInfo(); 196 List<ServerName> daughter2FN = fnm.getFavoredNodes(daughter2); 197 198 checkIfDaughterInherits2FN(parentFN, daughter1FN); 199 checkIfDaughterInherits2FN(parentFN, daughter2FN); 200 201 assertEquals("Daughter's PRIMARY FN should be PRIMARY of parent", 202 parentFN.get(PRIMARY.ordinal()), daughter1FN.get(PRIMARY.ordinal())); 203 assertEquals("Daughter's SECONDARY FN should be SECONDARY of parent", 204 parentFN.get(SECONDARY.ordinal()), daughter1FN.get(SECONDARY.ordinal())); 205 206 assertEquals("Daughter's PRIMARY FN should be PRIMARY of parent", 207 parentFN.get(PRIMARY.ordinal()), daughter2FN.get(PRIMARY.ordinal())); 208 assertEquals("Daughter's SECONDARY FN should be TERTIARY of parent", 209 parentFN.get(TERTIARY.ordinal()), daughter2FN.get(SECONDARY.ordinal())); 210 211 // Major compact table and run catalog janitor. Parent's FN should be removed 212 TEST_UTIL.getMiniHBaseCluster().compact(tableName, true); 213 admin.runCatalogScan(); 214 // Catalog cleanup is async. Wait on procedure to finish up. 215 ProcedureTestingUtility 216 .waitAllProcedures(TEST_UTIL.getMiniHBaseCluster().getMaster().getMasterProcedureExecutor()); 217 // assertEquals("Parent region should have been cleaned", 1, admin.runCatalogScan()); 218 assertNull("Parent FN should be null", fnm.getFavoredNodes(parent)); 219 220 List<HRegionInfo> regions = admin.getTableRegions(tableName); 221 // Split and Table Disable interfere with each other around region replicas 222 // TODO. Meantime pause a few seconds. 223 Threads.sleep(2000); 224 LOG.info("STARTING DELETE"); 225 TEST_UTIL.deleteTable(tableName); 226 227 checkNoFNForDeletedTable(regions); 228 } 229 230 /* 231 * Check if merged region inherits FN from one of its regions. 232 */ 233 @Test 234 public void testMergeTable() throws Exception { 235 final TableName tableName = TableName.valueOf(name.getMethodName()); 236 TEST_UTIL.createTable(tableName, Bytes.toBytes("f"), splitKeys); 237 TEST_UTIL.waitUntilAllRegionsAssigned(tableName); 238 239 checkIfFavoredNodeInformationIsCorrect(tableName); 240 241 RegionLocator locator = TEST_UTIL.getConnection().getRegionLocator(tableName); 242 HRegionInfo regionA = locator.getRegionLocation(HConstants.EMPTY_START_ROW).getRegionInfo(); 243 HRegionInfo regionB = locator.getRegionLocation(splitKeys[0]).getRegionInfo(); 244 245 List<ServerName> regionAFN = fnm.getFavoredNodes(regionA); 246 LOG.info("regionA: " + regionA.getEncodedName() + " with FN: " + fnm.getFavoredNodes(regionA)); 247 LOG.info("regionB: " + regionA.getEncodedName() + " with FN: " + fnm.getFavoredNodes(regionB)); 248 249 int countOfRegions = TEST_UTIL.getMiniHBaseCluster().getRegions(tableName).size(); 250 admin.mergeRegionsAsync(regionA.getEncodedNameAsBytes(), regionB.getEncodedNameAsBytes(), false) 251 .get(60, TimeUnit.SECONDS); 252 253 TEST_UTIL.waitUntilNoRegionsInTransition(WAIT_TIMEOUT); 254 waitUntilTableRegionCountReached(tableName, countOfRegions - 1); 255 256 // All regions should have favored nodes 257 checkIfFavoredNodeInformationIsCorrect(tableName); 258 259 HRegionInfo mergedRegion = 260 locator.getRegionLocation(HConstants.EMPTY_START_ROW).getRegionInfo(); 261 List<ServerName> mergedFN = fnm.getFavoredNodes(mergedRegion); 262 263 assertArrayEquals("Merged region doesn't match regionA's FN", regionAFN.toArray(), 264 mergedFN.toArray()); 265 266 // Major compact table and run catalog janitor. Parent FN should be removed 267 TEST_UTIL.getMiniHBaseCluster().compact(tableName, true); 268 assertEquals("Merge parents should have been cleaned", 1, admin.runCatalogScan()); 269 // Catalog cleanup is async. Wait on procedure to finish up. 270 ProcedureTestingUtility 271 .waitAllProcedures(TEST_UTIL.getMiniHBaseCluster().getMaster().getMasterProcedureExecutor()); 272 assertNull("Parent FN should be null", fnm.getFavoredNodes(regionA)); 273 assertNull("Parent FN should be null", fnm.getFavoredNodes(regionB)); 274 275 List<HRegionInfo> regions = admin.getTableRegions(tableName); 276 277 TEST_UTIL.deleteTable(tableName); 278 279 checkNoFNForDeletedTable(regions); 280 } 281 282 private void checkNoFNForDeletedTable(List<HRegionInfo> regions) { 283 for (HRegionInfo region : regions) { 284 LOG.info("Testing if FN data for " + region); 285 assertNull("FN not null for deleted table's region: " + region, fnm.getFavoredNodes(region)); 286 } 287 } 288 289 /* 290 * This checks the following: 1. Do all regions of the table have favored nodes updated in master? 291 * 2. Is the number of favored nodes correct for a region? Is the start code -1? 3. Is the FN 292 * information consistent between Master and the respective RegionServer? 293 */ 294 private void checkIfFavoredNodeInformationIsCorrect(TableName tableName) throws Exception { 295 296 /* 297 * Since we need HRegionServer to check for consistency of FN between Master and RS, lets 298 * construct a map for each serverName lookup. Makes it easy later. 299 */ 300 Map<ServerName, HRegionServer> snRSMap = Maps.newHashMap(); 301 for (JVMClusterUtil.RegionServerThread rst : TEST_UTIL.getMiniHBaseCluster() 302 .getLiveRegionServerThreads()) { 303 snRSMap.put(rst.getRegionServer().getServerName(), rst.getRegionServer()); 304 } 305 // Also include master, since it can also host user regions. 306 for (JVMClusterUtil.MasterThread rst : TEST_UTIL.getMiniHBaseCluster().getLiveMasterThreads()) { 307 snRSMap.put(rst.getMaster().getServerName(), rst.getMaster()); 308 } 309 310 int dnPort = fnm.getDataNodePort(); 311 RegionLocator regionLocator = admin.getConnection().getRegionLocator(tableName); 312 for (HRegionLocation regionLocation : regionLocator.getAllRegionLocations()) { 313 314 HRegionInfo regionInfo = regionLocation.getRegionInfo(); 315 List<ServerName> fnList = fnm.getFavoredNodes(regionInfo); 316 317 // 1. Does each region have favored node? 318 assertNotNull("Favored nodes should not be null for region:" + regionInfo, fnList); 319 320 // 2. Do we have the right number of favored nodes? Is start code -1? 321 assertEquals("Incorrect favored nodes for region:" + regionInfo + " fnlist: " + fnList, 322 FavoredNodeAssignmentHelper.FAVORED_NODES_NUM, fnList.size()); 323 for (ServerName sn : fnList) { 324 assertEquals("FN should not have startCode, fnlist:" + fnList, -1, sn.getStartcode()); 325 } 326 327 // 3. Check if the regionServers have all the FN updated and in sync with Master 328 HRegionServer regionServer = snRSMap.get(regionLocation.getServerName()); 329 assertNotNull("RS should not be null for regionLocation: " + regionLocation, regionServer); 330 331 InetSocketAddress[] rsFavNodes = 332 regionServer.getFavoredNodesForRegion(regionInfo.getEncodedName()); 333 assertNotNull( 334 "RS " + regionLocation.getServerName() + " does not have FN for region: " + regionInfo, 335 rsFavNodes); 336 assertEquals( 337 "Incorrect FN for region:" + regionInfo.getEncodedName() + " on server:" 338 + regionLocation.getServerName(), 339 FavoredNodeAssignmentHelper.FAVORED_NODES_NUM, rsFavNodes.length); 340 341 // 4. Does DN port match all FN node list? 342 for (ServerName sn : fnm.getFavoredNodesWithDNPort(regionInfo)) { 343 assertEquals("FN should not have startCode, fnlist:" + fnList, -1, sn.getStartcode()); 344 assertEquals("FN port should belong to DN port, fnlist:" + fnList, dnPort, sn.getPort()); 345 } 346 } 347 } 348 349 /* 350 * Check favored nodes for system tables 351 */ 352 @Test 353 public void testSystemTables() throws Exception { 354 final TableName tableName = TableName.valueOf(name.getMethodName()); 355 TEST_UTIL.createTable(tableName, Bytes.toBytes("f"), splitKeys); 356 TEST_UTIL.waitUntilAllRegionsAssigned(tableName); 357 358 // All regions should have favored nodes 359 checkIfFavoredNodeInformationIsCorrect(tableName); 360 361 for (TableName sysTable : admin 362 .listTableNamesByNamespace(NamespaceDescriptor.SYSTEM_NAMESPACE_NAME_STR)) { 363 List<HRegionInfo> regions = admin.getTableRegions(sysTable); 364 for (HRegionInfo region : regions) { 365 assertNull("FN should be null for sys region", fnm.getFavoredNodes(region)); 366 } 367 } 368 369 TEST_UTIL.deleteTable(tableName); 370 } 371 372 private void checkIfDaughterInherits2FN(List<ServerName> parentFN, List<ServerName> daughterFN) { 373 374 assertNotNull(parentFN); 375 assertNotNull(daughterFN); 376 377 List<ServerName> favoredNodes = Lists.newArrayList(daughterFN); 378 favoredNodes.removeAll(parentFN); 379 380 /* 381 * With a small cluster its likely some FN might accidentally get shared. Its likely the 3rd FN 382 * the balancer chooses might still belong to the parent in which case favoredNodes size would 383 * be 0. 384 */ 385 assertTrue( 386 "Daughter FN:" + daughterFN + " should have inherited 2 FN from parent FN:" + parentFN, 387 favoredNodes.size() <= 1); 388 } 389 390 private void waitUntilTableRegionCountReached(final TableName tableName, final int numRegions) 391 throws Exception { 392 TEST_UTIL.waitFor(WAIT_TIMEOUT, new Waiter.Predicate<Exception>() { 393 @Override 394 public boolean evaluate() throws Exception { 395 try (RegionLocator locator = TEST_UTIL.getConnection().getRegionLocator(tableName)) { 396 return locator.getAllRegionLocations().size() == numRegions; 397 } 398 } 399 }); 400 } 401}