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.assertEquals;
021
022import java.io.IOException;
023import java.util.List;
024import java.util.concurrent.Callable;
025import java.util.concurrent.atomic.AtomicBoolean;
026import java.util.concurrent.atomic.AtomicInteger;
027import org.apache.hadoop.hbase.HBaseClassTestRule;
028import org.apache.hadoop.hbase.HBaseTestingUtility;
029import org.apache.hadoop.hbase.TableName;
030import org.apache.hadoop.hbase.Waiter;
031import org.apache.hadoop.hbase.client.HBaseAdmin;
032import org.apache.hadoop.hbase.client.Put;
033import org.apache.hadoop.hbase.ipc.RpcServer;
034import org.apache.hadoop.hbase.replication.ReplicationPeerConfig;
035import org.apache.hadoop.hbase.replication.TestReplicationBase;
036import org.apache.hadoop.hbase.testclassification.MediumTests;
037import org.apache.hadoop.hbase.util.Bytes;
038import org.apache.hadoop.hbase.wal.WAL.Entry;
039import org.junit.AfterClass;
040import org.junit.BeforeClass;
041import org.junit.ClassRule;
042import org.junit.Test;
043import org.junit.experimental.categories.Category;
044import org.slf4j.Logger;
045import org.slf4j.LoggerFactory;
046
047import org.apache.hbase.thirdparty.com.google.protobuf.ServiceException;
048
049@Category(MediumTests.class)
050public class TestReplicator extends TestReplicationBase {
051
052  @ClassRule
053  public static final HBaseClassTestRule CLASS_RULE =
054      HBaseClassTestRule.forClass(TestReplicator.class);
055
056  static final Logger LOG = LoggerFactory.getLogger(TestReplicator.class);
057  static final int NUM_ROWS = 10;
058
059  @BeforeClass
060  public static void setUpBeforeClass() throws Exception {
061    // Set RPC size limit to 10kb (will be applied to both source and sink clusters)
062    CONF1.setInt(RpcServer.MAX_REQUEST_SIZE, 1024 * 10);
063    TestReplicationBase.setUpBeforeClass();
064  }
065
066  @Test
067  public void testReplicatorBatching() throws Exception {
068    // Clear the tables
069    truncateTable(UTIL1, tableName);
070    truncateTable(UTIL2, tableName);
071
072    // Replace the peer set up for us by the base class with a wrapper for this test
073    admin.addPeer("testReplicatorBatching",
074      new ReplicationPeerConfig().setClusterKey(UTIL2.getClusterKey())
075          .setReplicationEndpointImpl(ReplicationEndpointForTest.class.getName()),
076      null);
077
078    ReplicationEndpointForTest.setBatchCount(0);
079    ReplicationEndpointForTest.setEntriesCount(0);
080    try {
081      ReplicationEndpointForTest.pause();
082      try {
083        // Queue up a bunch of cells of size 8K. Because of RPC size limits, they will all
084        // have to be replicated separately.
085        final byte[] valueBytes = new byte[8 * 1024];
086        for (int i = 0; i < NUM_ROWS; i++) {
087          htable1.put(new Put(Bytes.toBytes("row" + Integer.toString(i))).addColumn(famName, null,
088            valueBytes));
089        }
090      } finally {
091        ReplicationEndpointForTest.resume();
092      }
093
094      // Wait for replication to complete.
095      Waiter.waitFor(CONF1, 60000, new Waiter.ExplainingPredicate<Exception>() {
096        @Override
097        public boolean evaluate() throws Exception {
098          LOG.info("Count=" + ReplicationEndpointForTest.getBatchCount());
099          return ReplicationEndpointForTest.getBatchCount() >= NUM_ROWS;
100        }
101
102        @Override
103        public String explainFailure() throws Exception {
104          return "We waited too long for expected replication of " + NUM_ROWS + " entries";
105        }
106      });
107
108      assertEquals("We sent an incorrect number of batches", NUM_ROWS,
109        ReplicationEndpointForTest.getBatchCount());
110      assertEquals("We did not replicate enough rows", NUM_ROWS, UTIL2.countRows(htable2));
111    } finally {
112      admin.removePeer("testReplicatorBatching");
113    }
114  }
115
116  @Test
117  public void testReplicatorWithErrors() throws Exception {
118    // Clear the tables
119    truncateTable(UTIL1, tableName);
120    truncateTable(UTIL2, tableName);
121
122    // Replace the peer set up for us by the base class with a wrapper for this test
123    admin.addPeer("testReplicatorWithErrors",
124      new ReplicationPeerConfig().setClusterKey(UTIL2.getClusterKey())
125          .setReplicationEndpointImpl(FailureInjectingReplicationEndpointForTest.class.getName()),
126      null);
127
128    FailureInjectingReplicationEndpointForTest.setBatchCount(0);
129    FailureInjectingReplicationEndpointForTest.setEntriesCount(0);
130    try {
131      FailureInjectingReplicationEndpointForTest.pause();
132      try {
133        // Queue up a bunch of cells of size 8K. Because of RPC size limits, they will all
134        // have to be replicated separately.
135        final byte[] valueBytes = new byte[8 * 1024];
136        for (int i = 0; i < NUM_ROWS; i++) {
137          htable1.put(new Put(Bytes.toBytes("row" + Integer.toString(i))).addColumn(famName, null,
138            valueBytes));
139        }
140      } finally {
141        FailureInjectingReplicationEndpointForTest.resume();
142      }
143
144      // Wait for replication to complete.
145      // We can expect 10 batches
146      Waiter.waitFor(CONF1, 60000, new Waiter.ExplainingPredicate<Exception>() {
147        @Override
148        public boolean evaluate() throws Exception {
149          return FailureInjectingReplicationEndpointForTest.getEntriesCount() >= NUM_ROWS;
150        }
151
152        @Override
153        public String explainFailure() throws Exception {
154          return "We waited too long for expected replication of " + NUM_ROWS + " entries";
155        }
156      });
157
158      assertEquals("We did not replicate enough rows", NUM_ROWS, UTIL2.countRows(htable2));
159    } finally {
160      admin.removePeer("testReplicatorWithErrors");
161    }
162  }
163
164  @AfterClass
165  public static void tearDownAfterClass() throws Exception {
166    TestReplicationBase.tearDownAfterClass();
167  }
168
169  private void truncateTable(HBaseTestingUtility util, TableName tablename) throws IOException {
170    HBaseAdmin admin = util.getHBaseAdmin();
171    admin.disableTable(tableName);
172    admin.truncateTable(tablename, false);
173  }
174
175  public static class ReplicationEndpointForTest extends HBaseInterClusterReplicationEndpoint {
176
177    protected static AtomicInteger batchCount = new AtomicInteger(0);
178    protected static int entriesCount;
179    private static final Object latch = new Object();
180    private static AtomicBoolean useLatch = new AtomicBoolean(false);
181
182    public static void resume() {
183      useLatch.set(false);
184      synchronized (latch) {
185        latch.notifyAll();
186      }
187    }
188
189    public static void pause() {
190      useLatch.set(true);
191    }
192
193    public static void await() throws InterruptedException {
194      if (useLatch.get()) {
195        LOG.info("Waiting on latch");
196        synchronized (latch) {
197          latch.wait();
198        }
199        LOG.info("Waited on latch, now proceeding");
200      }
201    }
202
203    public static int getBatchCount() {
204      return batchCount.get();
205    }
206
207    public static void setBatchCount(int i) {
208      LOG.info("SetBatchCount=" + i + ", old=" + getBatchCount());
209      batchCount.set(i);
210    }
211
212    public static int getEntriesCount() {
213      return entriesCount;
214    }
215
216    public static void setEntriesCount(int i) {
217      LOG.info("SetEntriesCount=" + i);
218      entriesCount = i;
219    }
220
221    @Override
222    public boolean replicate(ReplicateContext replicateContext) {
223      try {
224        await();
225      } catch (InterruptedException e) {
226        LOG.warn("Interrupted waiting for latch", e);
227      }
228      return super.replicate(replicateContext);
229    }
230
231    @Override
232    protected Callable<Integer> createReplicator(List<Entry> entries, int ordinal, int timeout) {
233      return () -> {
234        int batchIndex = replicateEntries(entries, ordinal, timeout);
235        entriesCount += entries.size();
236        int count = batchCount.incrementAndGet();
237        LOG.info(
238          "Completed replicating batch " + System.identityHashCode(entries) + " count=" + count);
239        return batchIndex;
240      };
241    }
242  }
243
244  public static class FailureInjectingReplicationEndpointForTest
245      extends ReplicationEndpointForTest {
246    private final AtomicBoolean failNext = new AtomicBoolean(false);
247
248    @Override
249    protected Callable<Integer> createReplicator(List<Entry> entries, int ordinal, int timeout) {
250      return () -> {
251        if (failNext.compareAndSet(false, true)) {
252          int batchIndex = replicateEntries(entries, ordinal, timeout);
253          entriesCount += entries.size();
254          int count = batchCount.incrementAndGet();
255          LOG.info(
256            "Completed replicating batch " + System.identityHashCode(entries) + " count=" + count);
257          return batchIndex;
258        } else if (failNext.compareAndSet(true, false)) {
259          throw new ServiceException("Injected failure");
260        }
261        return ordinal;
262      };
263    }
264  }
265}