View Javadoc

1   /*
2    *
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *     http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   */
19  package org.apache.hadoop.hbase.replication;
20  
21  import static org.junit.Assert.assertEquals;
22  import static org.junit.Assert.assertNotNull;
23  import static org.junit.Assert.assertNull;
24  
25  import java.io.IOException;
26  import java.util.concurrent.ExecutorService;
27  import java.util.concurrent.Executors;
28  import java.util.concurrent.Future;
29  
30  import org.apache.commons.logging.Log;
31  import org.apache.commons.logging.LogFactory;
32  import org.apache.hadoop.conf.Configuration;
33  import org.apache.hadoop.fs.FileSystem;
34  import org.apache.hadoop.fs.Path;
35  import org.apache.hadoop.hbase.CoordinatedStateManager;
36  import org.apache.hadoop.hbase.MiniHBaseCluster;
37  import org.apache.hadoop.hbase.TableName;
38  import org.apache.hadoop.hbase.Waiter;
39  import org.apache.hadoop.hbase.Waiter.Predicate;
40  import org.apache.hadoop.hbase.HBaseConfiguration;
41  import org.apache.hadoop.hbase.HBaseTestingUtility;
42  import org.apache.hadoop.hbase.HConstants;
43  import org.apache.hadoop.hbase.KeyValue;
44  import org.apache.hadoop.hbase.client.replication.ReplicationAdmin;
45  import org.apache.hadoop.hbase.regionserver.HRegionServer;
46  import org.apache.hadoop.hbase.replication.regionserver.Replication;
47  import org.apache.hadoop.hbase.replication.regionserver.ReplicationSourceManager;
48  import org.apache.hadoop.hbase.testclassification.MediumTests;
49  import org.apache.hadoop.hbase.wal.WAL;
50  import org.apache.hadoop.hbase.wal.WALProvider;
51  import org.apache.hadoop.hbase.wal.WALFactory;
52  import org.apache.hadoop.hbase.wal.WALKey;
53  import org.apache.hadoop.hbase.regionserver.wal.WALEdit;
54  import org.apache.hadoop.hbase.replication.regionserver.HBaseInterClusterReplicationEndpoint;
55  import org.apache.hadoop.hbase.replication.regionserver.ReplicationSource;
56  import org.apache.hadoop.hbase.util.Bytes;
57  import org.apache.hadoop.hbase.util.FSUtils;
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 static org.mockito.Mockito.mock;
64  
65  @Category(MediumTests.class)
66  public class TestReplicationSource {
67  
68    private static final Log LOG =
69        LogFactory.getLog(TestReplicationSource.class);
70    private final static HBaseTestingUtility TEST_UTIL =
71        new HBaseTestingUtility();
72    private final static HBaseTestingUtility TEST_UTIL_PEER =
73        new HBaseTestingUtility();
74    private static FileSystem FS;
75    private static Path oldLogDir;
76    private static Path logDir;
77    private static Configuration conf = TEST_UTIL.getConfiguration();
78  
79    /**
80     * @throws java.lang.Exception
81     */
82    @BeforeClass
83    public static void setUpBeforeClass() throws Exception {
84      TEST_UTIL.startMiniDFSCluster(1);
85      FS = TEST_UTIL.getDFSCluster().getFileSystem();
86      Path rootDir = TEST_UTIL.createRootDir();
87      oldLogDir = new Path(rootDir, HConstants.HREGION_OLDLOGDIR_NAME);
88      if (FS.exists(oldLogDir)) FS.delete(oldLogDir, true);
89      logDir = new Path(rootDir, HConstants.HREGION_LOGDIR_NAME);
90      if (FS.exists(logDir)) FS.delete(logDir, true);
91    }
92  
93    @AfterClass
94    public static void tearDownAfterClass() throws Exception {
95      TEST_UTIL_PEER.shutdownMiniHBaseCluster();
96      TEST_UTIL.shutdownMiniHBaseCluster();
97      TEST_UTIL.shutdownMiniDFSCluster();
98    }
99  
100   /**
101    * Sanity check that we can move logs around while we are reading
102    * from them. Should this test fail, ReplicationSource would have a hard
103    * time reading logs that are being archived.
104    * @throws Exception
105    */
106   @Test
107   public void testLogMoving() throws Exception{
108     Path logPath = new Path(logDir, "log");
109     if (!FS.exists(logDir)) FS.mkdirs(logDir);
110     if (!FS.exists(oldLogDir)) FS.mkdirs(oldLogDir);
111     WALProvider.Writer writer = WALFactory.createWALWriter(FS, logPath,
112         TEST_UTIL.getConfiguration());
113     for(int i = 0; i < 3; i++) {
114       byte[] b = Bytes.toBytes(Integer.toString(i));
115       KeyValue kv = new KeyValue(b,b,b);
116       WALEdit edit = new WALEdit();
117       edit.add(kv);
118       WALKey key = new WALKey(b, TableName.valueOf(b), 0, 0,
119           HConstants.DEFAULT_CLUSTER_ID);
120       writer.append(new WAL.Entry(key, edit));
121       writer.sync();
122     }
123     writer.close();
124 
125     WAL.Reader reader = WALFactory.createReader(FS, logPath, TEST_UTIL.getConfiguration());
126     WAL.Entry entry = reader.next();
127     assertNotNull(entry);
128 
129     Path oldLogPath = new Path(oldLogDir, "log");
130     FS.rename(logPath, oldLogPath);
131 
132     entry = reader.next();
133     assertNotNull(entry);
134 
135     entry = reader.next();
136     entry = reader.next();
137 
138     assertNull(entry);
139     reader.close();
140   }
141 
142   /**
143    * Tests that {@link ReplicationSource#terminate(String)} will timeout properly
144    */
145   @Test
146   public void testTerminateTimeout() throws Exception {
147     final ReplicationSource source = new ReplicationSource();
148     ReplicationEndpoint replicationEndpoint = new HBaseInterClusterReplicationEndpoint() {
149       @Override
150       protected void doStart() {
151         notifyStarted();
152       }
153 
154       @Override
155       protected void doStop() {
156         // not calling notifyStopped() here causes the caller of stop() to get a Future that never
157         // completes
158       }
159     };
160     replicationEndpoint.start();
161     ReplicationPeers mockPeers = mock(ReplicationPeers.class);
162     Configuration testConf = HBaseConfiguration.create();
163     testConf.setInt("replication.source.maxretriesmultiplier", 1);
164     source.init(testConf, null, null, null, mockPeers, null, "testPeer", null, replicationEndpoint,
165       null);
166     ExecutorService executor = Executors.newSingleThreadExecutor();
167     final Future<?> future = executor.submit(new Runnable() {
168 
169       @Override
170       public void run() {
171         source.terminate("testing source termination");
172       }
173     });
174     long sleepForRetries = testConf.getLong("replication.source.sleepforretries", 1000);
175     Waiter.waitFor(testConf, sleepForRetries * 2, new Predicate<Exception>() {
176 
177       @Override
178       public boolean evaluate() throws Exception {
179         return future.isDone();
180       }
181 
182     });
183 
184   }
185 
186   /**
187    * Tests that recovered queues are preserved on a regionserver shutdown.
188    * See HBASE-18192
189    * @throws Exception
190    */
191   @Test
192   public void testServerShutdownRecoveredQueue() throws Exception {
193     try {
194       // Ensure single-threaded WAL
195       conf.set("hbase.wal.provider", "defaultProvider");
196       conf.setInt("replication.sleep.before.failover", 2000);
197       // Introduces a delay in regionserver shutdown to give the race condition a chance to kick in.
198       conf.set(HConstants.REGION_SERVER_IMPL, ShutdownDelayRegionServer.class.getName());
199       MiniHBaseCluster cluster = TEST_UTIL.startMiniCluster(2);
200       TEST_UTIL_PEER.startMiniCluster(1);
201 
202       HRegionServer serverA = cluster.getRegionServer(0);
203       final ReplicationSourceManager managerA =
204           ((Replication) serverA.getReplicationSourceService()).getReplicationManager();
205       HRegionServer serverB = cluster.getRegionServer(1);
206       final ReplicationSourceManager managerB =
207           ((Replication) serverB.getReplicationSourceService()).getReplicationManager();
208       final ReplicationAdmin replicationAdmin = new ReplicationAdmin(TEST_UTIL.getConfiguration());
209 
210       final String peerId = "TestPeer";
211       replicationAdmin.addPeer(peerId,
212           new ReplicationPeerConfig().setClusterKey(TEST_UTIL_PEER.getClusterKey()), null);
213       // Wait for replication sources to come up
214       Waiter.waitFor(conf, 20000, new Waiter.Predicate<Exception>() {
215         @Override public boolean evaluate() throws Exception {
216           return !(managerA.getSources().isEmpty() || managerB.getSources().isEmpty());
217         }
218       });
219       // Disabling peer makes sure there is at least one log to claim when the server dies
220       // The recovered queue will also stay there until the peer is disabled even if the
221       // WALs it contains have no data.
222       replicationAdmin.disablePeer(peerId);
223 
224       // Stopping serverA
225       // It's queues should be claimed by the only other alive server i.e. serverB
226       cluster.stopRegionServer(serverA.getServerName());
227       Waiter.waitFor(conf, 20000, new Waiter.Predicate<Exception>() {
228         @Override public boolean evaluate() throws Exception {
229           return managerB.getOldSources().size() == 1;
230         }
231       });
232 
233       final HRegionServer serverC = cluster.startRegionServer().getRegionServer();
234       serverC.waitForServerOnline();
235       Waiter.waitFor(conf, 20000, new Waiter.Predicate<Exception>() {
236         @Override public boolean evaluate() throws Exception {
237           return serverC.getReplicationSourceService() != null;
238         }
239       });
240       final ReplicationSourceManager managerC =
241           ((Replication) serverC.getReplicationSourceService()).getReplicationManager();
242       // Sanity check
243       assertEquals(0, managerC.getOldSources().size());
244 
245       // Stopping serverB
246       // Now serverC should have two recovered queues:
247       // 1. The serverB's normal queue
248       // 2. serverA's recovered queue on serverB
249       cluster.stopRegionServer(serverB.getServerName());
250       Waiter.waitFor(conf, 20000, new Waiter.Predicate<Exception>() {
251         @Override public boolean evaluate() throws Exception {
252           return managerC.getOldSources().size() == 2;
253         }
254       });
255       replicationAdmin.enablePeer(peerId);
256       Waiter.waitFor(conf, 20000, new Waiter.Predicate<Exception>() {
257         @Override public boolean evaluate() throws Exception {
258           return managerC.getOldSources().size() == 0;
259         }
260       });
261     } finally {
262       conf.set(HConstants.REGION_SERVER_IMPL, HRegionServer.class.getName());
263     }
264   }
265 
266   /**
267    * Regionserver implementation that adds a delay on the graceful shutdown.
268    */
269   public static class ShutdownDelayRegionServer extends HRegionServer {
270     public ShutdownDelayRegionServer(Configuration conf) throws IOException, InterruptedException {
271       super(conf);
272     }
273 
274     public ShutdownDelayRegionServer(Configuration conf, CoordinatedStateManager csm)
275         throws IOException, InterruptedException {
276       super(conf, csm);
277     }
278 
279     @Override
280     protected void stopServiceThreads() {
281       // Add a delay before service threads are shutdown.
282       // This will keep the zookeeper connection alive for the duration of the delay.
283       LOG.info("Adding a delay to the regionserver shutdown");
284       try {
285         Thread.sleep(2000);
286       } catch (InterruptedException ex) {
287         LOG.error("Interrupted while sleeping");
288       }
289       super.stopServiceThreads();
290     }
291   }
292 
293 }
294