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.replication.regionserver;
019
020import static org.apache.hadoop.hbase.client.RegionLocator.LOCATOR_META_REPLICAS_MODE;
021import static org.hamcrest.MatcherAssert.assertThat;
022import static org.hamcrest.Matchers.greaterThan;
023import static org.junit.jupiter.api.Assertions.assertArrayEquals;
024import static org.junit.jupiter.api.Assertions.assertEquals;
025import static org.junit.jupiter.api.Assertions.assertNotNull;
026
027import java.io.IOException;
028import java.util.ArrayList;
029import java.util.Arrays;
030import java.util.List;
031import java.util.concurrent.Callable;
032import org.apache.hadoop.conf.Configuration;
033import org.apache.hadoop.hbase.Cell;
034import org.apache.hadoop.hbase.CellScanner;
035import org.apache.hadoop.hbase.CellUtil;
036import org.apache.hadoop.hbase.ClientMetaTableAccessor;
037import org.apache.hadoop.hbase.HBaseTestingUtil;
038import org.apache.hadoop.hbase.HConstants;
039import org.apache.hadoop.hbase.MetaTableAccessor;
040import org.apache.hadoop.hbase.SingleProcessHBaseCluster;
041import org.apache.hadoop.hbase.TableName;
042import org.apache.hadoop.hbase.Waiter;
043import org.apache.hadoop.hbase.client.Connection;
044import org.apache.hadoop.hbase.client.ConnectionFactory;
045import org.apache.hadoop.hbase.client.Get;
046import org.apache.hadoop.hbase.client.RegionInfo;
047import org.apache.hadoop.hbase.client.RegionLocator;
048import org.apache.hadoop.hbase.client.Result;
049import org.apache.hadoop.hbase.client.Scan;
050import org.apache.hadoop.hbase.client.Table;
051import org.apache.hadoop.hbase.regionserver.HRegion;
052import org.apache.hadoop.hbase.regionserver.HRegionServer;
053import org.apache.hadoop.hbase.regionserver.Region;
054import org.apache.hadoop.hbase.regionserver.RegionScanner;
055import org.apache.hadoop.hbase.testclassification.LargeTests;
056import org.apache.hadoop.hbase.testclassification.ReplicationTests;
057import org.apache.hadoop.hbase.util.Bytes;
058import org.apache.hadoop.hbase.util.ServerRegionReplicaUtil;
059import org.junit.jupiter.api.AfterEach;
060import org.junit.jupiter.api.BeforeEach;
061import org.junit.jupiter.api.Tag;
062import org.junit.jupiter.api.Test;
063import org.junit.jupiter.api.TestInfo;
064import org.slf4j.Logger;
065import org.slf4j.LoggerFactory;
066
067/**
068 * Tests region replication for hbase:meta by setting up region replicas and verifying async wal
069 * replication replays the edits to the secondary region in various scenarios.
070 * @see TestRegionReplicaReplication
071 */
072@Tag(ReplicationTests.TAG)
073@Tag(LargeTests.TAG)
074public class TestMetaRegionReplicaReplication {
075
076  private static final Logger LOG = LoggerFactory.getLogger(TestMetaRegionReplicaReplication.class);
077  private static final int NB_SERVERS = 4;
078  private final HBaseTestingUtil HTU = new HBaseTestingUtil();
079  private int numOfMetaReplica = NB_SERVERS - 1;
080  private static byte[] VALUE = Bytes.toBytes("value");
081
082  private String testName;
083
084  @BeforeEach
085  public void before(TestInfo testInfo) throws Exception {
086    testName = testInfo.getTestMethod().get().getName();
087    Configuration conf = HTU.getConfiguration();
088    conf.setInt("zookeeper.recovery.retry", 1);
089    conf.setInt("zookeeper.recovery.retry.intervalmill", 10);
090    conf.setLong(HConstants.THREAD_WAKE_FREQUENCY, 100);
091    conf.setBoolean("hbase.tests.use.shortcircuit.reads", false);
092    conf.setInt(HConstants.HBASE_CLIENT_SERVERSIDE_RETRIES_MULTIPLIER, 1);
093    // Enable hbase:meta replication.
094    conf.setBoolean(ServerRegionReplicaUtil.REGION_REPLICA_REPLICATION_CATALOG_CONF_KEY, true);
095    // Set hbase:meta replicas to be 3.
096    // conf.setInt(HConstants.META_REPLICAS_NUM, numOfMetaReplica);
097    HTU.startMiniCluster(NB_SERVERS);
098    // Enable hbase:meta replication.
099    HBaseTestingUtil.setReplicas(HTU.getAdmin(), TableName.META_TABLE_NAME, numOfMetaReplica);
100
101    HTU.waitFor(30000, () -> HTU.getMiniHBaseCluster().getRegions(TableName.META_TABLE_NAME).size()
102        >= numOfMetaReplica);
103  }
104
105  @AfterEach
106  public void after() throws Exception {
107    HTU.shutdownMiniCluster();
108  }
109
110  /**
111   * Test meta region replica replication. Create some tables and see if replicas pick up the
112   * additions.
113   */
114  @Test
115  public void testHBaseMetaReplicates() throws Exception {
116    try (
117      Table table = HTU.createTable(TableName.valueOf(testName + "_0"), HConstants.CATALOG_FAMILY,
118        Arrays.copyOfRange(HBaseTestingUtil.KEYS, 1, HBaseTestingUtil.KEYS.length))) {
119      verifyReplication(TableName.META_TABLE_NAME, numOfMetaReplica, getMetaCells(table.getName()));
120    }
121    try (
122      Table table = HTU.createTable(TableName.valueOf(testName + "_1"), HConstants.CATALOG_FAMILY,
123        Arrays.copyOfRange(HBaseTestingUtil.KEYS, 1, HBaseTestingUtil.KEYS.length))) {
124      verifyReplication(TableName.META_TABLE_NAME, numOfMetaReplica, getMetaCells(table.getName()));
125      // Try delete.
126      HTU.deleteTableIfAny(table.getName());
127      verifyDeletedReplication(TableName.META_TABLE_NAME, numOfMetaReplica, table.getName());
128    }
129  }
130
131  @Test
132  public void testCatalogReplicaReplicationWithFlushAndCompaction() throws Exception {
133    try (Connection connection = ConnectionFactory.createConnection(HTU.getConfiguration());
134      Table table = connection.getTable(TableName.META_TABLE_NAME)) {
135      // load the data to the table
136      for (int i = 0; i < 5; i++) {
137        LOG.info("Writing data from " + i * 1000 + " to " + (i * 1000 + 1000));
138        HTU.loadNumericRows(table, HConstants.CATALOG_FAMILY, i * 1000, i * 1000 + 1000);
139        LOG.info("flushing table");
140        HTU.flush(TableName.META_TABLE_NAME);
141        LOG.info("compacting table");
142        if (i < 4) {
143          HTU.compact(TableName.META_TABLE_NAME, false);
144        }
145      }
146
147      verifyReplication(TableName.META_TABLE_NAME, numOfMetaReplica, 0, 5000,
148        HConstants.CATALOG_FAMILY);
149    }
150  }
151
152  @Test
153  public void testCatalogReplicaReplicationWithReplicaMoved() throws Exception {
154    SingleProcessHBaseCluster cluster = HTU.getMiniHBaseCluster();
155    HRegionServer hrs = cluster.getRegionServer(cluster.getServerHoldingMeta());
156
157    HRegionServer hrsNoMetaReplica = null;
158    HRegionServer server = null;
159    Region metaReplica = null;
160    boolean hostingMeta;
161
162    for (int i = 0; i < cluster.getNumLiveRegionServers(); i++) {
163      server = cluster.getRegionServer(i);
164      hostingMeta = false;
165      if (server == hrs) {
166        continue;
167      }
168      for (Region region : server.getOnlineRegionsLocalContext()) {
169        if (region.getRegionInfo().isMetaRegion()) {
170          if (metaReplica == null) {
171            metaReplica = region;
172          }
173          hostingMeta = true;
174          break;
175        }
176      }
177      if (!hostingMeta) {
178        hrsNoMetaReplica = server;
179      }
180    }
181    try (Connection connection = ConnectionFactory.createConnection(HTU.getConfiguration());
182      Table table = connection.getTable(TableName.META_TABLE_NAME)) {
183      // load the data to the table
184      for (int i = 0; i < 5; i++) {
185        LOG.info("Writing data from " + i * 1000 + " to " + (i * 1000 + 1000));
186        HTU.loadNumericRows(table, HConstants.CATALOG_FAMILY, i * 1000, i * 1000 + 1000);
187        if (i == 0) {
188          HTU.moveRegionAndWait(metaReplica.getRegionInfo(), hrsNoMetaReplica.getServerName());
189        }
190      }
191
192      verifyReplication(TableName.META_TABLE_NAME, numOfMetaReplica, 0, 5000,
193        HConstants.CATALOG_FAMILY);
194    }
195  }
196
197  protected void verifyReplication(TableName tableName, int regionReplication, final int startRow,
198    final int endRow, final byte[] family) throws Exception {
199    verifyReplication(tableName, regionReplication, startRow, endRow, family, true);
200  }
201
202  private void verifyReplication(TableName tableName, int regionReplication, final int startRow,
203    final int endRow, final byte[] family, final boolean present) throws Exception {
204    // find the regions
205    final Region[] regions = new Region[regionReplication];
206
207    for (int i = 0; i < NB_SERVERS; i++) {
208      HRegionServer rs = HTU.getMiniHBaseCluster().getRegionServer(i);
209      List<HRegion> onlineRegions = rs.getRegions(tableName);
210      for (HRegion region : onlineRegions) {
211        regions[region.getRegionInfo().getReplicaId()] = region;
212      }
213    }
214
215    for (Region region : regions) {
216      assertNotNull(region);
217    }
218
219    for (int i = 1; i < regionReplication; i++) {
220      final Region region = regions[i];
221      // wait until all the data is replicated to all secondary regions
222      Waiter.waitFor(HTU.getConfiguration(), 90000, 1000, new Waiter.Predicate<Exception>() {
223        @Override
224        public boolean evaluate() throws Exception {
225          LOG.info("verifying replication for region replica:" + region.getRegionInfo());
226          try {
227            HTU.verifyNumericRows(region, family, startRow, endRow, present);
228          } catch (Throwable ex) {
229            LOG.warn("Verification from secondary region is not complete yet", ex);
230            // still wait
231            return false;
232          }
233          return true;
234        }
235      });
236    }
237  }
238
239  /**
240   * Scan hbase:meta for <code>tableName</code> content.
241   */
242  private List<Result> getMetaCells(TableName tableName) throws IOException {
243    final List<Result> results = new ArrayList<>();
244    ClientMetaTableAccessor.Visitor visitor = new ClientMetaTableAccessor.Visitor() {
245      @Override
246      public boolean visit(Result r) throws IOException {
247        results.add(r);
248        return true;
249      }
250    };
251    MetaTableAccessor.scanMetaForTableRegions(HTU.getConnection(), visitor, tableName);
252    return results;
253  }
254
255  /** Returns All Regions for tableName including Replicas. */
256  private Region[] getAllRegions(TableName tableName, int replication) {
257    final Region[] regions = new Region[replication];
258    for (int i = 0; i < NB_SERVERS; i++) {
259      HRegionServer rs = HTU.getMiniHBaseCluster().getRegionServer(i);
260      List<HRegion> onlineRegions = rs.getRegions(tableName);
261      for (HRegion region : onlineRegions) {
262        regions[region.getRegionInfo().getReplicaId()] = region;
263      }
264    }
265    for (Region region : regions) {
266      assertNotNull(region);
267    }
268    return regions;
269  }
270
271  /**
272   * Verify when a Table is deleted from primary, then there are no references in replicas (because
273   * they get the delete of the table rows too).
274   */
275  private void verifyDeletedReplication(TableName tableName, int regionReplication,
276    final TableName deletedTableName) {
277    final Region[] regions = getAllRegions(tableName, regionReplication);
278
279    // Start count at '1' so we skip default, primary replica and only look at secondaries.
280    for (int i = 1; i < regionReplication; i++) {
281      final Region region = regions[i];
282      // wait until all the data is replicated to all secondary regions
283      Waiter.waitFor(HTU.getConfiguration(), 30000, 1000, new Waiter.Predicate<Exception>() {
284        @Override
285        public boolean evaluate() throws Exception {
286          LOG.info("Verifying replication for region replica {}", region.getRegionInfo());
287          try (RegionScanner rs = region.getScanner(new Scan())) {
288            List<Cell> cells = new ArrayList<>();
289            while (rs.next(cells)) {
290              continue;
291            }
292            return doesNotContain(cells, deletedTableName);
293          } catch (Throwable ex) {
294            LOG.warn("Verification from secondary region is not complete yet", ex);
295            // still wait
296            return false;
297          }
298        }
299      });
300    }
301  }
302
303  /**
304   * Cells are from hbase:meta replica so will start w/ 'tableName,'; i.e. the tablename followed by
305   * HConstants.DELIMITER. Make sure the deleted table is no longer present in passed
306   * <code>cells</code>.
307   */
308  private boolean doesNotContain(List<Cell> cells, TableName tableName) {
309    for (Cell cell : cells) {
310      String row = Bytes.toString(cell.getRowArray(), cell.getRowOffset(), cell.getRowLength());
311      if (row.startsWith(tableName.toString() + HConstants.DELIMITER)) {
312        return false;
313      }
314    }
315    return true;
316  }
317
318  /**
319   * Verify Replicas have results (exactly).
320   */
321  private void verifyReplication(TableName tableName, int regionReplication,
322    List<Result> contains) {
323    final Region[] regions = getAllRegions(tableName, regionReplication);
324
325    // Start count at '1' so we skip default, primary replica and only look at secondaries.
326    for (int i = 1; i < regionReplication; i++) {
327      final Region region = regions[i];
328      // wait until all the data is replicated to all secondary regions
329      Waiter.waitFor(HTU.getConfiguration(), 30000, 1000, new Waiter.Predicate<Exception>() {
330        @Override
331        public boolean evaluate() throws Exception {
332          LOG.info("Verifying replication for region replica {}", region.getRegionInfo());
333          try (RegionScanner rs = region.getScanner(new Scan())) {
334            List<Cell> cells = new ArrayList<>();
335            while (rs.next(cells)) {
336              continue;
337            }
338            return contains(contains, cells);
339          } catch (Throwable ex) {
340            LOG.warn("Verification from secondary region is not complete yet", ex);
341            // still wait
342            return false;
343          }
344        }
345      });
346    }
347  }
348
349  /**
350   * Presumes sorted Cells. Verify that <code>cells</code> has <code>contains</code> at least.
351   */
352  static boolean contains(List<Result> contains, List<Cell> cells) throws IOException {
353    CellScanner containsScanner = CellUtil.createCellScanner(contains);
354    CellScanner cellsScanner = CellUtil.createCellScanner(cells);
355    int matches = 0;
356    int count = 0;
357    while (containsScanner.advance()) {
358      while (cellsScanner.advance()) {
359        count++;
360        LOG.info("{} {}", containsScanner.current(), cellsScanner.current());
361        if (containsScanner.current().equals(cellsScanner.current())) {
362          matches++;
363          break;
364        }
365      }
366    }
367    return !containsScanner.advance() && matches >= 1 && count >= matches && count == cells.size();
368  }
369
370  private void doNGets(final Table table, final byte[][] keys) throws Exception {
371    for (byte[] key : keys) {
372      Result r = table.get(new Get(key));
373      assertArrayEquals(VALUE, r.getValue(HConstants.CATALOG_FAMILY, HConstants.CATALOG_FAMILY));
374    }
375  }
376
377  private void primaryIncreaseReplicaNoChange(final long[] before, final long[] after) {
378    // There are read requests increase for primary meta replica.
379    assertThat(after[RegionInfo.DEFAULT_REPLICA_ID],
380      greaterThan(before[RegionInfo.DEFAULT_REPLICA_ID]));
381
382    // No change for replica regions
383    for (int i = 1; i < after.length; i++) {
384      assertEquals(before[i], after[i]);
385    }
386  }
387
388  private void primaryIncreaseReplicaIncrease(final long[] before, final long[] after) {
389    // There are read requests increase for all meta replica regions,
390    for (int i = 0; i < after.length; i++) {
391      assertThat(after[i], greaterThan(before[i]));
392    }
393  }
394
395  private void getMetaReplicaReadRequests(final Region[] metaRegions, final long[] counters) {
396    int i = 0;
397    for (Region r : metaRegions) {
398      LOG.info("read request for region {} is {}", r, r.getReadRequestsCount());
399      counters[i] = r.getReadRequestsCount();
400      i++;
401    }
402  }
403
404  private void runAtMostNTimes(int times, Callable<?> action, Runnable assertion) throws Exception {
405    for (int i = 0; i < times - 1; i++) {
406      action.call();
407      try {
408        assertion.run();
409        // return if the assertion passes, otherwise try again
410        return;
411      } catch (AssertionError e) {
412        LOG.warn("Assertion failed, times = {}, try again", i, e);
413      }
414    }
415    // try last time
416    action.call();
417    assertion.run();
418  }
419
420  @Test
421  public void testHBaseMetaReplicaGets() throws Exception {
422    TableName tn = TableName.valueOf(testName);
423    final Region[] metaRegions = getAllRegions(TableName.META_TABLE_NAME, numOfMetaReplica);
424    long[] readReqsForMetaReplicas = new long[numOfMetaReplica];
425    long[] readReqsForMetaReplicasAfterGet = new long[numOfMetaReplica];
426    long[] readReqsForMetaReplicasAfterGetAllLocations = new long[numOfMetaReplica];
427    long[] readReqsForMetaReplicasAfterMove = new long[numOfMetaReplica];
428    long[] readReqsForMetaReplicasAfterSecondMove = new long[numOfMetaReplica];
429    long[] readReqsForMetaReplicasAfterThirdGet = new long[numOfMetaReplica];
430    Region userRegion = null;
431    HRegionServer srcRs = null;
432    HRegionServer destRs = null;
433
434    try (Table table = HTU.createTable(tn, HConstants.CATALOG_FAMILY,
435      Arrays.copyOfRange(HBaseTestingUtil.KEYS, 1, HBaseTestingUtil.KEYS.length))) {
436      verifyReplication(TableName.META_TABLE_NAME, numOfMetaReplica, getMetaCells(table.getName()));
437      // load different values
438      HTU.loadTable(table, new byte[][] { HConstants.CATALOG_FAMILY }, VALUE);
439      for (int i = 0; i < NB_SERVERS; i++) {
440        HRegionServer rs = HTU.getMiniHBaseCluster().getRegionServer(i);
441        List<HRegion> onlineRegions = rs.getRegions(tn);
442        if (onlineRegions.size() > 0) {
443          userRegion = onlineRegions.get(0);
444          srcRs = rs;
445          if (i > 0) {
446            destRs = HTU.getMiniHBaseCluster().getRegionServer(0);
447          } else {
448            destRs = HTU.getMiniHBaseCluster().getRegionServer(1);
449          }
450        }
451      }
452    }
453
454    getMetaReplicaReadRequests(metaRegions, readReqsForMetaReplicas);
455
456    Configuration c = new Configuration(HTU.getConfiguration());
457    c.setBoolean(HConstants.USE_META_REPLICAS, true);
458    c.set(LOCATOR_META_REPLICAS_MODE, "LoadBalance");
459    try (Connection connection = ConnectionFactory.createConnection(c);
460      Table tableForGet = connection.getTable(tn);
461      RegionLocator locator = tableForGet.getRegionLocator()) {
462      byte[][] getRows = new byte[HBaseTestingUtil.KEYS.length][];
463      for (int i = 1; i < HBaseTestingUtil.KEYS.length; i++) {
464        getRows[i] = HBaseTestingUtil.KEYS[i];
465      }
466      getRows[0] = Bytes.toBytes("aaa");
467
468      // There are more reads against all meta replica regions, including the primary region.
469      // Since in load balance mode, we will randomly select region replicas, it is possible that we
470      // missed read some replicas, so here we run it at most 3 times to make it more stable
471      runAtMostNTimes(3, () -> {
472        locator.clearRegionLocationCache();
473        doNGets(tableForGet, getRows);
474        getMetaReplicaReadRequests(metaRegions, readReqsForMetaReplicasAfterGet);
475        return null;
476      }, () -> primaryIncreaseReplicaIncrease(readReqsForMetaReplicas,
477        readReqsForMetaReplicasAfterGet));
478
479      // Same as above, we need to run it multiple times to make it stable. And numOfMetaReplicas is
480      // much less than HBaseTestingUtil.KEYS.length, so here we need to run more times.
481      runAtMostNTimes(30, () -> {
482        for (int i = 0; i < numOfMetaReplica; i++) {
483          locator.getAllRegionLocations();
484        }
485        getMetaReplicaReadRequests(metaRegions, readReqsForMetaReplicasAfterGetAllLocations);
486        return null;
487      }, () -> primaryIncreaseReplicaIncrease(readReqsForMetaReplicasAfterGet,
488        readReqsForMetaReplicasAfterGetAllLocations));
489
490      // move one of regions so it meta cache may be invalid.
491      HTU.moveRegionAndWait(userRegion.getRegionInfo(), destRs.getServerName());
492
493      doNGets(tableForGet, getRows);
494
495      getMetaReplicaReadRequests(metaRegions, readReqsForMetaReplicasAfterMove);
496
497      // There are read requests increase for primary meta replica.
498      // For rest of meta replicas, there is no change as regionMove will tell the new location
499      primaryIncreaseReplicaNoChange(readReqsForMetaReplicasAfterGetAllLocations,
500        readReqsForMetaReplicasAfterMove);
501      // Move region again.
502      HTU.moveRegionAndWait(userRegion.getRegionInfo(), srcRs.getServerName());
503
504      // Remove it from the move region cache
505      destRs.removeFromMovedRegions(userRegion.getRegionInfo().getEncodedName());
506
507      getMetaReplicaReadRequests(metaRegions, readReqsForMetaReplicasAfterSecondMove);
508
509      // There are read requests increase for primary meta replica.
510      // For rest of meta replicas, there is no change.
511      primaryIncreaseReplicaNoChange(readReqsForMetaReplicasAfterMove,
512        readReqsForMetaReplicasAfterSecondMove);
513
514      doNGets(tableForGet, getRows);
515
516      getMetaReplicaReadRequests(metaRegions, readReqsForMetaReplicasAfterThirdGet);
517
518      // Since it gets RegionNotServedException, it will go to primary for the next lookup.
519      // There are read requests increase for primary meta replica.
520      // For rest of meta replicas, there is no change.
521      primaryIncreaseReplicaNoChange(readReqsForMetaReplicasAfterSecondMove,
522        readReqsForMetaReplicasAfterThirdGet);
523    }
524  }
525}