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