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}