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;
019
020import static org.junit.jupiter.api.Assertions.assertEquals;
021import static org.junit.jupiter.api.Assertions.assertNotNull;
022import static org.junit.jupiter.api.Assertions.assertNull;
023import static org.junit.jupiter.api.Assertions.assertTrue;
024import static org.mockito.ArgumentMatchers.any;
025import static org.mockito.Mockito.doReturn;
026import static org.mockito.Mockito.mock;
027import static org.mockito.Mockito.reset;
028import static org.mockito.Mockito.times;
029import static org.mockito.Mockito.verify;
030
031import java.io.IOException;
032import java.util.Collections;
033import java.util.List;
034import java.util.Random;
035import java.util.concurrent.ThreadLocalRandom;
036import org.apache.hadoop.conf.Configuration;
037import org.apache.hadoop.hbase.client.Connection;
038import org.apache.hadoop.hbase.client.ConnectionFactory;
039import org.apache.hadoop.hbase.client.Get;
040import org.apache.hadoop.hbase.client.RegionInfo;
041import org.apache.hadoop.hbase.client.RegionInfoBuilder;
042import org.apache.hadoop.hbase.client.RegionLocator;
043import org.apache.hadoop.hbase.client.Result;
044import org.apache.hadoop.hbase.client.Scan;
045import org.apache.hadoop.hbase.client.Table;
046import org.apache.hadoop.hbase.filter.PrefixFilter;
047import org.apache.hadoop.hbase.ipc.CallRunner;
048import org.apache.hadoop.hbase.ipc.DelegatingRpcScheduler;
049import org.apache.hadoop.hbase.ipc.PriorityFunction;
050import org.apache.hadoop.hbase.ipc.RpcScheduler;
051import org.apache.hadoop.hbase.master.HMaster;
052import org.apache.hadoop.hbase.regionserver.HRegion;
053import org.apache.hadoop.hbase.regionserver.HRegionServer;
054import org.apache.hadoop.hbase.regionserver.SimpleRpcSchedulerFactory;
055import org.apache.hadoop.hbase.testclassification.MediumTests;
056import org.apache.hadoop.hbase.testclassification.MiscTests;
057import org.apache.hadoop.hbase.util.Bytes;
058import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
059import org.apache.hadoop.hbase.util.Pair;
060import org.junit.jupiter.api.AfterAll;
061import org.junit.jupiter.api.BeforeAll;
062import org.junit.jupiter.api.Tag;
063import org.junit.jupiter.api.Test;
064import org.junit.jupiter.api.TestInfo;
065import org.slf4j.Logger;
066import org.slf4j.LoggerFactory;
067
068import org.apache.hbase.thirdparty.com.google.common.collect.Lists;
069
070/**
071 * Test {@link org.apache.hadoop.hbase.MetaTableAccessor}.
072 */
073@Tag(MiscTests.TAG)
074@Tag(MediumTests.TAG)
075@SuppressWarnings("deprecation")
076public class TestMetaTableAccessor {
077
078  private static final Logger LOG = LoggerFactory.getLogger(TestMetaTableAccessor.class);
079  private static final HBaseTestingUtil UTIL = new HBaseTestingUtil();
080  private static Connection connection;
081
082  @BeforeAll
083  public static void beforeClass() throws Exception {
084    UTIL.startMiniCluster(3);
085
086    Configuration c = new Configuration(UTIL.getConfiguration());
087    // Tests to 4 retries every 5 seconds. Make it try every 1 second so more
088    // responsive. 1 second is default as is ten retries.
089    c.setLong("hbase.client.pause", 1000);
090    c.setInt(HConstants.HBASE_CLIENT_RETRIES_NUMBER, 10);
091    connection = ConnectionFactory.createConnection(c);
092  }
093
094  @AfterAll
095  public static void afterClass() throws Exception {
096    connection.close();
097    UTIL.shutdownMiniCluster();
098  }
099
100  @Test
101  public void testIsMetaWhenAllHealthy() throws InterruptedException {
102    HMaster m = UTIL.getMiniHBaseCluster().getMaster();
103    assertTrue(m.waitForMetaOnline());
104  }
105
106  @Test
107  public void testIsMetaWhenMetaGoesOffline() throws InterruptedException {
108    HMaster m = UTIL.getMiniHBaseCluster().getMaster();
109    int index = UTIL.getMiniHBaseCluster().getServerWithMeta();
110    HRegionServer rsWithMeta = UTIL.getMiniHBaseCluster().getRegionServer(index);
111    rsWithMeta.abort("TESTING");
112    assertTrue(m.waitForMetaOnline());
113  }
114
115  /**
116   * Does {@link MetaTableAccessor#getRegion(Connection, byte[])} and a write against hbase:meta
117   * while its hosted server is restarted to prove our retrying works.
118   */
119  @Test
120  public void testRetrying(TestInfo testInfo) throws IOException, InterruptedException {
121    final TableName tableName = TableName.valueOf(testInfo.getTestMethod().get().getName());
122    LOG.info("Started " + tableName);
123    Table t = UTIL.createMultiRegionTable(tableName, HConstants.CATALOG_FAMILY);
124    int regionCount = -1;
125    try (RegionLocator r = UTIL.getConnection().getRegionLocator(tableName)) {
126      regionCount = r.getStartKeys().length;
127    }
128    // Test it works getting a region from just made user table.
129    final List<RegionInfo> regions = testGettingTableRegions(connection, tableName, regionCount);
130    MetaTask reader = new MetaTask(connection, "reader") {
131      @Override
132      void metaTask() throws Throwable {
133        testGetRegion(connection, regions.get(0));
134        LOG.info("Read " + regions.get(0).getEncodedName());
135      }
136    };
137    MetaTask writer = new MetaTask(connection, "writer") {
138
139      @Override
140      void metaTask() throws IOException {
141        MetaTableAccessor.addRegionsToMeta(connection, Collections.singletonList(regions.get(0)),
142          1);
143        LOG.info("Wrote " + regions.get(0).getEncodedName());
144      }
145    };
146    reader.start();
147    writer.start();
148
149    // We're gonna check how it takes. If it takes too long, we will consider
150    // it as a fail. We can't put that in the @Test tag as we want to close
151    // the threads nicely
152    final long timeOut = 180000;
153    long startTime = EnvironmentEdgeManager.currentTime();
154
155    try {
156      // Make sure reader and writer are working.
157      assertTrue(reader.isProgressing());
158      assertTrue(writer.isProgressing());
159
160      // Kill server hosting meta -- twice . See if our reader/writer ride over the
161      // meta moves. They'll need to retry.
162      for (int i = 0; i < 2; i++) {
163        LOG.info("Restart=" + i);
164        UTIL.ensureSomeRegionServersAvailable(2);
165        int index = -1;
166        do {
167          index = UTIL.getMiniHBaseCluster().getServerWithMeta();
168        } while (index == -1 && startTime + timeOut < EnvironmentEdgeManager.currentTime());
169
170        if (index != -1) {
171          UTIL.getMiniHBaseCluster().abortRegionServer(index);
172          UTIL.getMiniHBaseCluster().waitOnRegionServer(index);
173        }
174      }
175
176      assertTrue(reader.isProgressing(), "reader: " + reader.toString());
177      assertTrue(writer.isProgressing(), "writer: " + writer.toString());
178    } catch (IOException e) {
179      throw e;
180    } finally {
181      reader.stop = true;
182      writer.stop = true;
183      reader.join();
184      writer.join();
185      t.close();
186    }
187    long exeTime = EnvironmentEdgeManager.currentTime() - startTime;
188    assertTrue(exeTime < timeOut, "Timeout: test took " + exeTime / 1000 + " sec");
189  }
190
191  /**
192   * Thread that runs a MetaTableAccessor task until asked stop.
193   */
194  abstract static class MetaTask extends Thread {
195    boolean stop = false;
196    int count = 0;
197    Throwable t = null;
198    final Connection connection;
199
200    MetaTask(final Connection connection, final String name) {
201      super(name);
202      this.connection = connection;
203    }
204
205    @Override
206    public void run() {
207      try {
208        while (!this.stop) {
209          LOG.info("Before " + this.getName() + ", count=" + this.count);
210          metaTask();
211          this.count += 1;
212          LOG.info("After " + this.getName() + ", count=" + this.count);
213          Thread.sleep(100);
214        }
215      } catch (Throwable t) {
216        LOG.info(this.getName() + " failed", t);
217        this.t = t;
218      }
219    }
220
221    boolean isProgressing() throws InterruptedException {
222      int currentCount = this.count;
223      while (currentCount == this.count) {
224        if (!isAlive()) return false;
225        if (this.t != null) return false;
226        Thread.sleep(10);
227      }
228      return true;
229    }
230
231    @Override
232    public String toString() {
233      return "count=" + this.count + ", t=" + (this.t == null ? "null" : this.t.toString());
234    }
235
236    abstract void metaTask() throws Throwable;
237  }
238
239  @Test
240  public void testGetRegion(TestInfo testInfo) throws IOException, InterruptedException {
241    final String name = testInfo.getTestMethod().get().getName();
242    LOG.info("Started " + name);
243    // Test get on non-existent region.
244    Pair<RegionInfo, ServerName> pair =
245      MetaTableAccessor.getRegion(connection, Bytes.toBytes("nonexistent-region"));
246    assertNull(pair);
247    LOG.info("Finished " + name);
248  }
249
250  // Test for the optimization made in HBASE-3650
251  @Test
252  public void testScanMetaForTable(TestInfo testInfo) throws IOException, InterruptedException {
253    final TableName tableName = TableName.valueOf(testInfo.getTestMethod().get().getName());
254    LOG.info("Started " + tableName);
255
256    /**
257     * Create 2 tables - testScanMetaForTable - testScanMetaForTablf
258     **/
259
260    UTIL.createTable(tableName, HConstants.CATALOG_FAMILY);
261    // name that is +1 greater than the first one (e+1=f)
262    TableName greaterName = TableName.valueOf("testScanMetaForTablf");
263    UTIL.createTable(greaterName, HConstants.CATALOG_FAMILY);
264
265    // Now make sure we only get the regions from 1 of the tables at a time
266
267    assertEquals(1, MetaTableAccessor.getTableRegions(connection, tableName).size());
268    assertEquals(1, MetaTableAccessor.getTableRegions(connection, greaterName).size());
269  }
270
271  private static List<RegionInfo> testGettingTableRegions(final Connection connection,
272    final TableName name, final int regionCount) throws IOException, InterruptedException {
273    List<RegionInfo> regions = MetaTableAccessor.getTableRegions(connection, name);
274    assertEquals(regionCount, regions.size());
275    Pair<RegionInfo, ServerName> pair =
276      MetaTableAccessor.getRegion(connection, regions.get(0).getRegionName());
277    assertEquals(regions.get(0).getEncodedName(), pair.getFirst().getEncodedName());
278    return regions;
279  }
280
281  private static void testGetRegion(final Connection connection, final RegionInfo region)
282    throws IOException, InterruptedException {
283    Pair<RegionInfo, ServerName> pair =
284      MetaTableAccessor.getRegion(connection, region.getRegionName());
285    assertEquals(region.getEncodedName(), pair.getFirst().getEncodedName());
286  }
287
288  @Test
289  public void testMetaLocationsForRegionReplicas(TestInfo testInfo) throws IOException {
290    Random rand = ThreadLocalRandom.current();
291
292    ServerName serverName0 = ServerName.valueOf("foo", 60010, rand.nextLong());
293    ServerName serverName1 = ServerName.valueOf("bar", 60010, rand.nextLong());
294    ServerName serverName100 = ServerName.valueOf("baz", 60010, rand.nextLong());
295
296    long regionId = EnvironmentEdgeManager.currentTime();
297    RegionInfo primary =
298      RegionInfoBuilder.newBuilder(TableName.valueOf(testInfo.getTestMethod().get().getName()))
299        .setStartKey(HConstants.EMPTY_START_ROW).setEndKey(HConstants.EMPTY_END_ROW).setSplit(false)
300        .setRegionId(regionId).setReplicaId(0).build();
301    RegionInfo replica1 =
302      RegionInfoBuilder.newBuilder(TableName.valueOf(testInfo.getTestMethod().get().getName()))
303        .setStartKey(HConstants.EMPTY_START_ROW).setEndKey(HConstants.EMPTY_END_ROW).setSplit(false)
304        .setRegionId(regionId).setReplicaId(1).build();
305    RegionInfo replica100 =
306      RegionInfoBuilder.newBuilder(TableName.valueOf(testInfo.getTestMethod().get().getName()))
307        .setStartKey(HConstants.EMPTY_START_ROW).setEndKey(HConstants.EMPTY_END_ROW).setSplit(false)
308        .setRegionId(regionId).setReplicaId(100).build();
309
310    long seqNum0 = rand.nextLong();
311    long seqNum1 = rand.nextLong();
312    long seqNum100 = rand.nextLong();
313
314    try (Table meta = MetaTableAccessor.getMetaHTable(connection)) {
315      MetaTableAccessor.updateRegionLocation(connection, primary, serverName0, seqNum0,
316        EnvironmentEdgeManager.currentTime());
317
318      // assert that the server, startcode and seqNum columns are there for the primary region
319      assertMetaLocation(meta, primary.getRegionName(), serverName0, seqNum0, 0, true);
320
321      // add replica = 1
322      MetaTableAccessor.updateRegionLocation(connection, replica1, serverName1, seqNum1,
323        EnvironmentEdgeManager.currentTime());
324      // check whether the primary is still there
325      assertMetaLocation(meta, primary.getRegionName(), serverName0, seqNum0, 0, true);
326      // now check for replica 1
327      assertMetaLocation(meta, primary.getRegionName(), serverName1, seqNum1, 1, true);
328
329      // add replica = 1
330      MetaTableAccessor.updateRegionLocation(connection, replica100, serverName100, seqNum100,
331        EnvironmentEdgeManager.currentTime());
332      // check whether the primary is still there
333      assertMetaLocation(meta, primary.getRegionName(), serverName0, seqNum0, 0, true);
334      // check whether the replica 1 is still there
335      assertMetaLocation(meta, primary.getRegionName(), serverName1, seqNum1, 1, true);
336      // now check for replica 1
337      assertMetaLocation(meta, primary.getRegionName(), serverName100, seqNum100, 100, true);
338    }
339  }
340
341  public static void assertMetaLocation(Table meta, byte[] row, ServerName serverName, long seqNum,
342    int replicaId, boolean checkSeqNum) throws IOException {
343    Get get = new Get(row);
344    Result result = meta.get(get);
345    assertTrue(Bytes.equals(
346      result.getValue(HConstants.CATALOG_FAMILY, CatalogFamilyFormat.getServerColumn(replicaId)),
347      Bytes.toBytes(serverName.getAddress().toString())));
348    assertTrue(Bytes.equals(
349      result.getValue(HConstants.CATALOG_FAMILY, CatalogFamilyFormat.getStartCodeColumn(replicaId)),
350      Bytes.toBytes(serverName.getStartcode())));
351    if (checkSeqNum) {
352      assertTrue(Bytes.equals(
353        result.getValue(HConstants.CATALOG_FAMILY, CatalogFamilyFormat.getSeqNumColumn(replicaId)),
354        Bytes.toBytes(seqNum)));
355    }
356  }
357
358  public static void assertEmptyMetaLocation(Table meta, byte[] row, int replicaId)
359    throws IOException {
360    Get get = new Get(row);
361    Result result = meta.get(get);
362    Cell serverCell = result.getColumnLatestCell(HConstants.CATALOG_FAMILY,
363      CatalogFamilyFormat.getServerColumn(replicaId));
364    Cell startCodeCell = result.getColumnLatestCell(HConstants.CATALOG_FAMILY,
365      CatalogFamilyFormat.getStartCodeColumn(replicaId));
366    assertNotNull(serverCell);
367    assertNotNull(startCodeCell);
368    assertEquals(0, serverCell.getValueLength());
369    assertEquals(0, startCodeCell.getValueLength());
370  }
371
372  @Test
373  public void testMetaLocationForRegionReplicasIsAddedAtTableCreation(TestInfo testInfo)
374    throws IOException {
375    long regionId = EnvironmentEdgeManager.currentTime();
376    RegionInfo primary =
377      RegionInfoBuilder.newBuilder(TableName.valueOf(testInfo.getTestMethod().get().getName()))
378        .setStartKey(HConstants.EMPTY_START_ROW).setEndKey(HConstants.EMPTY_END_ROW).setSplit(false)
379        .setRegionId(regionId).setReplicaId(0).build();
380
381    Table meta = MetaTableAccessor.getMetaHTable(connection);
382    try {
383      List<RegionInfo> regionInfos = Lists.newArrayList(primary);
384      MetaTableAccessor.addRegionsToMeta(connection, regionInfos, 3);
385
386      assertEmptyMetaLocation(meta, primary.getRegionName(), 1);
387      assertEmptyMetaLocation(meta, primary.getRegionName(), 2);
388    } finally {
389      meta.close();
390    }
391  }
392
393  @Test
394  public void testMetaScanner(TestInfo testInfo) throws Exception {
395    LOG.info("Starting " + testInfo.getTestMethod().get().getName());
396
397    final TableName tableName = TableName.valueOf(testInfo.getTestMethod().get().getName());
398    final byte[] FAMILY = Bytes.toBytes("family");
399    final byte[][] SPLIT_KEYS =
400      new byte[][] { Bytes.toBytes("region_a"), Bytes.toBytes("region_b") };
401
402    UTIL.createTable(tableName, FAMILY, SPLIT_KEYS);
403    Table table = connection.getTable(tableName);
404    // Make sure all the regions are deployed
405    HBaseTestingUtil.countRows(table);
406
407    ClientMetaTableAccessor.Visitor visitor = mock(ClientMetaTableAccessor.Visitor.class);
408    doReturn(true).when(visitor).visit(any());
409
410    // Scanning the entire table should give us three rows
411    MetaTableAccessor.scanMetaForTableRegions(connection, visitor, tableName);
412    verify(visitor, times(3)).visit(any());
413
414    // Scanning the table with a specified empty start row should also
415    // give us three hbase:meta rows
416    reset(visitor);
417    doReturn(true).when(visitor).visit(any());
418    MetaTableAccessor.scanMeta(connection, visitor, tableName, null, 1000);
419    verify(visitor, times(3)).visit(any());
420
421    // Scanning the table starting in the middle should give us two rows:
422    // region_a and region_b
423    reset(visitor);
424    doReturn(true).when(visitor).visit(any());
425    MetaTableAccessor.scanMeta(connection, visitor, tableName, Bytes.toBytes("region_ac"), 1000);
426    verify(visitor, times(2)).visit(any());
427
428    // Scanning with a limit of 1 should only give us one row
429    reset(visitor);
430    doReturn(true).when(visitor).visit(any());
431    MetaTableAccessor.scanMeta(connection, visitor, tableName, Bytes.toBytes("region_ac"), 1);
432    verify(visitor, times(1)).visit(any());
433
434    byte[] prefix = Bytes.add(tableName.getName(), new byte[] { HConstants.DELIMITER });
435    try (Table metaTable = connection.getTable(TableName.META_TABLE_NAME)) {
436      Scan rangeScan = new Scan().withStartRow(prefix)
437        .withStopRow(Bytes.add(tableName.getName(), new byte[] { HConstants.DELIMITER + 1 }));
438      assertEquals(3, HBaseTestingUtil.countRows(metaTable, rangeScan));
439
440      Scan prefixScan = new Scan();
441      prefixScan.setFilter(new PrefixFilter(prefix));
442      assertEquals(3, HBaseTestingUtil.countRows(metaTable, prefixScan));
443    }
444    table.close();
445  }
446
447  /**
448   * Tests whether maximum of masters system time versus RSs local system time is used
449   */
450  @Test
451  public void testMastersSystemTimeIsUsedInUpdateLocations(TestInfo testInfo) throws IOException {
452    long regionId = EnvironmentEdgeManager.currentTime();
453    RegionInfo regionInfo =
454      RegionInfoBuilder.newBuilder(TableName.valueOf(testInfo.getTestMethod().get().getName()))
455        .setStartKey(HConstants.EMPTY_START_ROW).setEndKey(HConstants.EMPTY_END_ROW).setSplit(false)
456        .setRegionId(regionId).setReplicaId(0).build();
457
458    ServerName sn = ServerName.valueOf("bar", 0, 0);
459    try (Table meta = MetaTableAccessor.getMetaHTable(connection)) {
460      List<RegionInfo> regionInfos = Lists.newArrayList(regionInfo);
461      MetaTableAccessor.addRegionsToMeta(connection, regionInfos, 1);
462
463      long masterSystemTime = EnvironmentEdgeManager.currentTime() + 123456789;
464      MetaTableAccessor.updateRegionLocation(connection, regionInfo, sn, 1, masterSystemTime);
465
466      Get get = new Get(regionInfo.getRegionName());
467      Result result = meta.get(get);
468      Cell serverCell = result.getColumnLatestCell(HConstants.CATALOG_FAMILY,
469        CatalogFamilyFormat.getServerColumn(0));
470      Cell startCodeCell = result.getColumnLatestCell(HConstants.CATALOG_FAMILY,
471        CatalogFamilyFormat.getStartCodeColumn(0));
472      Cell seqNumCell = result.getColumnLatestCell(HConstants.CATALOG_FAMILY,
473        CatalogFamilyFormat.getSeqNumColumn(0));
474      assertNotNull(serverCell);
475      assertNotNull(startCodeCell);
476      assertNotNull(seqNumCell);
477      assertTrue(serverCell.getValueLength() > 0);
478      assertTrue(startCodeCell.getValueLength() > 0);
479      assertTrue(seqNumCell.getValueLength() > 0);
480      assertEquals(masterSystemTime, serverCell.getTimestamp());
481      assertEquals(masterSystemTime, startCodeCell.getTimestamp());
482      assertEquals(masterSystemTime, seqNumCell.getTimestamp());
483    }
484  }
485
486  public static class SpyingRpcSchedulerFactory extends SimpleRpcSchedulerFactory {
487    @Override
488    public RpcScheduler create(Configuration conf, PriorityFunction priority, Abortable server) {
489      final RpcScheduler delegate = super.create(conf, priority, server);
490      return new SpyingRpcScheduler(delegate);
491    }
492  }
493
494  public static class SpyingRpcScheduler extends DelegatingRpcScheduler {
495    long numPriorityCalls = 0;
496
497    public SpyingRpcScheduler(RpcScheduler delegate) {
498      super(delegate);
499    }
500
501    @Override
502    public boolean dispatch(CallRunner task) {
503      int priority = task.getRpcCall().getPriority();
504
505      if (priority > HConstants.QOS_THRESHOLD) {
506        numPriorityCalls++;
507      }
508      return super.dispatch(task);
509    }
510  }
511
512  @Test
513  public void testScanByRegionEncodedNameExistingRegion() throws Exception {
514    final TableName tableName = TableName.valueOf("testScanByRegionEncodedNameExistingRegion");
515    UTIL.createTable(tableName, "cf");
516    final List<HRegion> regions = UTIL.getHBaseCluster().getRegions(tableName);
517    final String encodedName = regions.get(0).getRegionInfo().getEncodedName();
518    final Result result =
519      MetaTableAccessor.scanByRegionEncodedName(UTIL.getConnection(), encodedName);
520    assertNotNull(result);
521    assertTrue(result.advance());
522    final String resultingRowKey = CellUtil.getCellKeyAsString(result.current());
523    assertTrue(resultingRowKey.contains(encodedName));
524    UTIL.deleteTable(tableName);
525  }
526
527  @Test
528  public void testScanByRegionEncodedNameNonExistingRegion() throws Exception {
529    final String encodedName = "nonexistingregion";
530    final Result result =
531      MetaTableAccessor.scanByRegionEncodedName(UTIL.getConnection(), encodedName);
532    assertNull(result);
533  }
534}