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