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.junit.Assert.assertFalse;
021import static org.junit.Assert.assertTrue;
022import static org.mockito.ArgumentMatchers.any;
023import static org.mockito.Mockito.doAnswer;
024import static org.mockito.Mockito.mock;
025import static org.mockito.Mockito.times;
026import static org.mockito.Mockito.verify;
027import static org.mockito.Mockito.when;
028
029import java.io.IOException;
030import java.util.Arrays;
031import java.util.List;
032import org.apache.hadoop.hbase.Cell;
033import org.apache.hadoop.hbase.Cell.Type;
034import org.apache.hadoop.hbase.CellBuilderFactory;
035import org.apache.hadoop.hbase.CellBuilderType;
036import org.apache.hadoop.hbase.HBaseClassTestRule;
037import org.apache.hadoop.hbase.HBaseTestingUtility;
038import org.apache.hadoop.hbase.HConstants;
039import org.apache.hadoop.hbase.MetaTableAccessor;
040import org.apache.hadoop.hbase.Server;
041import org.apache.hadoop.hbase.TableName;
042import org.apache.hadoop.hbase.client.Connection;
043import org.apache.hadoop.hbase.client.Put;
044import org.apache.hadoop.hbase.client.RegionInfo;
045import org.apache.hadoop.hbase.client.RegionInfoBuilder;
046import org.apache.hadoop.hbase.client.Table;
047import org.apache.hadoop.hbase.master.RegionState;
048import org.apache.hadoop.hbase.replication.ReplicationException;
049import org.apache.hadoop.hbase.replication.ReplicationQueueStorage;
050import org.apache.hadoop.hbase.replication.ReplicationStorageFactory;
051import org.apache.hadoop.hbase.testclassification.MediumTests;
052import org.apache.hadoop.hbase.testclassification.ReplicationTests;
053import org.apache.hadoop.hbase.util.Bytes;
054import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
055import org.apache.hadoop.hbase.wal.WAL.Entry;
056import org.apache.hadoop.hbase.wal.WALKeyImpl;
057import org.junit.AfterClass;
058import org.junit.Before;
059import org.junit.BeforeClass;
060import org.junit.ClassRule;
061import org.junit.Rule;
062import org.junit.Test;
063import org.junit.experimental.categories.Category;
064import org.junit.rules.TestName;
065import org.mockito.invocation.InvocationOnMock;
066import org.mockito.stubbing.Answer;
067
068import org.apache.hbase.thirdparty.com.google.common.collect.ImmutableMap;
069
070@Category({ ReplicationTests.class, MediumTests.class })
071public class TestSerialReplicationChecker {
072
073  @ClassRule
074  public static final HBaseClassTestRule CLASS_RULE =
075    HBaseClassTestRule.forClass(TestSerialReplicationChecker.class);
076
077  private static final HBaseTestingUtility UTIL = new HBaseTestingUtility();
078
079  private static String PEER_ID = "1";
080
081  private static ReplicationQueueStorage QUEUE_STORAGE;
082
083  private static String WAL_FILE_NAME = "test.wal";
084
085  private Connection conn;
086
087  private SerialReplicationChecker checker;
088
089  @Rule
090  public final TestName name = new TestName();
091
092  private TableName tableName;
093
094  @BeforeClass
095  public static void setUpBeforeClass() throws Exception {
096    UTIL.startMiniCluster(1);
097    QUEUE_STORAGE = ReplicationStorageFactory.getReplicationQueueStorage(UTIL.getZooKeeperWatcher(),
098      UTIL.getConfiguration());
099    QUEUE_STORAGE.addWAL(UTIL.getMiniHBaseCluster().getRegionServer(0).getServerName(), PEER_ID,
100      WAL_FILE_NAME);
101  }
102
103  @AfterClass
104  public static void tearDownAfterClass() throws Exception {
105    UTIL.shutdownMiniCluster();
106  }
107
108  @Before
109  public void setUp() throws IOException {
110    ReplicationSource source = mock(ReplicationSource.class);
111    when(source.getPeerId()).thenReturn(PEER_ID);
112    when(source.getReplicationQueueStorage()).thenReturn(QUEUE_STORAGE);
113    conn = mock(Connection.class);
114    when(conn.isClosed()).thenReturn(false);
115    doAnswer(new Answer<Table>() {
116
117      @Override
118      public Table answer(InvocationOnMock invocation) throws Throwable {
119        return UTIL.getConnection().getTable((TableName) invocation.getArgument(0));
120      }
121
122    }).when(conn).getTable(any(TableName.class));
123    Server server = mock(Server.class);
124    when(server.getConnection()).thenReturn(conn);
125    when(source.getServer()).thenReturn(server);
126    checker = new SerialReplicationChecker(UTIL.getConfiguration(), source);
127    tableName = TableName.valueOf(name.getMethodName());
128  }
129
130  private Entry createEntry(RegionInfo region, long seqId) {
131    WALKeyImpl key = mock(WALKeyImpl.class);
132    when(key.getTableName()).thenReturn(tableName);
133    when(key.getEncodedRegionName()).thenReturn(region.getEncodedNameAsBytes());
134    when(key.getSequenceId()).thenReturn(seqId);
135    Entry entry = mock(Entry.class);
136    when(entry.getKey()).thenReturn(key);
137    return entry;
138  }
139
140  private Cell createCell(RegionInfo region) {
141    return CellBuilderFactory.create(CellBuilderType.DEEP_COPY).setRow(region.getStartKey())
142      .setType(Type.Put).build();
143  }
144
145  @Test
146  public void testNoBarrierCanPush() throws IOException {
147    RegionInfo region = RegionInfoBuilder.newBuilder(tableName).build();
148    assertTrue(checker.canPush(createEntry(region, 100), createCell(region)));
149  }
150
151  private void addStateAndBarrier(RegionInfo region, RegionState.State state, long... barriers)
152    throws IOException {
153    Put put = new Put(region.getRegionName(), EnvironmentEdgeManager.currentTime());
154    if (state != null) {
155      put.addColumn(HConstants.CATALOG_FAMILY, HConstants.STATE_QUALIFIER,
156        Bytes.toBytes(state.name()));
157    }
158    for (int i = 0; i < barriers.length; i++) {
159      put.addColumn(HConstants.REPLICATION_BARRIER_FAMILY, HConstants.SEQNUM_QUALIFIER,
160        put.getTimeStamp() - barriers.length + i, Bytes.toBytes(barriers[i]));
161    }
162    try (Table table = UTIL.getConnection().getTable(TableName.META_TABLE_NAME)) {
163      table.put(put);
164    }
165  }
166
167  private void setState(RegionInfo region, RegionState.State state) throws IOException {
168    Put put = new Put(region.getRegionName(), EnvironmentEdgeManager.currentTime());
169    put.addColumn(HConstants.CATALOG_FAMILY, HConstants.STATE_QUALIFIER,
170      Bytes.toBytes(state.name()));
171    try (Table table = UTIL.getConnection().getTable(TableName.META_TABLE_NAME)) {
172      table.put(put);
173    }
174  }
175
176  private void updatePushedSeqId(RegionInfo region, long seqId) throws ReplicationException {
177    QUEUE_STORAGE.setWALPosition(UTIL.getMiniHBaseCluster().getRegionServer(0).getServerName(),
178      PEER_ID, WAL_FILE_NAME, 10, ImmutableMap.of(region.getEncodedName(), seqId));
179  }
180
181  private void addParents(RegionInfo region, List<RegionInfo> parents) throws IOException {
182    Put put = new Put(region.getRegionName(), EnvironmentEdgeManager.currentTime());
183    put.addColumn(HConstants.REPLICATION_BARRIER_FAMILY,
184      MetaTableAccessor.REPLICATION_PARENT_QUALIFIER, MetaTableAccessor.getParentsBytes(parents));
185    try (Table table = UTIL.getConnection().getTable(TableName.META_TABLE_NAME)) {
186      table.put(put);
187    }
188  }
189
190  @Test
191  public void testLastRegionAndOpeningCanNotPush() throws IOException, ReplicationException {
192    RegionInfo region = RegionInfoBuilder.newBuilder(tableName).build();
193    addStateAndBarrier(region, RegionState.State.OPEN, 10);
194    Cell cell = createCell(region);
195    // can push since we are in the first range
196    assertTrue(checker.canPush(createEntry(region, 100), cell));
197    setState(region, RegionState.State.OPENING);
198    // can not push since we are in the last range and the state is OPENING
199    assertFalse(checker.canPush(createEntry(region, 102), cell));
200    addStateAndBarrier(region, RegionState.State.OPEN, 50);
201    // can not push since the previous range has not been finished yet
202    assertFalse(checker.canPush(createEntry(region, 102), cell));
203    updatePushedSeqId(region, 49);
204    // can push since the previous range has been finished
205    assertTrue(checker.canPush(createEntry(region, 102), cell));
206    setState(region, RegionState.State.OPENING);
207    assertFalse(checker.canPush(createEntry(region, 104), cell));
208  }
209
210  @Test
211  public void testCanPushUnder() throws IOException, ReplicationException {
212    RegionInfo region = RegionInfoBuilder.newBuilder(tableName).build();
213    addStateAndBarrier(region, RegionState.State.OPEN, 10, 100);
214    updatePushedSeqId(region, 9);
215    Cell cell = createCell(region);
216    assertTrue(checker.canPush(createEntry(region, 20), cell));
217    verify(conn, times(1)).getTable(any(TableName.class));
218    // not continuous
219    for (int i = 22; i < 100; i += 2) {
220      assertTrue(checker.canPush(createEntry(region, i), cell));
221    }
222    // verify that we do not go to meta table
223    verify(conn, times(1)).getTable(any(TableName.class));
224  }
225
226  @Test
227  public void testCanPushIfContinuous() throws IOException, ReplicationException {
228    RegionInfo region = RegionInfoBuilder.newBuilder(tableName).build();
229    addStateAndBarrier(region, RegionState.State.OPEN, 10);
230    updatePushedSeqId(region, 9);
231    Cell cell = createCell(region);
232    assertTrue(checker.canPush(createEntry(region, 20), cell));
233    verify(conn, times(1)).getTable(any(TableName.class));
234    // continuous
235    for (int i = 21; i < 100; i++) {
236      assertTrue(checker.canPush(createEntry(region, i), cell));
237    }
238    // verify that we do not go to meta table
239    verify(conn, times(1)).getTable(any(TableName.class));
240  }
241
242  @Test
243  public void testCanPushAfterMerge() throws IOException, ReplicationException {
244    // 0xFF is the escape byte when storing region name so let's make sure it can work.
245    byte[] endKey = new byte[] { (byte) 0xFF, 0x00, (byte) 0xFF, (byte) 0xFF, 0x01 };
246    RegionInfo regionA =
247      RegionInfoBuilder.newBuilder(tableName).setEndKey(endKey).setRegionId(1).build();
248    RegionInfo regionB =
249      RegionInfoBuilder.newBuilder(tableName).setStartKey(endKey).setRegionId(2).build();
250    RegionInfo region = RegionInfoBuilder.newBuilder(tableName).setRegionId(3).build();
251    addStateAndBarrier(regionA, null, 10, 100);
252    addStateAndBarrier(regionB, null, 20, 200);
253    addStateAndBarrier(region, RegionState.State.OPEN, 200);
254    addParents(region, Arrays.asList(regionA, regionB));
255    Cell cell = createCell(region);
256    // can not push since both parents have not been finished yet
257    assertFalse(checker.canPush(createEntry(region, 300), cell));
258    updatePushedSeqId(regionB, 199);
259    // can not push since regionA has not been finished yet
260    assertFalse(checker.canPush(createEntry(region, 300), cell));
261    updatePushedSeqId(regionA, 99);
262    // can push since all parents have been finished
263    assertTrue(checker.canPush(createEntry(region, 300), cell));
264  }
265
266  @Test
267  public void testCanPushAfterSplit() throws IOException, ReplicationException {
268    // 0xFF is the escape byte when storing region name so let's make sure it can work.
269    byte[] endKey = new byte[] { (byte) 0xFF, 0x00, (byte) 0xFF, (byte) 0xFF, 0x01 };
270    RegionInfo region = RegionInfoBuilder.newBuilder(tableName).setRegionId(1).build();
271    RegionInfo regionA =
272      RegionInfoBuilder.newBuilder(tableName).setEndKey(endKey).setRegionId(2).build();
273    RegionInfo regionB =
274      RegionInfoBuilder.newBuilder(tableName).setStartKey(endKey).setRegionId(3).build();
275    addStateAndBarrier(region, null, 10, 100);
276    addStateAndBarrier(regionA, RegionState.State.OPEN, 100, 200);
277    addStateAndBarrier(regionB, RegionState.State.OPEN, 100, 300);
278    addParents(regionA, Arrays.asList(region));
279    addParents(regionB, Arrays.asList(region));
280    Cell cellA = createCell(regionA);
281    Cell cellB = createCell(regionB);
282    // can not push since parent has not been finished yet
283    assertFalse(checker.canPush(createEntry(regionA, 150), cellA));
284    assertFalse(checker.canPush(createEntry(regionB, 200), cellB));
285    updatePushedSeqId(region, 99);
286    // can push since parent has been finished
287    assertTrue(checker.canPush(createEntry(regionA, 150), cellA));
288    assertTrue(checker.canPush(createEntry(regionB, 200), cellB));
289  }
290
291  @Test
292  public void testCanPushEqualsToBarrier() throws IOException, ReplicationException {
293    // For binary search, equals to an element will result to a positive value, let's test whether
294    // it works.
295    RegionInfo region = RegionInfoBuilder.newBuilder(tableName).build();
296    addStateAndBarrier(region, RegionState.State.OPEN, 10, 100);
297    Cell cell = createCell(region);
298    assertTrue(checker.canPush(createEntry(region, 10), cell));
299    assertFalse(checker.canPush(createEntry(region, 100), cell));
300    updatePushedSeqId(region, 99);
301    assertTrue(checker.canPush(createEntry(region, 100), cell));
302  }
303}