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.hamcrest.CoreMatchers.instanceOf; 024import static org.junit.Assert.assertArrayEquals; 025import static org.junit.Assert.assertEquals; 026import static org.junit.Assert.assertSame; 027import static org.junit.Assert.assertThat; 028 029import java.io.IOException; 030import java.util.Arrays; 031import java.util.List; 032import java.util.concurrent.CompletableFuture; 033import java.util.concurrent.ExecutionException; 034import java.util.concurrent.ThreadLocalRandom; 035import java.util.stream.IntStream; 036import org.apache.commons.io.IOUtils; 037import org.apache.hadoop.hbase.HBaseClassTestRule; 038import org.apache.hadoop.hbase.HBaseTestingUtility; 039import org.apache.hadoop.hbase.HRegionLocation; 040import org.apache.hadoop.hbase.NotServingRegionException; 041import org.apache.hadoop.hbase.ServerName; 042import org.apache.hadoop.hbase.TableName; 043import org.apache.hadoop.hbase.TableNotFoundException; 044import org.apache.hadoop.hbase.Waiter.ExplainingPredicate; 045import org.apache.hadoop.hbase.security.User; 046import org.apache.hadoop.hbase.testclassification.ClientTests; 047import org.apache.hadoop.hbase.testclassification.MediumTests; 048import org.apache.hadoop.hbase.util.Bytes; 049import org.junit.After; 050import org.junit.AfterClass; 051import org.junit.BeforeClass; 052import org.junit.ClassRule; 053import org.junit.Test; 054import org.junit.experimental.categories.Category; 055 056@Category({ MediumTests.class, ClientTests.class }) 057public class TestAsyncNonMetaRegionLocator { 058 059 @ClassRule 060 public static final HBaseClassTestRule CLASS_RULE = 061 HBaseClassTestRule.forClass(TestAsyncNonMetaRegionLocator.class); 062 063 private static final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility(); 064 065 private static TableName TABLE_NAME = TableName.valueOf("async"); 066 067 private static byte[] FAMILY = Bytes.toBytes("cf"); 068 069 private static AsyncConnectionImpl CONN; 070 071 private static AsyncNonMetaRegionLocator LOCATOR; 072 073 private static byte[][] SPLIT_KEYS; 074 075 @BeforeClass 076 public static void setUp() throws Exception { 077 TEST_UTIL.startMiniCluster(3); 078 TEST_UTIL.getAdmin().balancerSwitch(false, true); 079 AsyncRegistry registry = AsyncRegistryFactory.getRegistry(TEST_UTIL.getConfiguration()); 080 CONN = new AsyncConnectionImpl(TEST_UTIL.getConfiguration(), registry, 081 registry.getClusterId().get(), User.getCurrent()); 082 LOCATOR = new AsyncNonMetaRegionLocator(CONN); 083 SPLIT_KEYS = new byte[8][]; 084 for (int i = 111; i < 999; i += 111) { 085 SPLIT_KEYS[i / 111 - 1] = Bytes.toBytes(String.format("%03d", i)); 086 } 087 } 088 089 @AfterClass 090 public static void tearDown() throws Exception { 091 IOUtils.closeQuietly(CONN); 092 TEST_UTIL.shutdownMiniCluster(); 093 } 094 095 @After 096 public void tearDownAfterTest() throws IOException { 097 Admin admin = TEST_UTIL.getAdmin(); 098 if (admin.tableExists(TABLE_NAME)) { 099 if (admin.isTableEnabled(TABLE_NAME)) { 100 TEST_UTIL.getAdmin().disableTable(TABLE_NAME); 101 } 102 TEST_UTIL.getAdmin().deleteTable(TABLE_NAME); 103 } 104 LOCATOR.clearCache(TABLE_NAME); 105 } 106 107 private void createSingleRegionTable() throws IOException, InterruptedException { 108 TEST_UTIL.createTable(TABLE_NAME, FAMILY); 109 TEST_UTIL.waitTableAvailable(TABLE_NAME); 110 } 111 112 @Test 113 public void testNoTable() throws InterruptedException { 114 for (RegionLocateType locateType : RegionLocateType.values()) { 115 try { 116 LOCATOR.getRegionLocation(TABLE_NAME, EMPTY_START_ROW, locateType, false).get(); 117 } catch (ExecutionException e) { 118 assertThat(e.getCause(), instanceOf(TableNotFoundException.class)); 119 } 120 } 121 } 122 123 @Test 124 public void testDisableTable() throws IOException, InterruptedException { 125 createSingleRegionTable(); 126 TEST_UTIL.getAdmin().disableTable(TABLE_NAME); 127 for (RegionLocateType locateType : RegionLocateType.values()) { 128 try { 129 LOCATOR.getRegionLocation(TABLE_NAME, EMPTY_START_ROW, locateType, false).get(); 130 } catch (ExecutionException e) { 131 assertThat(e.getCause(), instanceOf(TableNotFoundException.class)); 132 } 133 } 134 } 135 136 private void assertLocEquals(byte[] startKey, byte[] endKey, ServerName serverName, 137 HRegionLocation loc) { 138 RegionInfo info = loc.getRegion(); 139 assertEquals(TABLE_NAME, info.getTable()); 140 assertArrayEquals(startKey, info.getStartKey()); 141 assertArrayEquals(endKey, info.getEndKey()); 142 assertEquals(serverName, loc.getServerName()); 143 } 144 145 @Test 146 public void testSingleRegionTable() throws IOException, InterruptedException, ExecutionException { 147 createSingleRegionTable(); 148 ServerName serverName = TEST_UTIL.getRSForFirstRegionInTable(TABLE_NAME).getServerName(); 149 for (RegionLocateType locateType : RegionLocateType.values()) { 150 assertLocEquals(EMPTY_START_ROW, EMPTY_END_ROW, serverName, 151 LOCATOR.getRegionLocation(TABLE_NAME, EMPTY_START_ROW, locateType, false).get()); 152 } 153 byte[] randKey = new byte[ThreadLocalRandom.current().nextInt(128)]; 154 ThreadLocalRandom.current().nextBytes(randKey); 155 for (RegionLocateType locateType : RegionLocateType.values()) { 156 assertLocEquals(EMPTY_START_ROW, EMPTY_END_ROW, serverName, 157 LOCATOR.getRegionLocation(TABLE_NAME, randKey, locateType, false).get()); 158 } 159 } 160 161 private void createMultiRegionTable() throws IOException, InterruptedException { 162 TEST_UTIL.createTable(TABLE_NAME, FAMILY, SPLIT_KEYS); 163 TEST_UTIL.waitTableAvailable(TABLE_NAME); 164 } 165 166 private static byte[][] getStartKeys() { 167 byte[][] startKeys = new byte[SPLIT_KEYS.length + 1][]; 168 startKeys[0] = EMPTY_START_ROW; 169 System.arraycopy(SPLIT_KEYS, 0, startKeys, 1, SPLIT_KEYS.length); 170 return startKeys; 171 } 172 173 private static byte[][] getEndKeys() { 174 byte[][] endKeys = Arrays.copyOf(SPLIT_KEYS, SPLIT_KEYS.length + 1); 175 endKeys[endKeys.length - 1] = EMPTY_START_ROW; 176 return endKeys; 177 } 178 179 private ServerName[] getLocations(byte[][] startKeys) { 180 ServerName[] serverNames = new ServerName[startKeys.length]; 181 TEST_UTIL.getHBaseCluster().getRegionServerThreads().stream().map(t -> t.getRegionServer()) 182 .forEach(rs -> { 183 rs.getRegions(TABLE_NAME).forEach(r -> { 184 serverNames[Arrays.binarySearch(startKeys, r.getRegionInfo().getStartKey(), 185 Bytes::compareTo)] = rs.getServerName(); 186 }); 187 }); 188 return serverNames; 189 } 190 191 @Test 192 public void testMultiRegionTable() throws IOException, InterruptedException { 193 createMultiRegionTable(); 194 byte[][] startKeys = getStartKeys(); 195 ServerName[] serverNames = getLocations(startKeys); 196 IntStream.range(0, 2).forEach(n -> IntStream.range(0, startKeys.length).forEach(i -> { 197 try { 198 assertLocEquals(startKeys[i], i == startKeys.length - 1 ? EMPTY_END_ROW : startKeys[i + 1], 199 serverNames[i], LOCATOR 200 .getRegionLocation(TABLE_NAME, startKeys[i], RegionLocateType.CURRENT, false).get()); 201 } catch (InterruptedException | ExecutionException e) { 202 throw new RuntimeException(e); 203 } 204 })); 205 206 LOCATOR.clearCache(TABLE_NAME); 207 IntStream.range(0, 2).forEach(n -> IntStream.range(0, startKeys.length).forEach(i -> { 208 try { 209 assertLocEquals(startKeys[i], i == startKeys.length - 1 ? EMPTY_END_ROW : startKeys[i + 1], 210 serverNames[i], 211 LOCATOR.getRegionLocation(TABLE_NAME, startKeys[i], RegionLocateType.AFTER, false).get()); 212 } catch (InterruptedException | ExecutionException e) { 213 throw new RuntimeException(e); 214 } 215 })); 216 217 LOCATOR.clearCache(TABLE_NAME); 218 byte[][] endKeys = getEndKeys(); 219 IntStream.range(0, 2).forEach( 220 n -> IntStream.range(0, endKeys.length).map(i -> endKeys.length - 1 - i).forEach(i -> { 221 try { 222 assertLocEquals(i == 0 ? EMPTY_START_ROW : endKeys[i - 1], endKeys[i], serverNames[i], 223 LOCATOR.getRegionLocation(TABLE_NAME, endKeys[i], RegionLocateType.BEFORE, false) 224 .get()); 225 } catch (InterruptedException | ExecutionException e) { 226 throw new RuntimeException(e); 227 } 228 })); 229 } 230 231 @Test 232 public void testRegionMove() throws IOException, InterruptedException, ExecutionException { 233 createSingleRegionTable(); 234 ServerName serverName = TEST_UTIL.getRSForFirstRegionInTable(TABLE_NAME).getServerName(); 235 HRegionLocation loc = LOCATOR 236 .getRegionLocation(TABLE_NAME, EMPTY_START_ROW, RegionLocateType.CURRENT, false).get(); 237 assertLocEquals(EMPTY_START_ROW, EMPTY_END_ROW, serverName, loc); 238 ServerName newServerName = TEST_UTIL.getHBaseCluster().getRegionServerThreads().stream() 239 .map(t -> t.getRegionServer().getServerName()).filter(sn -> !sn.equals(serverName)) 240 .findAny().get(); 241 242 TEST_UTIL.getAdmin().move(Bytes.toBytes(loc.getRegion().getEncodedName()), 243 Bytes.toBytes(newServerName.getServerName())); 244 while (!TEST_UTIL.getRSForFirstRegionInTable(TABLE_NAME).getServerName() 245 .equals(newServerName)) { 246 Thread.sleep(100); 247 } 248 // Should be same as it is in cache 249 assertSame(loc, LOCATOR 250 .getRegionLocation(TABLE_NAME, EMPTY_START_ROW, RegionLocateType.CURRENT, false).get()); 251 LOCATOR.updateCachedLocation(loc, null); 252 // null error will not trigger a cache cleanup 253 assertSame(loc, LOCATOR 254 .getRegionLocation(TABLE_NAME, EMPTY_START_ROW, RegionLocateType.CURRENT, false).get()); 255 LOCATOR.updateCachedLocation(loc, new NotServingRegionException()); 256 assertLocEquals(EMPTY_START_ROW, EMPTY_END_ROW, newServerName, LOCATOR 257 .getRegionLocation(TABLE_NAME, EMPTY_START_ROW, RegionLocateType.CURRENT, false).get()); 258 } 259 260 // usually locate after will return the same result, so we add a test to make it return different 261 // result. 262 @Test 263 public void testLocateAfter() throws IOException, InterruptedException, ExecutionException { 264 byte[] row = Bytes.toBytes("1"); 265 byte[] splitKey = Arrays.copyOf(row, 2); 266 TEST_UTIL.createTable(TABLE_NAME, FAMILY, new byte[][] { splitKey }); 267 TEST_UTIL.waitTableAvailable(TABLE_NAME); 268 HRegionLocation currentLoc = 269 LOCATOR.getRegionLocation(TABLE_NAME, row, RegionLocateType.CURRENT, false).get(); 270 ServerName currentServerName = TEST_UTIL.getRSForFirstRegionInTable(TABLE_NAME).getServerName(); 271 assertLocEquals(EMPTY_START_ROW, splitKey, currentServerName, currentLoc); 272 273 HRegionLocation afterLoc = 274 LOCATOR.getRegionLocation(TABLE_NAME, row, RegionLocateType.AFTER, false).get(); 275 ServerName afterServerName = 276 TEST_UTIL.getHBaseCluster().getRegionServerThreads().stream().map(t -> t.getRegionServer()) 277 .filter(rs -> rs.getRegions(TABLE_NAME).stream() 278 .anyMatch(r -> Bytes.equals(splitKey, r.getRegionInfo().getStartKey()))) 279 .findAny().get().getServerName(); 280 assertLocEquals(splitKey, EMPTY_END_ROW, afterServerName, afterLoc); 281 282 assertSame(afterLoc, 283 LOCATOR.getRegionLocation(TABLE_NAME, row, RegionLocateType.AFTER, false).get()); 284 } 285 286 // For HBASE-17402 287 @Test 288 public void testConcurrentLocate() throws IOException, InterruptedException, ExecutionException { 289 createMultiRegionTable(); 290 byte[][] startKeys = getStartKeys(); 291 byte[][] endKeys = getEndKeys(); 292 ServerName[] serverNames = getLocations(startKeys); 293 for (int i = 0; i < 100; i++) { 294 LOCATOR.clearCache(TABLE_NAME); 295 List<CompletableFuture<HRegionLocation>> futures = IntStream.range(0, 1000) 296 .mapToObj(n -> String.format("%03d", n)).map(s -> Bytes.toBytes(s)) 297 .map(r -> LOCATOR.getRegionLocation(TABLE_NAME, r, RegionLocateType.CURRENT, false)) 298 .collect(toList()); 299 for (int j = 0; j < 1000; j++) { 300 int index = Math.min(8, j / 111); 301 assertLocEquals(startKeys[index], endKeys[index], serverNames[index], futures.get(j).get()); 302 } 303 } 304 } 305 306 @Test 307 public void testReload() throws Exception { 308 createSingleRegionTable(); 309 ServerName serverName = TEST_UTIL.getRSForFirstRegionInTable(TABLE_NAME).getServerName(); 310 for (RegionLocateType locateType : RegionLocateType.values()) { 311 assertLocEquals(EMPTY_START_ROW, EMPTY_END_ROW, serverName, 312 LOCATOR.getRegionLocation(TABLE_NAME, EMPTY_START_ROW, locateType, false).get()); 313 } 314 ServerName newServerName = TEST_UTIL.getHBaseCluster().getRegionServerThreads().stream() 315 .map(t -> t.getRegionServer().getServerName()).filter(sn -> !sn.equals(serverName)) 316 .findAny().get(); 317 Admin admin = TEST_UTIL.getAdmin(); 318 RegionInfo region = admin.getRegions(TABLE_NAME).stream().findAny().get(); 319 admin.move(region.getEncodedNameAsBytes(), Bytes.toBytes(newServerName.getServerName())); 320 TEST_UTIL.waitFor(30000, new ExplainingPredicate<Exception>() { 321 322 @Override 323 public boolean evaluate() throws Exception { 324 ServerName newServerName = TEST_UTIL.getRSForFirstRegionInTable(TABLE_NAME).getServerName(); 325 return newServerName != null && !newServerName.equals(serverName); 326 } 327 328 @Override 329 public String explainFailure() throws Exception { 330 return region.getRegionNameAsString() + " is still on " + serverName; 331 } 332 333 }); 334 // The cached location will not change 335 for (RegionLocateType locateType : RegionLocateType.values()) { 336 assertLocEquals(EMPTY_START_ROW, EMPTY_END_ROW, serverName, 337 LOCATOR.getRegionLocation(TABLE_NAME, EMPTY_START_ROW, locateType, false).get()); 338 } 339 // should get the new location when reload = true 340 assertLocEquals(EMPTY_START_ROW, EMPTY_END_ROW, newServerName, 341 LOCATOR.getRegionLocation(TABLE_NAME, EMPTY_START_ROW, RegionLocateType.CURRENT, true).get()); 342 // the cached location should be replaced 343 for (RegionLocateType locateType : RegionLocateType.values()) { 344 assertLocEquals(EMPTY_START_ROW, EMPTY_END_ROW, newServerName, 345 LOCATOR.getRegionLocation(TABLE_NAME, EMPTY_START_ROW, locateType, false).get()); 346 } 347 } 348 349 // Testcase for HBASE-20822 350 @Test 351 public void testLocateBeforeLastRegion() 352 throws IOException, InterruptedException, ExecutionException { 353 createMultiRegionTable(); 354 LOCATOR.getRegionLocation(TABLE_NAME, SPLIT_KEYS[0], RegionLocateType.CURRENT, false).join(); 355 HRegionLocation loc = 356 LOCATOR.getRegionLocation(TABLE_NAME, EMPTY_END_ROW, RegionLocateType.BEFORE, false).get(); 357 // should locate to the last region 358 assertArrayEquals(loc.getRegion().getEndKey(), EMPTY_END_ROW); 359 } 360}