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 java.util.stream.Collectors.toList; 021import static org.apache.hadoop.hbase.HConstants.EMPTY_END_ROW; 022import static org.apache.hadoop.hbase.HConstants.EMPTY_START_ROW; 023import static org.apache.hadoop.hbase.client.RegionReplicaTestHelper.testLocator; 024import static org.hamcrest.CoreMatchers.instanceOf; 025import static org.junit.Assert.assertArrayEquals; 026import static org.junit.Assert.assertEquals; 027import static org.junit.Assert.assertSame; 028import static org.junit.Assert.assertThat; 029 030import java.io.IOException; 031import java.util.Arrays; 032import java.util.Collection; 033import java.util.List; 034import java.util.Optional; 035import java.util.concurrent.CompletableFuture; 036import java.util.concurrent.ExecutionException; 037import java.util.concurrent.ThreadLocalRandom; 038import java.util.stream.IntStream; 039import org.apache.commons.io.IOUtils; 040import org.apache.hadoop.conf.Configuration; 041import org.apache.hadoop.hbase.HBaseClassTestRule; 042import org.apache.hadoop.hbase.HBaseTestingUtility; 043import org.apache.hadoop.hbase.HRegionLocation; 044import org.apache.hadoop.hbase.NotServingRegionException; 045import org.apache.hadoop.hbase.RegionLocations; 046import org.apache.hadoop.hbase.ServerName; 047import org.apache.hadoop.hbase.TableName; 048import org.apache.hadoop.hbase.TableNotFoundException; 049import org.apache.hadoop.hbase.Waiter.ExplainingPredicate; 050import org.apache.hadoop.hbase.client.RegionReplicaTestHelper.Locator; 051import org.apache.hadoop.hbase.security.User; 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.ServerRegionReplicaUtil; 056import org.junit.After; 057import org.junit.AfterClass; 058import org.junit.BeforeClass; 059import org.junit.ClassRule; 060import org.junit.Test; 061import org.junit.experimental.categories.Category; 062import org.junit.runner.RunWith; 063import org.junit.runners.Parameterized; 064import org.slf4j.Logger; 065import org.slf4j.LoggerFactory; 066 067@Category({ MediumTests.class, ClientTests.class }) 068@RunWith(Parameterized.class) 069public class TestAsyncNonMetaRegionLocator { 070 071 @ClassRule 072 public static final HBaseClassTestRule CLASS_RULE = 073 HBaseClassTestRule.forClass(TestAsyncNonMetaRegionLocator.class); 074 075 private static final Logger LOG = LoggerFactory.getLogger(TestAsyncNonMetaRegionLocator.class); 076 077 private static final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility(); 078 079 private static TableName TABLE_NAME = TableName.valueOf("async"); 080 081 private static byte[] FAMILY = Bytes.toBytes("cf"); 082 private static final int META_STOREFILE_REFRESH_PERIOD = 100; 083 private static final int NB_SERVERS = 4; 084 private static int numOfMetaReplica = NB_SERVERS - 1; 085 086 private static AsyncConnectionImpl CONN; 087 088 private static AsyncNonMetaRegionLocator LOCATOR; 089 private static ConnectionRegistry registry; 090 091 private static byte[][] SPLIT_KEYS; 092 private CatalogReplicaMode metaReplicaMode; 093 094 @BeforeClass 095 public static void setUp() throws Exception { 096 Configuration conf = TEST_UTIL.getConfiguration(); 097 098 // Enable hbase:meta replication. 099 conf.setBoolean(ServerRegionReplicaUtil.REGION_REPLICA_REPLICATION_CATALOG_CONF_KEY, true); 100 conf.setLong("replication.source.sleepforretries", 10); // 10 ms 101 TEST_UTIL.startMiniCluster(NB_SERVERS); 102 Admin admin = TEST_UTIL.getAdmin(); 103 admin.balancerSwitch(false, true); 104 // Enable hbase:meta replication. 105 HBaseTestingUtility.setReplicas(admin, TableName.META_TABLE_NAME, numOfMetaReplica); 106 TEST_UTIL.waitFor(30000, () -> TEST_UTIL.getMiniHBaseCluster().getRegions( 107 TableName.META_TABLE_NAME).size() >= numOfMetaReplica); 108 109 registry = ConnectionRegistryFactory.getRegistry(TEST_UTIL.getConfiguration()); 110 SPLIT_KEYS = new byte[8][]; 111 for (int i = 111; i < 999; i += 111) { 112 SPLIT_KEYS[i / 111 - 1] = Bytes.toBytes(String.format("%03d", i)); 113 } 114 } 115 116 @AfterClass 117 public static void tearDown() throws Exception { 118 IOUtils.closeQuietly(CONN); 119 TEST_UTIL.shutdownMiniCluster(); 120 } 121 122 @After 123 public void tearDownAfterTest() throws IOException { 124 Admin admin = TEST_UTIL.getAdmin(); 125 if (admin.tableExists(TABLE_NAME)) { 126 if (admin.isTableEnabled(TABLE_NAME)) { 127 TEST_UTIL.getAdmin().disableTable(TABLE_NAME); 128 } 129 TEST_UTIL.getAdmin().deleteTable(TABLE_NAME); 130 } 131 LOCATOR.clearCache(TABLE_NAME); 132 } 133 134 @Parameterized.Parameters 135 public static Collection<Object[]> parameters() { 136 return Arrays.asList(new Object[][] { 137 { null }, 138 { CatalogReplicaMode.LOAD_BALANCE.toString() } 139 }); 140 } 141 142 public TestAsyncNonMetaRegionLocator(String clientMetaReplicaMode) throws Exception { 143 Configuration c = new Configuration(TEST_UTIL.getConfiguration()); 144 // Enable meta replica LoadBalance mode for this connection. 145 if (clientMetaReplicaMode != null) { 146 c.set(RegionLocator.LOCATOR_META_REPLICAS_MODE, clientMetaReplicaMode); 147 metaReplicaMode = CatalogReplicaMode.fromString(clientMetaReplicaMode); 148 } 149 150 CONN = new AsyncConnectionImpl(c, registry, registry.getClusterId().get(), User.getCurrent()); 151 LOCATOR = new AsyncNonMetaRegionLocator(CONN); 152 } 153 154 private void createSingleRegionTable() throws IOException, InterruptedException { 155 TEST_UTIL.createTable(TABLE_NAME, FAMILY); 156 TEST_UTIL.waitTableAvailable(TABLE_NAME); 157 } 158 159 private CompletableFuture<HRegionLocation> getDefaultRegionLocation(TableName tableName, 160 byte[] row, RegionLocateType locateType, boolean reload) { 161 return LOCATOR 162 .getRegionLocations(tableName, row, RegionReplicaUtil.DEFAULT_REPLICA_ID, locateType, reload) 163 .thenApply(RegionLocations::getDefaultRegionLocation); 164 } 165 166 @Test 167 public void testNoTable() throws InterruptedException { 168 for (RegionLocateType locateType : RegionLocateType.values()) { 169 try { 170 getDefaultRegionLocation(TABLE_NAME, EMPTY_START_ROW, locateType, false).get(); 171 } catch (ExecutionException e) { 172 assertThat(e.getCause(), instanceOf(TableNotFoundException.class)); 173 } 174 } 175 } 176 177 @Test 178 public void testDisableTable() throws IOException, InterruptedException { 179 createSingleRegionTable(); 180 TEST_UTIL.getAdmin().disableTable(TABLE_NAME); 181 for (RegionLocateType locateType : RegionLocateType.values()) { 182 try { 183 getDefaultRegionLocation(TABLE_NAME, EMPTY_START_ROW, locateType, false).get(); 184 } catch (ExecutionException e) { 185 assertThat(e.getCause(), instanceOf(TableNotFoundException.class)); 186 } 187 } 188 } 189 190 private void assertLocEquals(byte[] startKey, byte[] endKey, ServerName serverName, 191 HRegionLocation loc) { 192 RegionInfo info = loc.getRegion(); 193 assertEquals(TABLE_NAME, info.getTable()); 194 assertArrayEquals(startKey, info.getStartKey()); 195 assertArrayEquals(endKey, info.getEndKey()); 196 assertEquals(serverName, loc.getServerName()); 197 } 198 199 @Test 200 public void testSingleRegionTable() throws IOException, InterruptedException, ExecutionException { 201 createSingleRegionTable(); 202 ServerName serverName = TEST_UTIL.getRSForFirstRegionInTable(TABLE_NAME).getServerName(); 203 for (RegionLocateType locateType : RegionLocateType.values()) { 204 assertLocEquals(EMPTY_START_ROW, EMPTY_END_ROW, serverName, 205 getDefaultRegionLocation(TABLE_NAME, EMPTY_START_ROW, locateType, false).get()); 206 } 207 byte[] randKey = new byte[ThreadLocalRandom.current().nextInt(128)]; 208 ThreadLocalRandom.current().nextBytes(randKey); 209 for (RegionLocateType locateType : RegionLocateType.values()) { 210 assertLocEquals(EMPTY_START_ROW, EMPTY_END_ROW, serverName, 211 getDefaultRegionLocation(TABLE_NAME, randKey, locateType, false).get()); 212 } 213 } 214 215 private void createMultiRegionTable() throws IOException, InterruptedException { 216 TEST_UTIL.createTable(TABLE_NAME, FAMILY, SPLIT_KEYS); 217 TEST_UTIL.waitTableAvailable(TABLE_NAME); 218 } 219 220 private static byte[][] getStartKeys() { 221 byte[][] startKeys = new byte[SPLIT_KEYS.length + 1][]; 222 startKeys[0] = EMPTY_START_ROW; 223 System.arraycopy(SPLIT_KEYS, 0, startKeys, 1, SPLIT_KEYS.length); 224 return startKeys; 225 } 226 227 private static byte[][] getEndKeys() { 228 byte[][] endKeys = Arrays.copyOf(SPLIT_KEYS, SPLIT_KEYS.length + 1); 229 endKeys[endKeys.length - 1] = EMPTY_START_ROW; 230 return endKeys; 231 } 232 233 private ServerName[] getLocations(byte[][] startKeys) { 234 ServerName[] serverNames = new ServerName[startKeys.length]; 235 TEST_UTIL.getHBaseCluster().getRegionServerThreads().stream().map(t -> t.getRegionServer()) 236 .forEach(rs -> { 237 rs.getRegions(TABLE_NAME).forEach(r -> { 238 serverNames[Arrays.binarySearch(startKeys, r.getRegionInfo().getStartKey(), 239 Bytes::compareTo)] = rs.getServerName(); 240 }); 241 }); 242 return serverNames; 243 } 244 245 @Test 246 public void testMultiRegionTable() throws IOException, InterruptedException { 247 createMultiRegionTable(); 248 byte[][] startKeys = getStartKeys(); 249 ServerName[] serverNames = getLocations(startKeys); 250 IntStream.range(0, 2).forEach(n -> IntStream.range(0, startKeys.length).forEach(i -> { 251 try { 252 assertLocEquals(startKeys[i], i == startKeys.length - 1 ? EMPTY_END_ROW : startKeys[i + 1], 253 serverNames[i], 254 getDefaultRegionLocation(TABLE_NAME, startKeys[i], RegionLocateType.CURRENT, false) 255 .get()); 256 } catch (InterruptedException | ExecutionException e) { 257 throw new RuntimeException(e); 258 } 259 })); 260 261 LOCATOR.clearCache(TABLE_NAME); 262 IntStream.range(0, 2).forEach(n -> IntStream.range(0, startKeys.length).forEach(i -> { 263 try { 264 assertLocEquals(startKeys[i], i == startKeys.length - 1 ? EMPTY_END_ROW : startKeys[i + 1], 265 serverNames[i], 266 getDefaultRegionLocation(TABLE_NAME, startKeys[i], RegionLocateType.AFTER, false).get()); 267 } catch (InterruptedException | ExecutionException e) { 268 throw new RuntimeException(e); 269 } 270 })); 271 272 LOCATOR.clearCache(TABLE_NAME); 273 byte[][] endKeys = getEndKeys(); 274 IntStream.range(0, 2).forEach( 275 n -> IntStream.range(0, endKeys.length).map(i -> endKeys.length - 1 - i).forEach(i -> { 276 try { 277 assertLocEquals(i == 0 ? EMPTY_START_ROW : endKeys[i - 1], endKeys[i], serverNames[i], 278 getDefaultRegionLocation(TABLE_NAME, endKeys[i], RegionLocateType.BEFORE, false).get()); 279 } catch (InterruptedException | ExecutionException e) { 280 throw new RuntimeException(e); 281 } 282 })); 283 } 284 285 @Test 286 public void testRegionMove() throws IOException, InterruptedException, ExecutionException { 287 createSingleRegionTable(); 288 ServerName serverName = TEST_UTIL.getRSForFirstRegionInTable(TABLE_NAME).getServerName(); 289 HRegionLocation loc = 290 getDefaultRegionLocation(TABLE_NAME, EMPTY_START_ROW, RegionLocateType.CURRENT, false).get(); 291 assertLocEquals(EMPTY_START_ROW, EMPTY_END_ROW, serverName, loc); 292 ServerName newServerName = TEST_UTIL.getHBaseCluster().getRegionServerThreads().stream() 293 .map(t -> t.getRegionServer().getServerName()).filter(sn -> !sn.equals(serverName)).findAny() 294 .get(); 295 296 TEST_UTIL.getAdmin().move(Bytes.toBytes(loc.getRegion().getEncodedName()), newServerName); 297 while (!TEST_UTIL.getRSForFirstRegionInTable(TABLE_NAME).getServerName() 298 .equals(newServerName)) { 299 Thread.sleep(100); 300 } 301 // Should be same as it is in cache 302 assertSame(loc, 303 getDefaultRegionLocation(TABLE_NAME, EMPTY_START_ROW, RegionLocateType.CURRENT, false).get()); 304 LOCATOR.updateCachedLocationOnError(loc, null); 305 // null error will not trigger a cache cleanup 306 assertSame(loc, 307 getDefaultRegionLocation(TABLE_NAME, EMPTY_START_ROW, RegionLocateType.CURRENT, false).get()); 308 LOCATOR.updateCachedLocationOnError(loc, new NotServingRegionException()); 309 assertLocEquals(EMPTY_START_ROW, EMPTY_END_ROW, newServerName, 310 getDefaultRegionLocation(TABLE_NAME, EMPTY_START_ROW, RegionLocateType.CURRENT, false).get()); 311 } 312 313 // usually locate after will return the same result, so we add a test to make it return different 314 // result. 315 @Test 316 public void testLocateAfter() throws IOException, InterruptedException, ExecutionException { 317 byte[] row = Bytes.toBytes("1"); 318 byte[] splitKey = Arrays.copyOf(row, 2); 319 TEST_UTIL.createTable(TABLE_NAME, FAMILY, new byte[][] { splitKey }); 320 TEST_UTIL.waitTableAvailable(TABLE_NAME); 321 HRegionLocation currentLoc = 322 getDefaultRegionLocation(TABLE_NAME, row, RegionLocateType.CURRENT, false).get(); 323 ServerName currentServerName = TEST_UTIL.getRSForFirstRegionInTable(TABLE_NAME).getServerName(); 324 assertLocEquals(EMPTY_START_ROW, splitKey, currentServerName, currentLoc); 325 326 HRegionLocation afterLoc = 327 getDefaultRegionLocation(TABLE_NAME, row, RegionLocateType.AFTER, false).get(); 328 ServerName afterServerName = 329 TEST_UTIL.getHBaseCluster().getRegionServerThreads().stream().map(t -> t.getRegionServer()) 330 .filter(rs -> rs.getRegions(TABLE_NAME).stream() 331 .anyMatch(r -> Bytes.equals(splitKey, r.getRegionInfo().getStartKey()))) 332 .findAny().get().getServerName(); 333 assertLocEquals(splitKey, EMPTY_END_ROW, afterServerName, afterLoc); 334 335 assertSame(afterLoc, 336 getDefaultRegionLocation(TABLE_NAME, row, RegionLocateType.AFTER, false).get()); 337 } 338 339 // For HBASE-17402 340 @Test 341 public void testConcurrentLocate() throws IOException, InterruptedException, ExecutionException { 342 createMultiRegionTable(); 343 byte[][] startKeys = getStartKeys(); 344 byte[][] endKeys = getEndKeys(); 345 ServerName[] serverNames = getLocations(startKeys); 346 for (int i = 0; i < 100; i++) { 347 LOCATOR.clearCache(TABLE_NAME); 348 List<CompletableFuture<HRegionLocation>> futures = 349 IntStream.range(0, 1000).mapToObj(n -> String.format("%03d", n)).map(s -> Bytes.toBytes(s)) 350 .map(r -> getDefaultRegionLocation(TABLE_NAME, r, RegionLocateType.CURRENT, false)) 351 .collect(toList()); 352 for (int j = 0; j < 1000; j++) { 353 int index = Math.min(8, j / 111); 354 assertLocEquals(startKeys[index], endKeys[index], serverNames[index], futures.get(j).get()); 355 } 356 } 357 } 358 359 @Test 360 public void testReload() throws Exception { 361 createSingleRegionTable(); 362 ServerName serverName = TEST_UTIL.getRSForFirstRegionInTable(TABLE_NAME).getServerName(); 363 for (RegionLocateType locateType : RegionLocateType.values()) { 364 assertLocEquals(EMPTY_START_ROW, EMPTY_END_ROW, serverName, 365 getDefaultRegionLocation(TABLE_NAME, EMPTY_START_ROW, locateType, false).get()); 366 } 367 ServerName newServerName = TEST_UTIL.getHBaseCluster().getRegionServerThreads().stream() 368 .map(t -> t.getRegionServer().getServerName()).filter(sn -> !sn.equals(serverName)).findAny() 369 .get(); 370 Admin admin = TEST_UTIL.getAdmin(); 371 RegionInfo region = admin.getRegions(TABLE_NAME).stream().findAny().get(); 372 admin.move(region.getEncodedNameAsBytes(), newServerName); 373 TEST_UTIL.waitFor(30000, new ExplainingPredicate<Exception>() { 374 375 @Override 376 public boolean evaluate() throws Exception { 377 ServerName newServerName = TEST_UTIL.getRSForFirstRegionInTable(TABLE_NAME).getServerName(); 378 return newServerName != null && !newServerName.equals(serverName); 379 } 380 381 @Override 382 public String explainFailure() throws Exception { 383 return region.getRegionNameAsString() + " is still on " + serverName; 384 } 385 386 }); 387 // The cached location will not change 388 for (RegionLocateType locateType : RegionLocateType.values()) { 389 assertLocEquals(EMPTY_START_ROW, EMPTY_END_ROW, serverName, 390 getDefaultRegionLocation(TABLE_NAME, EMPTY_START_ROW, locateType, false).get()); 391 } 392 // should get the new location when reload = true 393 // when meta replica LoadBalance mode is enabled, it may delay a bit. 394 TEST_UTIL.waitFor(3000, new ExplainingPredicate<Exception>() { 395 @Override 396 public boolean evaluate() throws Exception { 397 HRegionLocation loc = getDefaultRegionLocation(TABLE_NAME, EMPTY_START_ROW, 398 RegionLocateType.CURRENT, true).get(); 399 return newServerName.equals(loc.getServerName()); 400 } 401 402 @Override 403 public String explainFailure() throws Exception { 404 return "New location does not show up in meta (replica) region"; 405 } 406 }); 407 408 // the cached location should be replaced 409 for (RegionLocateType locateType : RegionLocateType.values()) { 410 assertLocEquals(EMPTY_START_ROW, EMPTY_END_ROW, newServerName, 411 getDefaultRegionLocation(TABLE_NAME, EMPTY_START_ROW, locateType, false).get()); 412 } 413 } 414 415 // Testcase for HBASE-20822 416 @Test 417 public void testLocateBeforeLastRegion() 418 throws IOException, InterruptedException, ExecutionException { 419 createMultiRegionTable(); 420 getDefaultRegionLocation(TABLE_NAME, SPLIT_KEYS[0], RegionLocateType.CURRENT, false).join(); 421 HRegionLocation loc = 422 getDefaultRegionLocation(TABLE_NAME, EMPTY_END_ROW, RegionLocateType.BEFORE, false).get(); 423 // should locate to the last region 424 assertArrayEquals(loc.getRegion().getEndKey(), EMPTY_END_ROW); 425 } 426 427 @Test 428 public void testRegionReplicas() throws Exception { 429 TEST_UTIL.getAdmin().createTable(TableDescriptorBuilder.newBuilder(TABLE_NAME) 430 .setColumnFamily(ColumnFamilyDescriptorBuilder.of(FAMILY)).setRegionReplication(3).build()); 431 TEST_UTIL.waitUntilAllRegionsAssigned(TABLE_NAME); 432 testLocator(TEST_UTIL, TABLE_NAME, new Locator() { 433 434 @Override 435 public void updateCachedLocationOnError(HRegionLocation loc, Throwable error) 436 throws Exception { 437 LOCATOR.updateCachedLocationOnError(loc, error); 438 } 439 440 @Override 441 public RegionLocations getRegionLocations(TableName tableName, int replicaId, boolean reload) 442 throws Exception { 443 return LOCATOR.getRegionLocations(tableName, EMPTY_START_ROW, replicaId, 444 RegionLocateType.CURRENT, reload).get(); 445 } 446 }); 447 } 448 449 // Testcase for HBASE-21961 450 @Test 451 public void testLocateBeforeInOnlyRegion() throws IOException, InterruptedException { 452 createSingleRegionTable(); 453 HRegionLocation loc = 454 getDefaultRegionLocation(TABLE_NAME, Bytes.toBytes(1), RegionLocateType.BEFORE, false).join(); 455 // should locate to the only region 456 assertArrayEquals(loc.getRegion().getStartKey(), EMPTY_START_ROW); 457 assertArrayEquals(loc.getRegion().getEndKey(), EMPTY_END_ROW); 458 } 459 460 @Test 461 public void testConcurrentUpdateCachedLocationOnError() throws Exception { 462 createSingleRegionTable(); 463 HRegionLocation loc = 464 getDefaultRegionLocation(TABLE_NAME, EMPTY_START_ROW, RegionLocateType.CURRENT, false) 465 .get(); 466 IntStream.range(0, 100).parallel() 467 .forEach(i -> LOCATOR.updateCachedLocationOnError(loc, new NotServingRegionException())); 468 } 469}