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 org.junit.jupiter.api.Assertions.assertArrayEquals;
021import static org.junit.jupiter.api.Assertions.assertEquals;
022import static org.junit.jupiter.api.Assertions.assertNotNull;
023import static org.junit.jupiter.api.Assertions.assertThrows;
024import static org.junit.jupiter.api.Assertions.assertTrue;
025
026import java.io.IOException;
027import java.util.ArrayList;
028import java.util.Collections;
029import java.util.List;
030import org.apache.hadoop.hbase.HBaseTestingUtil;
031import org.apache.hadoop.hbase.HConstants;
032import org.apache.hadoop.hbase.HRegionLocation;
033import org.apache.hadoop.hbase.RegionLocations;
034import org.apache.hadoop.hbase.ServerName;
035import org.apache.hadoop.hbase.TableName;
036import org.apache.hadoop.hbase.regionserver.Region;
037import org.apache.hadoop.hbase.security.User;
038import org.apache.hadoop.hbase.util.Bytes;
039import org.apache.hadoop.hbase.util.Pair;
040import org.junit.jupiter.api.AfterEach;
041import org.junit.jupiter.api.Test;
042
043public abstract class AbstractTestRegionLocator {
044
045  protected static final HBaseTestingUtil UTIL = new HBaseTestingUtil();
046
047  protected static TableName TABLE_NAME = TableName.valueOf("Locator");
048
049  /**
050   * Single-replica companion table used only by tests that need to assert per-replica cache
051   * population without tripping over the multi-replica
052   * {@link AsyncNonMetaRegionLocator#addLocationToCache(HRegionLocation)} merge limitation.
053   */
054  protected static TableName TABLE_NAME_NO_REPLICA = TableName.valueOf("LocatorNoReplica");
055
056  protected static byte[] FAMILY = Bytes.toBytes("family");
057
058  protected static int REGION_REPLICATION = 3;
059
060  protected static byte[][] SPLIT_KEYS;
061
062  protected static void startClusterAndCreateTable() throws Exception {
063    UTIL.startMiniCluster(3);
064    HBaseTestingUtil.setReplicas(UTIL.getAdmin(), TableName.META_TABLE_NAME, REGION_REPLICATION);
065    TableDescriptor td =
066      TableDescriptorBuilder.newBuilder(TABLE_NAME).setRegionReplication(REGION_REPLICATION)
067        .setColumnFamily(ColumnFamilyDescriptorBuilder.of(FAMILY)).build();
068    SPLIT_KEYS = new byte[9][];
069    for (int i = 0; i < 9; i++) {
070      SPLIT_KEYS[i] = Bytes.toBytes(Integer.toString(i + 1));
071    }
072    UTIL.getAdmin().createTable(td, SPLIT_KEYS);
073    UTIL.waitTableAvailable(TABLE_NAME);
074    TableDescriptor tdNoReplica = TableDescriptorBuilder.newBuilder(TABLE_NAME_NO_REPLICA)
075      .setColumnFamily(ColumnFamilyDescriptorBuilder.of(FAMILY)).build();
076    UTIL.getAdmin().createTable(tdNoReplica, SPLIT_KEYS);
077    UTIL.waitTableAvailable(TABLE_NAME_NO_REPLICA);
078    try (ConnectionRegistry registry =
079      ConnectionRegistryFactory.create(UTIL.getConfiguration(), User.getCurrent())) {
080      RegionReplicaTestHelper.waitUntilAllMetaReplicasAreReady(UTIL, registry);
081    }
082    UTIL.getAdmin().balancerSwitch(false, true);
083  }
084
085  @AfterEach
086  public void tearDownAfterTest() throws IOException {
087    clearCache(TABLE_NAME);
088    clearCache(TABLE_NAME_NO_REPLICA);
089    clearCache(TableName.META_TABLE_NAME);
090  }
091
092  private byte[] getStartKey(int index) {
093    return index == 0 ? HConstants.EMPTY_START_ROW : SPLIT_KEYS[index - 1];
094  }
095
096  private byte[] getEndKey(int index) {
097    return index == SPLIT_KEYS.length ? HConstants.EMPTY_END_ROW : SPLIT_KEYS[index];
098  }
099
100  private void assertStartKeys(byte[][] startKeys) {
101    assertEquals(SPLIT_KEYS.length + 1, startKeys.length);
102    for (int i = 0; i < startKeys.length; i++) {
103      assertArrayEquals(getStartKey(i), startKeys[i]);
104    }
105  }
106
107  private void assertEndKeys(byte[][] endKeys) {
108    assertEquals(SPLIT_KEYS.length + 1, endKeys.length);
109    for (int i = 0; i < endKeys.length; i++) {
110      assertArrayEquals(getEndKey(i), endKeys[i]);
111    }
112  }
113
114  @Test
115  public void testStartEndKeys() throws IOException {
116    assertStartKeys(getStartKeys(TABLE_NAME));
117    assertEndKeys(getEndKeys(TABLE_NAME));
118    Pair<byte[][], byte[][]> startEndKeys = getStartEndKeys(TABLE_NAME);
119    assertStartKeys(startEndKeys.getFirst());
120    assertEndKeys(startEndKeys.getSecond());
121  }
122
123  private void assertRegionLocation(HRegionLocation loc, int index, int replicaId) {
124    RegionInfo region = loc.getRegion();
125    byte[] startKey = getStartKey(index);
126    assertArrayEquals(startKey, region.getStartKey());
127    assertArrayEquals(getEndKey(index), region.getEndKey());
128    assertEquals(replicaId, region.getReplicaId());
129    ServerName expected = findRegionLocation(TABLE_NAME, region.getStartKey(), replicaId);
130    assertEquals(expected, loc.getServerName());
131  }
132
133  private ServerName findRegionLocation(TableName tableName, byte[] startKey, int replicaId) {
134    return UTIL.getMiniHBaseCluster().getRegionServerThreads().stream()
135      .map(t -> t.getRegionServer())
136      .filter(rs -> rs.getRegions(tableName).stream().map(Region::getRegionInfo)
137        .anyMatch(r -> r.containsRow(startKey) && r.getReplicaId() == replicaId))
138      .findFirst().get().getServerName();
139  }
140
141  @Test
142  public void testGetRegionLocation() throws IOException {
143    for (int i = 0; i <= SPLIT_KEYS.length; i++) {
144      for (int replicaId = 0; replicaId < REGION_REPLICATION; replicaId++) {
145        assertRegionLocation(getRegionLocation(TABLE_NAME, getStartKey(i), replicaId), i,
146          replicaId);
147      }
148    }
149  }
150
151  @Test
152  public void testGetRegionLocations() throws IOException {
153    for (int i = 0; i <= SPLIT_KEYS.length; i++) {
154      List<HRegionLocation> locs = getRegionLocations(TABLE_NAME, getStartKey(i));
155      assertEquals(REGION_REPLICATION, locs.size());
156      for (int replicaId = 0; replicaId < REGION_REPLICATION; replicaId++) {
157        assertRegionLocation(locs.get(replicaId), i, replicaId);
158      }
159    }
160  }
161
162  @Test
163  public void testGetAllRegionLocations() throws IOException {
164    List<HRegionLocation> locs = getAllRegionLocations(TABLE_NAME);
165    assertEquals(REGION_REPLICATION * (SPLIT_KEYS.length + 1), locs.size());
166    Collections.sort(locs, (l1, l2) -> {
167      int c = Bytes.compareTo(l1.getRegion().getStartKey(), l2.getRegion().getStartKey());
168      if (c != 0) {
169        return c;
170      }
171      return Integer.compare(l1.getRegion().getReplicaId(), l2.getRegion().getReplicaId());
172    });
173    for (int i = 0; i <= SPLIT_KEYS.length; i++) {
174      for (int replicaId = 0; replicaId < REGION_REPLICATION; replicaId++) {
175        assertRegionLocation(locs.get(i * REGION_REPLICATION + replicaId), i, replicaId);
176      }
177    }
178  }
179
180  @Test
181  public void testGetRegionLocationsFirstPage() throws IOException {
182    List<HRegionLocation> page = getRegionLocationsPage(TABLE_NAME, null, 2);
183    assertEquals(2 * REGION_REPLICATION, page.size());
184    for (int i = 0; i < 2; i++) {
185      for (int replicaId = 0; replicaId < REGION_REPLICATION; replicaId++) {
186        assertRegionLocation(page.get(i * REGION_REPLICATION + replicaId), i, replicaId);
187      }
188    }
189  }
190
191  @Test
192  public void testGetRegionLocationsPagination() throws IOException {
193    int pageSize = 3;
194    int totalRegions = SPLIT_KEYS.length + 1;
195    List<HRegionLocation> all = new ArrayList<>();
196    byte[] cursor = null;
197    while (true) {
198      List<HRegionLocation> page = getRegionLocationsPage(TABLE_NAME, cursor, pageSize);
199      if (page.isEmpty()) {
200        break;
201      }
202      all.addAll(page);
203      HRegionLocation last = page.get(page.size() - 1);
204      byte[] endKey = last.getRegion().getEndKey();
205      if (endKey.length == 0) {
206        break;
207      }
208      cursor = endKey;
209    }
210    assertEquals(totalRegions * REGION_REPLICATION, all.size());
211    for (int i = 0; i <= SPLIT_KEYS.length; i++) {
212      for (int replicaId = 0; replicaId < REGION_REPLICATION; replicaId++) {
213        assertRegionLocation(all.get(i * REGION_REPLICATION + replicaId), i, replicaId);
214      }
215    }
216  }
217
218  @Test
219  public void testGetRegionLocationsEmptyAfterEnd() throws IOException {
220    // Use a startKey lexicographically after all split keys: SPLIT_KEYS go "1".."9", so "z".
221    List<HRegionLocation> page = getRegionLocationsPage(TABLE_NAME, Bytes.toBytes("z"), 5);
222    assertTrue(page.isEmpty(),
223      "expected empty page past the last region; got " + page.size() + " entries");
224  }
225
226  @Test
227  public void testGetRegionLocationsLimitFallsBackToConfig() throws IOException {
228    // mini-cluster default for hbase.meta.scanner.caching is well above SPLIT_KEYS.length+1, so
229    // limit<=0 must return every region.
230    List<HRegionLocation> page = getRegionLocationsPage(TABLE_NAME, null, 0);
231    assertEquals((SPLIT_KEYS.length + 1) * REGION_REPLICATION, page.size());
232    page = getRegionLocationsPage(TABLE_NAME, null, -1);
233    assertEquals((SPLIT_KEYS.length + 1) * REGION_REPLICATION, page.size());
234  }
235
236  @Test
237  public void testGetRegionLocationsRejectsMeta() {
238    assertThrows(IOException.class,
239      () -> getRegionLocationsPage(TableName.META_TABLE_NAME, HConstants.EMPTY_START_ROW, 1));
240  }
241
242  @Test
243  public void testGetRegionLocationsPopulatesCache() throws IOException {
244    // Use the single-replica companion table so the multi-replica
245    // addLocationToCache merge limitation does not interfere with this test.
246    clearCache(TABLE_NAME_NO_REPLICA);
247    List<HRegionLocation> page = getRegionLocationsPage(TABLE_NAME_NO_REPLICA, null, 3);
248    assertEquals(3, page.size());
249    for (HRegionLocation loc : page) {
250      byte[] startKey = loc.getRegion().getStartKey();
251      RegionLocations cached = getCachedLocation(TABLE_NAME_NO_REPLICA, startKey);
252      assertNotNull(cached, "metaCache miss for region starting at "
253        + Bytes.toStringBinary(startKey) + " — bulk API did not populate the cache");
254      HRegionLocation cachedLoc = cached.getRegionLocation(RegionInfo.DEFAULT_REPLICA_ID);
255      assertNotNull(cachedLoc, "metaCache had region but missing default replica entry");
256      assertEquals(loc.getServerName(), cachedLoc.getServerName(),
257        "cached server differs from server returned by bulk API");
258    }
259  }
260
261  private void assertMetaStartOrEndKeys(byte[][] keys) {
262    assertEquals(1, keys.length);
263    assertArrayEquals(HConstants.EMPTY_BYTE_ARRAY, keys[0]);
264  }
265
266  private void assertMetaRegionLocation(HRegionLocation loc, int replicaId) {
267    RegionInfo region = loc.getRegion();
268    assertArrayEquals(HConstants.EMPTY_START_ROW, region.getStartKey());
269    assertArrayEquals(HConstants.EMPTY_END_ROW, region.getEndKey());
270    assertEquals(replicaId, region.getReplicaId());
271    ServerName expected =
272      findRegionLocation(TableName.META_TABLE_NAME, region.getStartKey(), replicaId);
273    assertEquals(expected, loc.getServerName());
274  }
275
276  private void assertMetaRegionLocations(List<HRegionLocation> locs) {
277    assertEquals(REGION_REPLICATION, locs.size());
278    for (int replicaId = 0; replicaId < REGION_REPLICATION; replicaId++) {
279      assertMetaRegionLocation(locs.get(replicaId), replicaId);
280    }
281  }
282
283  @Test
284  public void testMeta() throws IOException {
285    assertMetaStartOrEndKeys(getStartKeys(TableName.META_TABLE_NAME));
286    assertMetaStartOrEndKeys(getEndKeys(TableName.META_TABLE_NAME));
287    Pair<byte[][], byte[][]> startEndKeys = getStartEndKeys(TableName.META_TABLE_NAME);
288    assertMetaStartOrEndKeys(startEndKeys.getFirst());
289    assertMetaStartOrEndKeys(startEndKeys.getSecond());
290    for (int replicaId = 0; replicaId < REGION_REPLICATION; replicaId++) {
291      assertMetaRegionLocation(
292        getRegionLocation(TableName.META_TABLE_NAME, HConstants.EMPTY_START_ROW, replicaId),
293        replicaId);
294    }
295    assertMetaRegionLocations(
296      getRegionLocations(TableName.META_TABLE_NAME, HConstants.EMPTY_START_ROW));
297    assertMetaRegionLocations(getAllRegionLocations(TableName.META_TABLE_NAME));
298  }
299
300  protected abstract byte[][] getStartKeys(TableName tableName) throws IOException;
301
302  protected abstract byte[][] getEndKeys(TableName tableName) throws IOException;
303
304  protected abstract Pair<byte[][], byte[][]> getStartEndKeys(TableName tableName)
305    throws IOException;
306
307  protected abstract HRegionLocation getRegionLocation(TableName tableName, byte[] row,
308    int replicaId) throws IOException;
309
310  protected abstract List<HRegionLocation> getRegionLocations(TableName tableName, byte[] row)
311    throws IOException;
312
313  protected abstract List<HRegionLocation> getAllRegionLocations(TableName tableName)
314    throws IOException;
315
316  protected abstract List<HRegionLocation> getRegionLocationsPage(TableName tableName,
317    byte[] startKey, int limit) throws IOException;
318
319  protected abstract RegionLocations getCachedLocation(TableName tableName, byte[] startKey)
320    throws IOException;
321
322  protected abstract void clearCache(TableName tableName) throws IOException;
323}