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.hamcrest.MatcherAssert.assertThat;
026import static org.junit.jupiter.api.Assertions.assertArrayEquals;
027import static org.junit.jupiter.api.Assertions.assertEquals;
028import static org.junit.jupiter.api.Assertions.assertNotNull;
029import static org.junit.jupiter.api.Assertions.assertNull;
030import static org.junit.jupiter.api.Assertions.assertSame;
031
032import java.io.IOException;
033import java.util.Arrays;
034import java.util.List;
035import java.util.concurrent.CompletableFuture;
036import java.util.concurrent.ExecutionException;
037import java.util.concurrent.ThreadLocalRandom;
038import java.util.stream.IntStream;
039import java.util.stream.Stream;
040import org.apache.hadoop.conf.Configuration;
041import org.apache.hadoop.hbase.CatalogReplicaMode;
042import org.apache.hadoop.hbase.HBaseParameterizedTestTemplate;
043import org.apache.hadoop.hbase.HBaseTestingUtil;
044import org.apache.hadoop.hbase.HRegionLocation;
045import org.apache.hadoop.hbase.MetaTableAccessor;
046import org.apache.hadoop.hbase.NotServingRegionException;
047import org.apache.hadoop.hbase.RegionLocations;
048import org.apache.hadoop.hbase.ServerName;
049import org.apache.hadoop.hbase.TableName;
050import org.apache.hadoop.hbase.TableNotFoundException;
051import org.apache.hadoop.hbase.Waiter.ExplainingPredicate;
052import org.apache.hadoop.hbase.client.RegionReplicaTestHelper.Locator;
053import org.apache.hadoop.hbase.security.User;
054import org.apache.hadoop.hbase.testclassification.ClientTests;
055import org.apache.hadoop.hbase.testclassification.MediumTests;
056import org.apache.hadoop.hbase.util.Bytes;
057import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
058import org.apache.hadoop.hbase.util.ServerRegionReplicaUtil;
059import org.junit.jupiter.api.AfterAll;
060import org.junit.jupiter.api.AfterEach;
061import org.junit.jupiter.api.BeforeAll;
062import org.junit.jupiter.api.BeforeEach;
063import org.junit.jupiter.api.Tag;
064import org.junit.jupiter.api.TestTemplate;
065import org.junit.jupiter.params.provider.Arguments;
066
067import org.apache.hbase.thirdparty.com.google.common.collect.Lists;
068import org.apache.hbase.thirdparty.com.google.common.io.Closeables;
069
070@Tag(MediumTests.TAG)
071@Tag(ClientTests.TAG)
072@HBaseParameterizedTestTemplate(name = "[{index}]: metaReplicaMode = {0}")
073public class TestAsyncNonMetaRegionLocator {
074
075  private static final HBaseTestingUtil TEST_UTIL = new HBaseTestingUtil();
076
077  private static final TableName TABLE_NAME = TableName.valueOf("async");
078
079  private static byte[] FAMILY = Bytes.toBytes("cf");
080  private static final int NB_SERVERS = 4;
081  private static final int NUM_OF_META_REPLICA = NB_SERVERS - 1;
082
083  private static byte[][] SPLIT_KEYS;
084
085  private AsyncConnectionImpl conn;
086  private AsyncNonMetaRegionLocator locator;
087
088  private final CatalogReplicaMode metaReplicaMode;
089
090  public TestAsyncNonMetaRegionLocator(CatalogReplicaMode metaReplicaMode) {
091    this.metaReplicaMode = metaReplicaMode;
092  }
093
094  @BeforeAll
095  public static void setUp() throws Exception {
096    Configuration conf = TEST_UTIL.getConfiguration();
097    // Enable hbase:meta replication.
098    conf.setBoolean(ServerRegionReplicaUtil.REGION_REPLICA_REPLICATION_CATALOG_CONF_KEY, true);
099    conf.setLong("replication.source.sleepforretries", 10); // 10 ms
100
101    TEST_UTIL.startMiniCluster(NB_SERVERS);
102    Admin admin = TEST_UTIL.getAdmin();
103    admin.balancerSwitch(false, true);
104
105    // Enable hbase:meta replication.
106    HBaseTestingUtil.setReplicas(admin, TableName.META_TABLE_NAME, NUM_OF_META_REPLICA);
107    TEST_UTIL.waitFor(30000,
108      () -> TEST_UTIL.getMiniHBaseCluster().getRegions(TableName.META_TABLE_NAME).size()
109          >= NUM_OF_META_REPLICA);
110
111    SPLIT_KEYS = new byte[8][];
112    for (int i = 111; i < 999; i += 111) {
113      SPLIT_KEYS[i / 111 - 1] = Bytes.toBytes(String.format("%03d", i));
114    }
115  }
116
117  @AfterAll
118  public static void tearDown() throws Exception {
119    TEST_UTIL.shutdownMiniCluster();
120  }
121
122  @BeforeEach
123  public void setUpBeforeTest() throws InterruptedException, ExecutionException, IOException {
124    Configuration c = new Configuration(TEST_UTIL.getConfiguration());
125    // Enable meta replica LoadBalance mode for this connection.
126    c.set(RegionLocator.LOCATOR_META_REPLICAS_MODE, metaReplicaMode.toString());
127    ConnectionRegistry registry =
128      ConnectionRegistryFactory.create(TEST_UTIL.getConfiguration(), User.getCurrent());
129    conn =
130      new AsyncConnectionImpl(c, registry, registry.getClusterId().get(), null, User.getCurrent());
131    locator = new AsyncNonMetaRegionLocator(conn, AsyncConnectionImpl.RETRY_TIMER);
132  }
133
134  @AfterEach
135  public void tearDownAfterTest() throws IOException {
136    Admin admin = TEST_UTIL.getAdmin();
137    if (admin.tableExists(TABLE_NAME)) {
138      if (admin.isTableEnabled(TABLE_NAME)) {
139        admin.disableTable(TABLE_NAME);
140      }
141      admin.deleteTable(TABLE_NAME);
142    }
143    Closeables.close(conn, true);
144  }
145
146  public static Stream<Arguments> parameters() {
147    return Stream.of(Arguments.of(CatalogReplicaMode.NONE),
148      Arguments.of(CatalogReplicaMode.LOAD_BALANCE));
149  }
150
151  private void createSingleRegionTable() throws IOException, InterruptedException {
152    TEST_UTIL.createTable(TABLE_NAME, FAMILY);
153    TEST_UTIL.waitTableAvailable(TABLE_NAME);
154  }
155
156  private CompletableFuture<HRegionLocation> getDefaultRegionLocation(TableName tableName,
157    byte[] row, RegionLocateType locateType, boolean reload) {
158    return locator
159      .getRegionLocations(tableName, row, RegionReplicaUtil.DEFAULT_REPLICA_ID, locateType, reload)
160      .thenApply(RegionLocations::getDefaultRegionLocation);
161  }
162
163  @TestTemplate
164  public void testNoTable() throws InterruptedException {
165    for (RegionLocateType locateType : RegionLocateType.values()) {
166      try {
167        getDefaultRegionLocation(TABLE_NAME, EMPTY_START_ROW, locateType, false).get();
168      } catch (ExecutionException e) {
169        assertThat(e.getCause(), instanceOf(TableNotFoundException.class));
170      }
171    }
172  }
173
174  @TestTemplate
175  public void testDisableTable() throws IOException, InterruptedException {
176    createSingleRegionTable();
177    TEST_UTIL.getAdmin().disableTable(TABLE_NAME);
178    for (RegionLocateType locateType : RegionLocateType.values()) {
179      try {
180        getDefaultRegionLocation(TABLE_NAME, EMPTY_START_ROW, locateType, false).get();
181      } catch (ExecutionException e) {
182        assertThat(e.getCause(), instanceOf(TableNotFoundException.class));
183      }
184    }
185  }
186
187  private void assertLocEquals(byte[] startKey, byte[] endKey, ServerName serverName,
188    HRegionLocation loc) {
189    RegionInfo info = loc.getRegion();
190    assertEquals(TABLE_NAME, info.getTable());
191    assertArrayEquals(startKey, info.getStartKey());
192    assertArrayEquals(endKey, info.getEndKey());
193    assertEquals(serverName, loc.getServerName());
194  }
195
196  @TestTemplate
197  public void testSingleRegionTable() throws IOException, InterruptedException, ExecutionException {
198    createSingleRegionTable();
199    ServerName serverName = TEST_UTIL.getRSForFirstRegionInTable(TABLE_NAME).getServerName();
200    for (RegionLocateType locateType : RegionLocateType.values()) {
201      assertLocEquals(EMPTY_START_ROW, EMPTY_END_ROW, serverName,
202        getDefaultRegionLocation(TABLE_NAME, EMPTY_START_ROW, locateType, false).get());
203    }
204    byte[] key = new byte[ThreadLocalRandom.current().nextInt(128)];
205    Bytes.random(key);
206    for (RegionLocateType locateType : RegionLocateType.values()) {
207      assertLocEquals(EMPTY_START_ROW, EMPTY_END_ROW, serverName,
208        getDefaultRegionLocation(TABLE_NAME, key, locateType, false).get());
209    }
210  }
211
212  private void createMultiRegionTable() throws IOException, InterruptedException {
213    TEST_UTIL.createTable(TABLE_NAME, FAMILY, SPLIT_KEYS);
214    TEST_UTIL.waitTableAvailable(TABLE_NAME);
215  }
216
217  private static byte[][] getStartKeys() {
218    byte[][] startKeys = new byte[SPLIT_KEYS.length + 1][];
219    startKeys[0] = EMPTY_START_ROW;
220    System.arraycopy(SPLIT_KEYS, 0, startKeys, 1, SPLIT_KEYS.length);
221    return startKeys;
222  }
223
224  private static byte[][] getEndKeys() {
225    byte[][] endKeys = Arrays.copyOf(SPLIT_KEYS, SPLIT_KEYS.length + 1);
226    endKeys[endKeys.length - 1] = EMPTY_START_ROW;
227    return endKeys;
228  }
229
230  private ServerName[] getLocations(byte[][] startKeys) {
231    ServerName[] serverNames = new ServerName[startKeys.length];
232    TEST_UTIL.getHBaseCluster().getRegionServerThreads().stream().map(t -> t.getRegionServer())
233      .forEach(rs -> {
234        rs.getRegions(TABLE_NAME).forEach(r -> {
235          serverNames[Arrays.binarySearch(startKeys, r.getRegionInfo().getStartKey(),
236            Bytes::compareTo)] = rs.getServerName();
237        });
238      });
239    return serverNames;
240  }
241
242  @TestTemplate
243  public void testMultiRegionTable() throws IOException, InterruptedException {
244    createMultiRegionTable();
245    byte[][] startKeys = getStartKeys();
246    ServerName[] serverNames = getLocations(startKeys);
247    IntStream.range(0, 2).forEach(n -> IntStream.range(0, startKeys.length).forEach(i -> {
248      try {
249        assertLocEquals(startKeys[i], i == startKeys.length - 1 ? EMPTY_END_ROW : startKeys[i + 1],
250          serverNames[i],
251          getDefaultRegionLocation(TABLE_NAME, startKeys[i], RegionLocateType.CURRENT, false)
252            .get());
253      } catch (InterruptedException | ExecutionException e) {
254        throw new RuntimeException(e);
255      }
256    }));
257
258    locator.clearCache(TABLE_NAME);
259    IntStream.range(0, 2).forEach(n -> IntStream.range(0, startKeys.length).forEach(i -> {
260      try {
261        assertLocEquals(startKeys[i], i == startKeys.length - 1 ? EMPTY_END_ROW : startKeys[i + 1],
262          serverNames[i],
263          getDefaultRegionLocation(TABLE_NAME, startKeys[i], RegionLocateType.AFTER, false).get());
264      } catch (InterruptedException | ExecutionException e) {
265        throw new RuntimeException(e);
266      }
267    }));
268
269    locator.clearCache(TABLE_NAME);
270    byte[][] endKeys = getEndKeys();
271    IntStream.range(0, 2).forEach(
272      n -> IntStream.range(0, endKeys.length).map(i -> endKeys.length - 1 - i).forEach(i -> {
273        try {
274          assertLocEquals(i == 0 ? EMPTY_START_ROW : endKeys[i - 1], endKeys[i], serverNames[i],
275            getDefaultRegionLocation(TABLE_NAME, endKeys[i], RegionLocateType.BEFORE, false).get());
276        } catch (InterruptedException | ExecutionException e) {
277          throw new RuntimeException(e);
278        }
279      }));
280  }
281
282  @TestTemplate
283  public void testRegionMove() throws IOException, InterruptedException, ExecutionException {
284    createSingleRegionTable();
285    ServerName serverName = TEST_UTIL.getRSForFirstRegionInTable(TABLE_NAME).getServerName();
286    HRegionLocation loc =
287      getDefaultRegionLocation(TABLE_NAME, EMPTY_START_ROW, RegionLocateType.CURRENT, false).get();
288    assertLocEquals(EMPTY_START_ROW, EMPTY_END_ROW, serverName, loc);
289    ServerName newServerName = TEST_UTIL.getHBaseCluster().getRegionServerThreads().stream()
290      .map(t -> t.getRegionServer().getServerName()).filter(sn -> !sn.equals(serverName)).findAny()
291      .get();
292
293    TEST_UTIL.getAdmin().move(Bytes.toBytes(loc.getRegion().getEncodedName()), newServerName);
294    while (
295      !TEST_UTIL.getRSForFirstRegionInTable(TABLE_NAME).getServerName().equals(newServerName)
296    ) {
297      Thread.sleep(100);
298    }
299    // Should be same as it is in cache
300    assertSame(loc,
301      getDefaultRegionLocation(TABLE_NAME, EMPTY_START_ROW, RegionLocateType.CURRENT, false).get());
302    locator.updateCachedLocationOnError(loc, null);
303    // null error will not trigger a cache cleanup
304    assertSame(loc,
305      getDefaultRegionLocation(TABLE_NAME, EMPTY_START_ROW, RegionLocateType.CURRENT, false).get());
306    locator.updateCachedLocationOnError(loc, new NotServingRegionException());
307    assertLocEquals(EMPTY_START_ROW, EMPTY_END_ROW, newServerName,
308      getDefaultRegionLocation(TABLE_NAME, EMPTY_START_ROW, RegionLocateType.CURRENT, false).get());
309  }
310
311  // usually locate after will return the same result, so we add a test to make it return different
312  // result.
313  @TestTemplate
314  public void testLocateAfter() throws IOException, InterruptedException, ExecutionException {
315    byte[] row = Bytes.toBytes("1");
316    byte[] splitKey = Arrays.copyOf(row, 2);
317    TEST_UTIL.createTable(TABLE_NAME, FAMILY, new byte[][] { splitKey });
318    TEST_UTIL.waitTableAvailable(TABLE_NAME);
319    HRegionLocation currentLoc =
320      getDefaultRegionLocation(TABLE_NAME, row, RegionLocateType.CURRENT, false).get();
321    ServerName currentServerName = TEST_UTIL.getRSForFirstRegionInTable(TABLE_NAME).getServerName();
322    assertLocEquals(EMPTY_START_ROW, splitKey, currentServerName, currentLoc);
323
324    HRegionLocation afterLoc =
325      getDefaultRegionLocation(TABLE_NAME, row, RegionLocateType.AFTER, false).get();
326    ServerName afterServerName =
327      TEST_UTIL.getHBaseCluster().getRegionServerThreads().stream().map(t -> t.getRegionServer())
328        .filter(rs -> rs.getRegions(TABLE_NAME).stream()
329          .anyMatch(r -> Bytes.equals(splitKey, r.getRegionInfo().getStartKey())))
330        .findAny().get().getServerName();
331    assertLocEquals(splitKey, EMPTY_END_ROW, afterServerName, afterLoc);
332
333    assertSame(afterLoc,
334      getDefaultRegionLocation(TABLE_NAME, row, RegionLocateType.AFTER, false).get());
335  }
336
337  // For HBASE-17402
338  @TestTemplate
339  public void testConcurrentLocate() throws IOException, InterruptedException, ExecutionException {
340    createMultiRegionTable();
341    byte[][] startKeys = getStartKeys();
342    byte[][] endKeys = getEndKeys();
343    ServerName[] serverNames = getLocations(startKeys);
344    for (int i = 0; i < 100; i++) {
345      locator.clearCache(TABLE_NAME);
346      List<CompletableFuture<HRegionLocation>> futures =
347        IntStream.range(0, 1000).mapToObj(n -> String.format("%03d", n)).map(s -> Bytes.toBytes(s))
348          .map(r -> getDefaultRegionLocation(TABLE_NAME, r, RegionLocateType.CURRENT, false))
349          .collect(toList());
350      for (int j = 0; j < 1000; j++) {
351        int index = Math.min(8, j / 111);
352        assertLocEquals(startKeys[index], endKeys[index], serverNames[index], futures.get(j).get());
353      }
354    }
355  }
356
357  @TestTemplate
358  public void testReload() throws Exception {
359    createSingleRegionTable();
360    ServerName serverName = TEST_UTIL.getRSForFirstRegionInTable(TABLE_NAME).getServerName();
361    for (RegionLocateType locateType : RegionLocateType.values()) {
362      assertLocEquals(EMPTY_START_ROW, EMPTY_END_ROW, serverName,
363        getDefaultRegionLocation(TABLE_NAME, EMPTY_START_ROW, locateType, false).get());
364    }
365    ServerName newServerName = TEST_UTIL.getHBaseCluster().getRegionServerThreads().stream()
366      .map(t -> t.getRegionServer().getServerName()).filter(sn -> !sn.equals(serverName)).findAny()
367      .get();
368    Admin admin = TEST_UTIL.getAdmin();
369    RegionInfo region = admin.getRegions(TABLE_NAME).stream().findAny().get();
370    admin.move(region.getEncodedNameAsBytes(), newServerName);
371    TEST_UTIL.waitFor(30000, new ExplainingPredicate<Exception>() {
372
373      @Override
374      public boolean evaluate() throws Exception {
375        ServerName newServerName = TEST_UTIL.getRSForFirstRegionInTable(TABLE_NAME).getServerName();
376        return newServerName != null && !newServerName.equals(serverName);
377      }
378
379      @Override
380      public String explainFailure() throws Exception {
381        return region.getRegionNameAsString() + " is still on " + serverName;
382      }
383
384    });
385    // The cached location will not change
386    for (RegionLocateType locateType : RegionLocateType.values()) {
387      assertLocEquals(EMPTY_START_ROW, EMPTY_END_ROW, serverName,
388        getDefaultRegionLocation(TABLE_NAME, EMPTY_START_ROW, locateType, false).get());
389    }
390    // should get the new location when reload = true
391    // when meta replica LoadBalance mode is enabled, it may delay a bit.
392    TEST_UTIL.waitFor(3000, new ExplainingPredicate<Exception>() {
393      @Override
394      public boolean evaluate() throws Exception {
395        HRegionLocation loc =
396          getDefaultRegionLocation(TABLE_NAME, EMPTY_START_ROW, RegionLocateType.CURRENT, true)
397            .get();
398        return newServerName.equals(loc.getServerName());
399      }
400
401      @Override
402      public String explainFailure() throws Exception {
403        return "New location does not show up in meta (replica) region";
404      }
405    });
406
407    // the cached location should be replaced
408    for (RegionLocateType locateType : RegionLocateType.values()) {
409      assertLocEquals(EMPTY_START_ROW, EMPTY_END_ROW, newServerName,
410        getDefaultRegionLocation(TABLE_NAME, EMPTY_START_ROW, locateType, false).get());
411    }
412  }
413
414  // Testcase for HBASE-20822
415  @TestTemplate
416  public void testLocateBeforeLastRegion()
417    throws IOException, InterruptedException, ExecutionException {
418    createMultiRegionTable();
419    getDefaultRegionLocation(TABLE_NAME, SPLIT_KEYS[0], RegionLocateType.CURRENT, false).join();
420    HRegionLocation loc =
421      getDefaultRegionLocation(TABLE_NAME, EMPTY_END_ROW, RegionLocateType.BEFORE, false).get();
422    // should locate to the last region
423    assertArrayEquals(loc.getRegion().getEndKey(), EMPTY_END_ROW);
424  }
425
426  @TestTemplate
427  public void testRegionReplicas() throws Exception {
428    TEST_UTIL.getAdmin().createTable(TableDescriptorBuilder.newBuilder(TABLE_NAME)
429      .setColumnFamily(ColumnFamilyDescriptorBuilder.of(FAMILY)).setRegionReplication(3).build());
430    TEST_UTIL.waitUntilAllRegionsAssigned(TABLE_NAME);
431    testLocator(TEST_UTIL, TABLE_NAME, new Locator() {
432
433      @Override
434      public void updateCachedLocationOnError(HRegionLocation loc, Throwable error)
435        throws Exception {
436        locator.updateCachedLocationOnError(loc, error);
437      }
438
439      @Override
440      public RegionLocations getRegionLocations(TableName tableName, int replicaId, boolean reload)
441        throws Exception {
442        return locator.getRegionLocations(tableName, EMPTY_START_ROW, replicaId,
443          RegionLocateType.CURRENT, reload).get();
444      }
445    });
446  }
447
448  // Testcase for HBASE-21961
449  @TestTemplate
450  public void testLocateBeforeInOnlyRegion() throws IOException, InterruptedException {
451    createSingleRegionTable();
452    HRegionLocation loc =
453      getDefaultRegionLocation(TABLE_NAME, Bytes.toBytes(1), RegionLocateType.BEFORE, false).join();
454    // should locate to the only region
455    assertArrayEquals(loc.getRegion().getStartKey(), EMPTY_START_ROW);
456    assertArrayEquals(loc.getRegion().getEndKey(), EMPTY_END_ROW);
457  }
458
459  @TestTemplate
460  public void testConcurrentUpdateCachedLocationOnError() throws Exception {
461    createSingleRegionTable();
462    HRegionLocation loc =
463      getDefaultRegionLocation(TABLE_NAME, EMPTY_START_ROW, RegionLocateType.CURRENT, false).get();
464    IntStream.range(0, 100).parallel()
465      .forEach(i -> locator.updateCachedLocationOnError(loc, new NotServingRegionException()));
466  }
467
468  @TestTemplate
469  public void testCacheLocationWhenGetAllLocations() throws Exception {
470    createMultiRegionTable();
471    AsyncConnectionImpl conn = (AsyncConnectionImpl) ConnectionFactory
472      .createAsyncConnection(TEST_UTIL.getConfiguration()).get();
473    conn.getRegionLocator(TABLE_NAME).getAllRegionLocations().get();
474    List<RegionInfo> regions = TEST_UTIL.getAdmin().getRegions(TABLE_NAME);
475    for (RegionInfo region : regions) {
476      assertNotNull(conn.getLocator().getRegionLocationInCache(TABLE_NAME, region.getStartKey()));
477    }
478  }
479
480  @TestTemplate
481  public void testDoNotCacheLocationWithNullServerNameWhenGetAllLocations() throws Exception {
482    createMultiRegionTable();
483    AsyncConnectionImpl conn = (AsyncConnectionImpl) ConnectionFactory
484      .createAsyncConnection(TEST_UTIL.getConfiguration()).get();
485    List<RegionInfo> regions = TEST_UTIL.getAdmin().getRegions(TABLE_NAME);
486    RegionInfo chosen = regions.get(0);
487
488    // re-populate region cache
489    AsyncTableRegionLocator regionLocator = conn.getRegionLocator(TABLE_NAME);
490    regionLocator.clearRegionLocationCache();
491    regionLocator.getAllRegionLocations().get();
492
493    int tries = 3;
494    // expect all to be non-null at first
495    checkRegionsWithRetries(conn, regions, null, tries);
496
497    // clear servername from region info
498    Put put = MetaTableAccessor.makePutFromRegionInfo(chosen, EnvironmentEdgeManager.currentTime());
499    MetaTableAccessor.addEmptyLocation(put, 0);
500    MetaTableAccessor.putsToMetaTable(TEST_UTIL.getConnection(), Lists.newArrayList(put));
501
502    // re-populate region cache again. check that we succeeded in nulling the servername
503    regionLocator.clearRegionLocationCache();
504    for (HRegionLocation loc : regionLocator.getAllRegionLocations().get()) {
505      if (loc.getRegion().equals(chosen)) {
506        assertNull(loc.getServerName());
507      }
508    }
509
510    // expect all but chosen to be non-null. chosen should be null because serverName was null
511    checkRegionsWithRetries(conn, regions, chosen, tries);
512  }
513
514  // caching of getAllRegionLocations is async. so we give it a couple tries
515  private void checkRegionsWithRetries(AsyncConnectionImpl conn, List<RegionInfo> regions,
516    RegionInfo chosen, int retries) throws InterruptedException {
517    while (true) {
518      try {
519        checkRegions(conn, regions, chosen);
520        break;
521      } catch (AssertionError e) {
522        if (retries-- <= 0) {
523          throw e;
524        }
525        Thread.sleep(500);
526      }
527    }
528  }
529
530  private void checkRegions(AsyncConnectionImpl conn, List<RegionInfo> regions, RegionInfo chosen) {
531    for (RegionInfo region : regions) {
532      RegionLocations fromCache =
533        conn.getLocator().getRegionLocationInCache(TABLE_NAME, region.getStartKey());
534      if (region.equals(chosen)) {
535        assertNull(fromCache);
536      } else {
537        assertNotNull(fromCache);
538      }
539    }
540  }
541}