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