View Javadoc

1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  
19  package org.apache.hadoop.hbase.replication.regionserver;
20  
21  import static org.junit.Assert.*;
22  import static org.mockito.Mockito.mock;
23  import static org.mockito.Mockito.when;
24  
25  import java.io.IOException;
26  import java.util.List;
27  import java.util.concurrent.Executors;
28  import java.util.concurrent.atomic.AtomicLong;
29  
30  import org.apache.commons.logging.Log;
31  import org.apache.commons.logging.LogFactory;
32  import org.apache.commons.logging.impl.Log4JLogger;
33  import org.apache.hadoop.conf.Configuration;
34  import org.apache.hadoop.hbase.HBaseTestingUtility;
35  import org.apache.hadoop.hbase.HConstants;
36  import org.apache.hadoop.hbase.HRegionLocation;
37  import org.apache.hadoop.hbase.HTableDescriptor;
38  import org.apache.hadoop.hbase.testclassification.MediumTests;
39  import org.apache.hadoop.hbase.TableName;
40  import org.apache.hadoop.hbase.Waiter;
41  import org.apache.hadoop.hbase.client.ClusterConnection;
42  import org.apache.hadoop.hbase.client.RpcRetryingCaller;
43  import org.apache.hadoop.hbase.client.Connection;
44  import org.apache.hadoop.hbase.client.ConnectionFactory;
45  import org.apache.hadoop.hbase.client.RegionLocator;
46  import org.apache.hadoop.hbase.client.Table;
47  import org.apache.hadoop.hbase.client.replication.ReplicationAdmin;
48  import org.apache.hadoop.hbase.regionserver.HRegionServer;
49  import org.apache.hadoop.hbase.regionserver.Region;
50  import org.apache.hadoop.hbase.wal.WAL.Entry;
51  import org.apache.hadoop.hbase.wal.WALKey;
52  import org.apache.hadoop.hbase.regionserver.wal.WALEdit;
53  import org.apache.hadoop.hbase.replication.ReplicationException;
54  import org.apache.hadoop.hbase.replication.ReplicationPeerConfig;
55  import org.apache.hadoop.hbase.util.ServerRegionReplicaUtil;
56  import org.apache.hadoop.hbase.zookeeper.ZKUtil;
57  import org.apache.log4j.Level;
58  import org.junit.AfterClass;
59  import org.junit.BeforeClass;
60  import org.junit.Test;
61  import org.junit.experimental.categories.Category;
62  
63  import com.google.common.collect.Lists;
64  
65  /**
66   * Tests RegionReplicaReplicationEndpoint class by setting up region replicas and verifying
67   * async wal replication replays the edits to the secondary region in various scenarios.
68   */
69  @Category(MediumTests.class)
70  public class TestRegionReplicaReplicationEndpoint {
71  
72    private static final Log LOG = LogFactory.getLog(TestRegionReplicaReplicationEndpoint.class);
73  
74    static {
75      ((Log4JLogger)RpcRetryingCaller.LOG).getLogger().setLevel(Level.ALL);
76    }
77  
78    private static final int NB_SERVERS = 2;
79  
80    private static final HBaseTestingUtility HTU = new HBaseTestingUtility();
81  
82    @BeforeClass
83    public static void beforeClass() throws Exception {
84      Configuration conf = HTU.getConfiguration();
85      conf.setFloat("hbase.regionserver.logroll.multiplier", 0.0003f);
86      conf.setInt("replication.source.size.capacity", 10240);
87      conf.setLong("replication.source.sleepforretries", 100);
88      conf.setInt("hbase.regionserver.maxlogs", 10);
89      conf.setLong("hbase.master.logcleaner.ttl", 10);
90      conf.setInt("zookeeper.recovery.retry", 1);
91      conf.setInt("zookeeper.recovery.retry.intervalmill", 10);
92      conf.setBoolean(HConstants.REPLICATION_ENABLE_KEY, true);
93      conf.setBoolean(ServerRegionReplicaUtil.REGION_REPLICA_REPLICATION_CONF_KEY, true);
94      conf.setLong(HConstants.THREAD_WAKE_FREQUENCY, 100);
95      conf.setInt("replication.stats.thread.period.seconds", 5);
96      conf.setBoolean("hbase.tests.use.shortcircuit.reads", false);
97      conf.setInt(HConstants.HBASE_CLIENT_RETRIES_NUMBER, 5); // less number of retries is needed
98      conf.setInt("hbase.client.serverside.retries.multiplier", 1);
99  
100     HTU.startMiniCluster(NB_SERVERS);
101   }
102 
103   @AfterClass
104   public static void afterClass() throws Exception {
105     HTU.shutdownMiniCluster();
106   }
107 
108   @Test
109   public void testRegionReplicaReplicationPeerIsCreated() throws IOException, ReplicationException {
110     // create a table with region replicas. Check whether the replication peer is created
111     // and replication started.
112     ReplicationAdmin admin = new ReplicationAdmin(HTU.getConfiguration());
113     String peerId = "region_replica_replication";
114 
115     if (admin.getPeerConfig(peerId) != null) {
116       admin.removePeer(peerId);
117     }
118 
119     HTableDescriptor htd = HTU.createTableDescriptor(
120       "testReplicationPeerIsCreated_no_region_replicas");
121     HTU.getHBaseAdmin().createTable(htd);
122     ReplicationPeerConfig peerConfig = admin.getPeerConfig(peerId);
123     assertNull(peerConfig);
124 
125     htd = HTU.createTableDescriptor("testReplicationPeerIsCreated");
126     htd.setRegionReplication(2);
127     HTU.getHBaseAdmin().createTable(htd);
128 
129     // assert peer configuration is correct
130     peerConfig = admin.getPeerConfig(peerId);
131     assertNotNull(peerConfig);
132     assertEquals(peerConfig.getClusterKey(), ZKUtil.getZooKeeperClusterKey(HTU.getConfiguration()));
133     assertEquals(peerConfig.getReplicationEndpointImpl(),
134       RegionReplicaReplicationEndpoint.class.getName());
135     admin.close();
136   }
137 
138   @Test (timeout=240000)
139   public void testRegionReplicaReplicationPeerIsCreatedForModifyTable() throws Exception {
140     // modify a table by adding region replicas. Check whether the replication peer is created
141     // and replication started.
142     ReplicationAdmin admin = new ReplicationAdmin(HTU.getConfiguration());
143     String peerId = "region_replica_replication";
144 
145     if (admin.getPeerConfig(peerId) != null) {
146       admin.removePeer(peerId);
147     }
148 
149     HTableDescriptor htd
150       = HTU.createTableDescriptor("testRegionReplicaReplicationPeerIsCreatedForModifyTable");
151     HTU.getHBaseAdmin().createTable(htd);
152 
153     // assert that replication peer is not created yet
154     ReplicationPeerConfig peerConfig = admin.getPeerConfig(peerId);
155     assertNull(peerConfig);
156 
157     HTU.getHBaseAdmin().disableTable(htd.getTableName());
158     htd.setRegionReplication(2);
159     HTU.getHBaseAdmin().modifyTable(htd.getTableName(), htd);
160     HTU.getHBaseAdmin().enableTable(htd.getTableName());
161 
162     // assert peer configuration is correct
163     peerConfig = admin.getPeerConfig(peerId);
164     assertNotNull(peerConfig);
165     assertEquals(peerConfig.getClusterKey(), ZKUtil.getZooKeeperClusterKey(HTU.getConfiguration()));
166     assertEquals(peerConfig.getReplicationEndpointImpl(),
167       RegionReplicaReplicationEndpoint.class.getName());
168     admin.close();
169   }
170 
171   public void testRegionReplicaReplication(int regionReplication) throws Exception {
172     // test region replica replication. Create a table with single region, write some data
173     // ensure that data is replicated to the secondary region
174     TableName tableName = TableName.valueOf("testRegionReplicaReplicationWithReplicas_"
175         + regionReplication);
176     HTableDescriptor htd = HTU.createTableDescriptor(tableName.toString());
177     htd.setRegionReplication(regionReplication);
178     HTU.getHBaseAdmin().createTable(htd);
179     TableName tableNameNoReplicas =
180         TableName.valueOf("testRegionReplicaReplicationWithReplicas_NO_REPLICAS");
181     HTU.deleteTableIfAny(tableNameNoReplicas);
182     HTU.createTable(tableNameNoReplicas, HBaseTestingUtility.fam1);
183 
184     Connection connection = ConnectionFactory.createConnection(HTU.getConfiguration());
185     Table table = connection.getTable(tableName);
186     Table tableNoReplicas = connection.getTable(tableNameNoReplicas);
187 
188     try {
189       // load some data to the non-replicated table
190       HTU.loadNumericRows(tableNoReplicas, HBaseTestingUtility.fam1, 6000, 7000);
191 
192       // load the data to the table
193       HTU.loadNumericRows(table, HBaseTestingUtility.fam1, 0, 1000);
194 
195       verifyReplication(tableName, regionReplication, 0, 1000);
196 
197     } finally {
198       table.close();
199       tableNoReplicas.close();
200       HTU.deleteTableIfAny(tableNameNoReplicas);
201       connection.close();
202     }
203   }
204 
205   private void verifyReplication(TableName tableName, int regionReplication,
206       final int startRow, final int endRow) throws Exception {
207     verifyReplication(tableName, regionReplication, startRow, endRow, true);
208   }
209 
210   private void verifyReplication(TableName tableName, int regionReplication,
211       final int startRow, final int endRow, final boolean present) throws Exception {
212     // find the regions
213     final Region[] regions = new Region[regionReplication];
214 
215     for (int i=0; i < NB_SERVERS; i++) {
216       HRegionServer rs = HTU.getMiniHBaseCluster().getRegionServer(i);
217       List<Region> onlineRegions = rs.getOnlineRegions(tableName);
218       for (Region region : onlineRegions) {
219         regions[region.getRegionInfo().getReplicaId()] = region;
220       }
221     }
222 
223     for (Region region : regions) {
224       assertNotNull(region);
225     }
226 
227     for (int i = 1; i < regionReplication; i++) {
228       final Region region = regions[i];
229       // wait until all the data is replicated to all secondary regions
230       Waiter.waitFor(HTU.getConfiguration(), 90000, new Waiter.Predicate<Exception>() {
231         @Override
232         public boolean evaluate() throws Exception {
233           LOG.info("verifying replication for region replica:" + region.getRegionInfo());
234           try {
235             HTU.verifyNumericRows(region, HBaseTestingUtility.fam1, startRow, endRow, present);
236           } catch(Throwable ex) {
237             LOG.warn("Verification from secondary region is not complete yet", ex);
238             // still wait
239             return false;
240           }
241           return true;
242         }
243       });
244     }
245   }
246 
247   @Test(timeout = 240000)
248   public void testRegionReplicaReplicationWith2Replicas() throws Exception {
249     testRegionReplicaReplication(2);
250   }
251 
252   @Test(timeout = 240000)
253   public void testRegionReplicaReplicationWith3Replicas() throws Exception {
254     testRegionReplicaReplication(3);
255   }
256 
257   @Test(timeout = 240000)
258   public void testRegionReplicaReplicationWith10Replicas() throws Exception {
259     testRegionReplicaReplication(10);
260   }
261 
262   @Test (timeout = 240000)
263   public void testRegionReplicaWithoutMemstoreReplication() throws Exception {
264     int regionReplication = 3;
265     TableName tableName = TableName.valueOf("testRegionReplicaWithoutMemstoreReplication");
266     HTableDescriptor htd = HTU.createTableDescriptor(tableName.toString());
267     htd.setRegionReplication(regionReplication);
268     htd.setRegionMemstoreReplication(false);
269     HTU.getHBaseAdmin().createTable(htd);
270 
271     Connection connection = ConnectionFactory.createConnection(HTU.getConfiguration());
272     Table table = connection.getTable(tableName);
273     try {
274       // write data to the primary. The replicas should not receive the data
275       final int STEP = 100;
276       for (int i = 0; i < 3; ++i) {
277         final int startRow = i * STEP;
278         final int endRow = (i + 1) * STEP;
279         LOG.info("Writing data from " + startRow + " to " + endRow);
280         HTU.loadNumericRows(table, HBaseTestingUtility.fam1, startRow, endRow);
281         verifyReplication(tableName, regionReplication, startRow, endRow, false);
282 
283         // Flush the table, now the data should show up in the replicas
284         LOG.info("flushing table");
285         HTU.flush(tableName);
286         verifyReplication(tableName, regionReplication, 0, endRow, true);
287       }
288     } finally {
289       table.close();
290       connection.close();
291     }
292   }
293 
294   @Test (timeout = 240000)
295   public void testRegionReplicaReplicationForFlushAndCompaction() throws Exception {
296     // Tests a table with region replication 3. Writes some data, and causes flushes and
297     // compactions. Verifies that the data is readable from the replicas. Note that this
298     // does not test whether the replicas actually pick up flushed files and apply compaction
299     // to their stores
300     int regionReplication = 3;
301     TableName tableName = TableName.valueOf("testRegionReplicaReplicationForFlushAndCompaction");
302     HTableDescriptor htd = HTU.createTableDescriptor(tableName.toString());
303     htd.setRegionReplication(regionReplication);
304     HTU.getHBaseAdmin().createTable(htd);
305 
306     Connection connection = ConnectionFactory.createConnection(HTU.getConfiguration());
307     Table table = connection.getTable(tableName);
308 
309     try {
310       // load the data to the table
311 
312       for (int i = 0; i < 6000; i += 1000) {
313         LOG.info("Writing data from " + i + " to " + (i+1000));
314         HTU.loadNumericRows(table, HBaseTestingUtility.fam1, i, i+1000);
315         LOG.info("flushing table");
316         HTU.flush(tableName);
317         LOG.info("compacting table");
318         HTU.compact(tableName, false);
319       }
320 
321       verifyReplication(tableName, regionReplication, 0, 1000);
322     } finally {
323       table.close();
324       connection.close();
325     }
326   }
327 
328   @Test (timeout = 240000)
329   public void testRegionReplicaReplicationIgnoresDisabledTables() throws Exception {
330     testRegionReplicaReplicationIgnoresDisabledTables(false);
331   }
332 
333   @Test (timeout = 240000)
334   public void testRegionReplicaReplicationIgnoresDroppedTables() throws Exception {
335     testRegionReplicaReplicationIgnoresDisabledTables(true);
336   }
337 
338   public void testRegionReplicaReplicationIgnoresDisabledTables(boolean dropTable)
339       throws Exception {
340     // tests having edits from a disabled or dropped table is handled correctly by skipping those
341     // entries and further edits after the edits from dropped/disabled table can be replicated
342     // without problems.
343     TableName tableName = TableName.valueOf("testRegionReplicaReplicationIgnoresDisabledTables"
344       + dropTable);
345     HTableDescriptor htd = HTU.createTableDescriptor(tableName.toString());
346     int regionReplication = 3;
347     htd.setRegionReplication(regionReplication);
348     HTU.deleteTableIfAny(tableName);
349     HTU.getHBaseAdmin().createTable(htd);
350     TableName toBeDisabledTable = TableName.valueOf(dropTable ? "droppedTable" : "disabledTable");
351     HTU.deleteTableIfAny(toBeDisabledTable);
352     htd = HTU.createTableDescriptor(toBeDisabledTable.toString());
353     htd.setRegionReplication(regionReplication);
354     HTU.getHBaseAdmin().createTable(htd);
355 
356     // both tables are created, now pause replication
357     ReplicationAdmin admin = new ReplicationAdmin(HTU.getConfiguration());
358     admin.disablePeer(ServerRegionReplicaUtil.getReplicationPeerId());
359 
360     // now that the replication is disabled, write to the table to be dropped, then drop the table.
361 
362     Connection connection = ConnectionFactory.createConnection(HTU.getConfiguration());
363     Table table = connection.getTable(tableName);
364     Table tableToBeDisabled = connection.getTable(toBeDisabledTable);
365 
366     HTU.loadNumericRows(tableToBeDisabled, HBaseTestingUtility.fam1, 6000, 7000);
367 
368     AtomicLong skippedEdits = new AtomicLong();
369     RegionReplicaReplicationEndpoint.RegionReplicaOutputSink sink =
370         mock(RegionReplicaReplicationEndpoint.RegionReplicaOutputSink.class);
371     when(sink.getSkippedEditsCounter()).thenReturn(skippedEdits);
372     RegionReplicaReplicationEndpoint.RegionReplicaSinkWriter sinkWriter =
373         new RegionReplicaReplicationEndpoint.RegionReplicaSinkWriter(sink,
374           (ClusterConnection) connection,
375           Executors.newSingleThreadExecutor(), Integer.MAX_VALUE);
376     RegionLocator rl = connection.getRegionLocator(toBeDisabledTable);
377     HRegionLocation hrl = rl.getRegionLocation(HConstants.EMPTY_BYTE_ARRAY);
378     byte[] encodedRegionName = hrl.getRegionInfo().getEncodedNameAsBytes();
379 
380     Entry entry = new Entry(
381       new WALKey(encodedRegionName, toBeDisabledTable, 1),
382       new WALEdit());
383 
384     HTU.getHBaseAdmin().disableTable(toBeDisabledTable); // disable the table
385     if (dropTable) {
386       HTU.getHBaseAdmin().deleteTable(toBeDisabledTable);
387     }
388 
389     sinkWriter.append(toBeDisabledTable, encodedRegionName,
390       HConstants.EMPTY_BYTE_ARRAY, Lists.newArrayList(entry, entry));
391 
392     assertEquals(2, skippedEdits.get());
393 
394     try {
395       // load some data to the to-be-dropped table
396 
397       // load the data to the table
398       HTU.loadNumericRows(table, HBaseTestingUtility.fam1, 0, 1000);
399 
400       // now enable the replication
401       admin.enablePeer(ServerRegionReplicaUtil.getReplicationPeerId());
402 
403       verifyReplication(tableName, regionReplication, 0, 1000);
404 
405     } finally {
406       admin.close();
407       table.close();
408       rl.close();
409       tableToBeDisabled.close();
410       HTU.deleteTableIfAny(toBeDisabledTable);
411       connection.close();
412     }
413   }
414 }