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