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.Assert.assertEquals;
021import static org.junit.Assert.assertNotNull;
022import static org.junit.Assert.assertNull;
023import static org.junit.Assert.assertTrue;
024import static org.mockito.ArgumentMatchers.anyObject;
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 org.apache.hadoop.conf.Configuration;
036import org.apache.hadoop.hbase.client.Connection;
037import org.apache.hadoop.hbase.client.ConnectionFactory;
038import org.apache.hadoop.hbase.client.Get;
039import org.apache.hadoop.hbase.client.RegionInfo;
040import org.apache.hadoop.hbase.client.RegionInfoBuilder;
041import org.apache.hadoop.hbase.client.RegionLocator;
042import org.apache.hadoop.hbase.client.Result;
043import org.apache.hadoop.hbase.client.Table;
044import org.apache.hadoop.hbase.ipc.CallRunner;
045import org.apache.hadoop.hbase.ipc.DelegatingRpcScheduler;
046import org.apache.hadoop.hbase.ipc.PriorityFunction;
047import org.apache.hadoop.hbase.ipc.RpcScheduler;
048import org.apache.hadoop.hbase.master.HMaster;
049import org.apache.hadoop.hbase.regionserver.HRegion;
050import org.apache.hadoop.hbase.regionserver.HRegionServer;
051import org.apache.hadoop.hbase.regionserver.SimpleRpcSchedulerFactory;
052import org.apache.hadoop.hbase.testclassification.MediumTests;
053import org.apache.hadoop.hbase.testclassification.MiscTests;
054import org.apache.hadoop.hbase.util.Bytes;
055import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
056import org.apache.hadoop.hbase.util.Pair;
057import org.junit.AfterClass;
058import org.junit.BeforeClass;
059import org.junit.ClassRule;
060import org.junit.Rule;
061import org.junit.Test;
062import org.junit.experimental.categories.Category;
063import org.junit.rules.TestName;
064import org.slf4j.Logger;
065import org.slf4j.LoggerFactory;
066
067import org.apache.hbase.thirdparty.com.google.common.collect.Lists;
068
069/**
070 * Test {@link org.apache.hadoop.hbase.MetaTableAccessor}.
071 */
072@Category({ MiscTests.class, MediumTests.class })
073@SuppressWarnings("deprecation")
074public class TestMetaTableAccessor {
075  @ClassRule
076  public static final HBaseClassTestRule CLASS_RULE =
077    HBaseClassTestRule.forClass(TestMetaTableAccessor.class);
078
079  private static final Logger LOG = LoggerFactory.getLogger(TestMetaTableAccessor.class);
080  private static final HBaseTestingUtil UTIL = new HBaseTestingUtil();
081  private static Connection connection;
082  private Random random = new Random();
083
084  @Rule
085  public TestName name = new TestName();
086
087  @BeforeClass
088  public static void beforeClass() throws Exception {
089    UTIL.startMiniCluster(3);
090
091    Configuration c = new Configuration(UTIL.getConfiguration());
092    // Tests to 4 retries every 5 seconds. Make it try every 1 second so more
093    // responsive. 1 second is default as is ten retries.
094    c.setLong("hbase.client.pause", 1000);
095    c.setInt(HConstants.HBASE_CLIENT_RETRIES_NUMBER, 10);
096    connection = ConnectionFactory.createConnection(c);
097  }
098
099  @AfterClass
100  public static void afterClass() throws Exception {
101    connection.close();
102    UTIL.shutdownMiniCluster();
103  }
104
105  @Test
106  public void testIsMetaWhenAllHealthy() throws InterruptedException {
107    HMaster m = UTIL.getMiniHBaseCluster().getMaster();
108    assertTrue(m.waitForMetaOnline());
109  }
110
111  @Test
112  public void testIsMetaWhenMetaGoesOffline() throws InterruptedException {
113    HMaster m = UTIL.getMiniHBaseCluster().getMaster();
114    int index = UTIL.getMiniHBaseCluster().getServerWithMeta();
115    HRegionServer rsWithMeta = UTIL.getMiniHBaseCluster().getRegionServer(index);
116    rsWithMeta.abort("TESTING");
117    assertTrue(m.waitForMetaOnline());
118  }
119
120  /**
121   * Does {@link MetaTableAccessor#getRegion(Connection, byte[])} and a write against hbase:meta
122   * while its hosted server is restarted to prove our retrying works.
123   */
124  @Test
125  public void testRetrying() throws IOException, InterruptedException {
126    final TableName tableName = TableName.valueOf(name.getMethodName());
127    LOG.info("Started " + tableName);
128    Table t = UTIL.createMultiRegionTable(tableName, HConstants.CATALOG_FAMILY);
129    int regionCount = -1;
130    try (RegionLocator r = UTIL.getConnection().getRegionLocator(tableName)) {
131      regionCount = r.getStartKeys().length;
132    }
133    // Test it works getting a region from just made user table.
134    final List<RegionInfo> regions = testGettingTableRegions(connection, tableName, regionCount);
135    MetaTask reader = new MetaTask(connection, "reader") {
136      @Override
137      void metaTask() throws Throwable {
138        testGetRegion(connection, regions.get(0));
139        LOG.info("Read " + regions.get(0).getEncodedName());
140      }
141    };
142    MetaTask writer = new MetaTask(connection, "writer") {
143
144      @Override
145      void metaTask() throws IOException {
146        MetaTableAccessor.addRegionsToMeta(connection, Collections.singletonList(regions.get(0)),
147          1);
148        LOG.info("Wrote " + regions.get(0).getEncodedName());
149      }
150    };
151    reader.start();
152    writer.start();
153
154    // We're gonna check how it takes. If it takes too long, we will consider
155    // it as a fail. We can't put that in the @Test tag as we want to close
156    // the threads nicely
157    final long timeOut = 180000;
158    long startTime = EnvironmentEdgeManager.currentTime();
159
160    try {
161      // Make sure reader and writer are working.
162      assertTrue(reader.isProgressing());
163      assertTrue(writer.isProgressing());
164
165      // Kill server hosting meta -- twice . See if our reader/writer ride over the
166      // meta moves. They'll need to retry.
167      for (int i = 0; i < 2; i++) {
168        LOG.info("Restart=" + i);
169        UTIL.ensureSomeRegionServersAvailable(2);
170        int index = -1;
171        do {
172          index = UTIL.getMiniHBaseCluster().getServerWithMeta();
173        } while (index == -1 && startTime + timeOut < EnvironmentEdgeManager.currentTime());
174
175        if (index != -1) {
176          UTIL.getMiniHBaseCluster().abortRegionServer(index);
177          UTIL.getMiniHBaseCluster().waitOnRegionServer(index);
178        }
179      }
180
181      assertTrue("reader: " + reader.toString(), reader.isProgressing());
182      assertTrue("writer: " + writer.toString(), writer.isProgressing());
183    } catch (IOException e) {
184      throw e;
185    } finally {
186      reader.stop = true;
187      writer.stop = true;
188      reader.join();
189      writer.join();
190      t.close();
191    }
192    long exeTime = EnvironmentEdgeManager.currentTime() - startTime;
193    assertTrue("Timeout: test took " + exeTime / 1000 + " sec", exeTime < timeOut);
194  }
195
196  /**
197   * Thread that runs a MetaTableAccessor task until asked stop.
198   */
199  abstract static class MetaTask extends Thread {
200    boolean stop = false;
201    int count = 0;
202    Throwable t = null;
203    final Connection connection;
204
205    MetaTask(final Connection connection, final String name) {
206      super(name);
207      this.connection = connection;
208    }
209
210    @Override
211    public void run() {
212      try {
213        while (!this.stop) {
214          LOG.info("Before " + this.getName() + ", count=" + this.count);
215          metaTask();
216          this.count += 1;
217          LOG.info("After " + this.getName() + ", count=" + this.count);
218          Thread.sleep(100);
219        }
220      } catch (Throwable t) {
221        LOG.info(this.getName() + " failed", t);
222        this.t = t;
223      }
224    }
225
226    boolean isProgressing() throws InterruptedException {
227      int currentCount = this.count;
228      while (currentCount == this.count) {
229        if (!isAlive()) return false;
230        if (this.t != null) return false;
231        Thread.sleep(10);
232      }
233      return true;
234    }
235
236    @Override
237    public String toString() {
238      return "count=" + this.count + ", t=" + (this.t == null ? "null" : this.t.toString());
239    }
240
241    abstract void metaTask() throws Throwable;
242  }
243
244  @Test
245  public void testGetRegion() throws IOException, InterruptedException {
246    final String name = this.name.getMethodName();
247    LOG.info("Started " + name);
248    // Test get on non-existent region.
249    Pair<RegionInfo, ServerName> pair =
250      MetaTableAccessor.getRegion(connection, Bytes.toBytes("nonexistent-region"));
251    assertNull(pair);
252    LOG.info("Finished " + name);
253  }
254
255  // Test for the optimization made in HBASE-3650
256  @Test
257  public void testScanMetaForTable() throws IOException, InterruptedException {
258    final TableName tableName = TableName.valueOf(name.getMethodName());
259    LOG.info("Started " + tableName);
260
261    /**
262     * Create 2 tables - testScanMetaForTable - testScanMetaForTablf
263     **/
264
265    UTIL.createTable(tableName, HConstants.CATALOG_FAMILY);
266    // name that is +1 greater than the first one (e+1=f)
267    TableName greaterName = TableName.valueOf("testScanMetaForTablf");
268    UTIL.createTable(greaterName, HConstants.CATALOG_FAMILY);
269
270    // Now make sure we only get the regions from 1 of the tables at a time
271
272    assertEquals(1, MetaTableAccessor.getTableRegions(connection, tableName).size());
273    assertEquals(1, MetaTableAccessor.getTableRegions(connection, greaterName).size());
274  }
275
276  private static List<RegionInfo> testGettingTableRegions(final Connection connection,
277    final TableName name, final int regionCount) throws IOException, InterruptedException {
278    List<RegionInfo> regions = MetaTableAccessor.getTableRegions(connection, name);
279    assertEquals(regionCount, regions.size());
280    Pair<RegionInfo, ServerName> pair =
281      MetaTableAccessor.getRegion(connection, regions.get(0).getRegionName());
282    assertEquals(regions.get(0).getEncodedName(), pair.getFirst().getEncodedName());
283    return regions;
284  }
285
286  private static void testGetRegion(final Connection connection, final RegionInfo region)
287    throws IOException, InterruptedException {
288    Pair<RegionInfo, ServerName> pair =
289      MetaTableAccessor.getRegion(connection, region.getRegionName());
290    assertEquals(region.getEncodedName(), pair.getFirst().getEncodedName());
291  }
292
293  @Test
294  public void testMetaLocationsForRegionReplicas() throws IOException {
295    ServerName serverName0 = ServerName.valueOf("foo", 60010, random.nextLong());
296    ServerName serverName1 = ServerName.valueOf("bar", 60010, random.nextLong());
297    ServerName serverName100 = ServerName.valueOf("baz", 60010, random.nextLong());
298
299    long regionId = EnvironmentEdgeManager.currentTime();
300    RegionInfo primary = RegionInfoBuilder.newBuilder(TableName.valueOf(name.getMethodName()))
301      .setStartKey(HConstants.EMPTY_START_ROW).setEndKey(HConstants.EMPTY_END_ROW).setSplit(false)
302      .setRegionId(regionId).setReplicaId(0).build();
303    RegionInfo replica1 = RegionInfoBuilder.newBuilder(TableName.valueOf(name.getMethodName()))
304      .setStartKey(HConstants.EMPTY_START_ROW).setEndKey(HConstants.EMPTY_END_ROW).setSplit(false)
305      .setRegionId(regionId).setReplicaId(1).build();
306    RegionInfo replica100 = RegionInfoBuilder.newBuilder(TableName.valueOf(name.getMethodName()))
307      .setStartKey(HConstants.EMPTY_START_ROW).setEndKey(HConstants.EMPTY_END_ROW).setSplit(false)
308      .setRegionId(regionId).setReplicaId(100).build();
309
310    long seqNum0 = random.nextLong();
311    long seqNum1 = random.nextLong();
312    long seqNum100 = random.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() throws IOException {
374    long regionId = EnvironmentEdgeManager.currentTime();
375    RegionInfo primary = RegionInfoBuilder.newBuilder(TableName.valueOf(name.getMethodName()))
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() throws Exception {
393    LOG.info("Starting " + name.getMethodName());
394
395    final TableName tableName = TableName.valueOf(name.getMethodName());
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((Result) anyObject());
407
408    // Scanning the entire table should give us three rows
409    MetaTableAccessor.scanMetaForTableRegions(connection, visitor, tableName);
410    verify(visitor, times(3)).visit((Result) anyObject());
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((Result) anyObject());
416    MetaTableAccessor.scanMeta(connection, visitor, tableName, null, 1000);
417    verify(visitor, times(3)).visit((Result) anyObject());
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((Result) anyObject());
423    MetaTableAccessor.scanMeta(connection, visitor, tableName, Bytes.toBytes("region_ac"), 1000);
424    verify(visitor, times(2)).visit((Result) anyObject());
425
426    // Scanning with a limit of 1 should only give us one row
427    reset(visitor);
428    doReturn(true).when(visitor).visit((Result) anyObject());
429    MetaTableAccessor.scanMeta(connection, visitor, tableName, Bytes.toBytes("region_ac"), 1);
430    verify(visitor, times(1)).visit((Result) anyObject());
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() throws IOException {
439    long regionId = EnvironmentEdgeManager.currentTime();
440    RegionInfo regionInfo = RegionInfoBuilder.newBuilder(TableName.valueOf(name.getMethodName()))
441      .setStartKey(HConstants.EMPTY_START_ROW).setEndKey(HConstants.EMPTY_END_ROW).setSplit(false)
442      .setRegionId(regionId).setReplicaId(0).build();
443
444    ServerName sn = ServerName.valueOf("bar", 0, 0);
445    try (Table meta = MetaTableAccessor.getMetaHTable(connection)) {
446      List<RegionInfo> regionInfos = Lists.newArrayList(regionInfo);
447      MetaTableAccessor.addRegionsToMeta(connection, regionInfos, 1);
448
449      long masterSystemTime = EnvironmentEdgeManager.currentTime() + 123456789;
450      MetaTableAccessor.updateRegionLocation(connection, regionInfo, sn, 1, masterSystemTime);
451
452      Get get = new Get(regionInfo.getRegionName());
453      Result result = meta.get(get);
454      Cell serverCell = result.getColumnLatestCell(HConstants.CATALOG_FAMILY,
455        CatalogFamilyFormat.getServerColumn(0));
456      Cell startCodeCell = result.getColumnLatestCell(HConstants.CATALOG_FAMILY,
457        CatalogFamilyFormat.getStartCodeColumn(0));
458      Cell seqNumCell = result.getColumnLatestCell(HConstants.CATALOG_FAMILY,
459        CatalogFamilyFormat.getSeqNumColumn(0));
460      assertNotNull(serverCell);
461      assertNotNull(startCodeCell);
462      assertNotNull(seqNumCell);
463      assertTrue(serverCell.getValueLength() > 0);
464      assertTrue(startCodeCell.getValueLength() > 0);
465      assertTrue(seqNumCell.getValueLength() > 0);
466      assertEquals(masterSystemTime, serverCell.getTimestamp());
467      assertEquals(masterSystemTime, startCodeCell.getTimestamp());
468      assertEquals(masterSystemTime, seqNumCell.getTimestamp());
469    }
470  }
471
472  public static class SpyingRpcSchedulerFactory extends SimpleRpcSchedulerFactory {
473    @Override
474    public RpcScheduler create(Configuration conf, PriorityFunction priority, Abortable server) {
475      final RpcScheduler delegate = super.create(conf, priority, server);
476      return new SpyingRpcScheduler(delegate);
477    }
478  }
479
480  public static class SpyingRpcScheduler extends DelegatingRpcScheduler {
481    long numPriorityCalls = 0;
482
483    public SpyingRpcScheduler(RpcScheduler delegate) {
484      super(delegate);
485    }
486
487    @Override
488    public boolean dispatch(CallRunner task) throws IOException, InterruptedException {
489      int priority = task.getRpcCall().getPriority();
490
491      if (priority > HConstants.QOS_THRESHOLD) {
492        numPriorityCalls++;
493      }
494      return super.dispatch(task);
495    }
496  }
497
498  @Test
499  public void testScanByRegionEncodedNameExistingRegion() throws Exception {
500    final TableName tableName = TableName.valueOf("testScanByRegionEncodedNameExistingRegion");
501    UTIL.createTable(tableName, "cf");
502    final List<HRegion> regions = UTIL.getHBaseCluster().getRegions(tableName);
503    final String encodedName = regions.get(0).getRegionInfo().getEncodedName();
504    final Result result =
505      MetaTableAccessor.scanByRegionEncodedName(UTIL.getConnection(), encodedName);
506    assertNotNull(result);
507    assertTrue(result.advance());
508    final String resultingRowKey = CellUtil.getCellKeyAsString(result.current());
509    assertTrue(resultingRowKey.contains(encodedName));
510    UTIL.deleteTable(tableName);
511  }
512
513  @Test
514  public void testScanByRegionEncodedNameNonExistingRegion() throws Exception {
515    final String encodedName = "nonexistingregion";
516    final Result result =
517      MetaTableAccessor.scanByRegionEncodedName(UTIL.getConnection(), encodedName);
518    assertNull(result);
519  }
520}