/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.zookeeper.server.quorum; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.lang.reflect.Field; import java.net.InetSocketAddress; import java.net.ServerSocket; import java.net.Socket; import java.net.SocketAddress; import java.nio.ByteBuffer; import java.util.HashMap; import org.apache.jute.BinaryInputArchive; import org.apache.jute.BinaryOutputArchive; import org.apache.jute.InputArchive; import org.apache.jute.OutputArchive; import org.apache.zookeeper.ZKUtil; import org.apache.zookeeper.server.ByteBufferOutputStream; import org.apache.zookeeper.server.DataTree; import org.apache.zookeeper.server.ServerCnxn; import org.apache.zookeeper.server.ServerCnxnFactory; import org.apache.zookeeper.server.ZKDatabase; import org.apache.zookeeper.server.ZooKeeperServer; import org.apache.zookeeper.server.ZooKeeperServer.DataTreeBuilder; import org.apache.zookeeper.server.persistence.FileTxnSnapLog; import org.apache.zookeeper.server.quorum.Leader; import org.apache.zookeeper.server.quorum.QuorumPeer.QuorumServer; import org.apache.zookeeper.server.quorum.flexible.QuorumMaj; import org.apache.zookeeper.server.util.ZxidUtils; import org.junit.Assert; import org.junit.Test; public class Zab1_0Test { private static final class LeadThread extends Thread { private final Leader leader; private LeadThread(Leader leader) { this.leader = leader; } public void run() { try { leader.lead(); } catch (Exception e) { e.printStackTrace(); } finally { leader.shutdown("lead ended"); } } } private static final class NullServerCnxnFactory extends ServerCnxnFactory { public void startup(ZooKeeperServer zkServer) throws IOException, InterruptedException { } public void start() { } public void shutdown() { } public void setMaxClientCnxnsPerHost(int max) { } public void join() throws InterruptedException { } public int getMaxClientCnxnsPerHost() { return 0; } public int getLocalPort() { return 0; } public InetSocketAddress getLocalAddress() { return null; } public Iterable<ServerCnxn> getConnections() { return null; } public void configure(InetSocketAddress addr, int maxClientCnxns) throws IOException { } public void closeSession(long sessionId) { } public void closeAll() { } } static class MockDataTreeBuilder implements DataTreeBuilder { @Override public DataTree build() { return new DataTree(); } } static Socket[] getSocketPair() throws IOException { ServerSocket ss = new ServerSocket(); ss.bind(null); InetSocketAddress endPoint = (InetSocketAddress) ss.getLocalSocketAddress(); Socket s = new Socket(endPoint.getAddress(), endPoint.getPort()); return new Socket[] { s, ss.accept() }; } static void readPacketSkippingPing(InputArchive ia, QuorumPacket qp) throws IOException { while(true) { ia.readRecord(qp, null); if (qp.getType() != Leader.PING) { return; } } } static public interface LeaderConversation { void converseWithLeader(InputArchive ia, OutputArchive oa) throws Exception; } static public interface FollowerConversation { void converseWithFollower(InputArchive ia, OutputArchive oa) throws Exception; } public void testConversation(LeaderConversation conversation) throws Exception { Socket pair[] = getSocketPair(); Socket leaderSocket = pair[0]; Socket followerSocket = pair[1]; File tmpDir = File.createTempFile("test", "dir"); tmpDir.delete(); tmpDir.mkdir(); LeadThread leadThread = null; Leader leader = null; try { QuorumPeer peer = createQuorumPeer(tmpDir); leader = createLeader(tmpDir, peer); peer.leader = leader; leadThread = new LeadThread(leader); leadThread.start(); while(!leader.readyToStart) { Thread.sleep(20); } LearnerHandler lh = new LearnerHandler(leaderSocket, leader); lh.start(); leaderSocket.setSoTimeout(4000); InputArchive ia = BinaryInputArchive.getArchive(followerSocket .getInputStream()); OutputArchive oa = BinaryOutputArchive.getArchive(followerSocket .getOutputStream()); conversation.converseWithLeader(ia, oa); } finally { recursiveDelete(tmpDir); if (leader != null) { leader.shutdown("end of test"); } if (leadThread != null) { leadThread.interrupt(); leadThread.join(); } } } @Test public void testNormalRun() throws Exception { testConversation(new LeaderConversation() { public void converseWithLeader(InputArchive ia, OutputArchive oa) throws IOException { /* we test a normal run. everything should work out well. */ LearnerInfo li = new LearnerInfo(1, 0x10000); byte liBytes[] = new byte[12]; ByteBufferOutputStream.record2ByteBuffer(li, ByteBuffer.wrap(liBytes)); QuorumPacket qp = new QuorumPacket(Leader.FOLLOWERINFO, 0, liBytes, null); oa.writeRecord(qp, null); readPacketSkippingPing(ia, qp); Assert.assertEquals(Leader.LEADERINFO, qp.getType()); Assert.assertEquals(ZxidUtils.makeZxid(1, 0), qp.getZxid()); Assert.assertEquals(ByteBuffer.wrap(qp.getData()).getInt(), 0x10000); qp = new QuorumPacket(Leader.ACKEPOCH, 0, new byte[4], null); oa.writeRecord(qp, null); readPacketSkippingPing(ia, qp); Assert.assertEquals(Leader.DIFF, qp.getType()); readPacketSkippingPing(ia, qp); Assert.assertEquals(Leader.NEWLEADER, qp.getType()); Assert.assertEquals(ZxidUtils.makeZxid(1, 0), qp.getZxid()); qp = new QuorumPacket(Leader.ACK, qp.getZxid(), null, null); oa.writeRecord(qp, null); readPacketSkippingPing(ia, qp); Assert.assertEquals(Leader.UPTODATE, qp.getType()); } }); } @Test public void testLeaderBehind() throws Exception { testConversation(new LeaderConversation() { public void converseWithLeader(InputArchive ia, OutputArchive oa) throws IOException { /* we test a normal run. everything should work out well. */ LearnerInfo li = new LearnerInfo(1, 0x10000); byte liBytes[] = new byte[12]; ByteBufferOutputStream.record2ByteBuffer(li, ByteBuffer.wrap(liBytes)); /* we are going to say we last acked epoch 20 */ QuorumPacket qp = new QuorumPacket(Leader.FOLLOWERINFO, ZxidUtils.makeZxid(20, 0), liBytes, null); oa.writeRecord(qp, null); readPacketSkippingPing(ia, qp); Assert.assertEquals(Leader.LEADERINFO, qp.getType()); Assert.assertEquals(ZxidUtils.makeZxid(21, 0), qp.getZxid()); Assert.assertEquals(ByteBuffer.wrap(qp.getData()).getInt(), 0x10000); qp = new QuorumPacket(Leader.ACKEPOCH, 0, new byte[4], null); oa.writeRecord(qp, null); readPacketSkippingPing(ia, qp); Assert.assertEquals(Leader.DIFF, qp.getType()); readPacketSkippingPing(ia, qp); Assert.assertEquals(Leader.NEWLEADER, qp.getType()); Assert.assertEquals(ZxidUtils.makeZxid(21, 0), qp.getZxid()); qp = new QuorumPacket(Leader.ACK, qp.getZxid(), null, null); oa.writeRecord(qp, null); readPacketSkippingPing(ia, qp); Assert.assertEquals(Leader.UPTODATE, qp.getType()); } }); } private void recursiveDelete(File file) { if (file.isFile()) { file.delete(); } else { for(File c: file.listFiles()) { recursiveDelete(c); } file.delete(); } } private Leader createLeader(File tmpDir, QuorumPeer peer) throws IOException, NoSuchFieldException, IllegalAccessException { FileTxnSnapLog logFactory = new FileTxnSnapLog(tmpDir, tmpDir); peer.setTxnFactory(logFactory); Field addrField = peer.getClass().getDeclaredField("myQuorumAddr"); addrField.setAccessible(true); addrField.set(peer, new InetSocketAddress(33556)); ZKDatabase zkDb = new ZKDatabase(logFactory); DataTreeBuilder treeBuilder = new MockDataTreeBuilder(); LeaderZooKeeperServer zk = new LeaderZooKeeperServer(logFactory, peer, treeBuilder, zkDb); return new Leader(peer, zk); } private QuorumPeer createQuorumPeer(File tmpDir) throws IOException, FileNotFoundException { QuorumPeer peer = new QuorumPeer(); peer.syncLimit = 2; peer.initLimit = 2; peer.tickTime = 2000; peer.quorumPeers = new HashMap<Long, QuorumServer>(); peer.quorumPeers.put(1L, new QuorumServer(0, new InetSocketAddress(33221))); peer.quorumPeers.put(1L, new QuorumServer(1, new InetSocketAddress(33223))); peer.setQuorumVerifier(new QuorumMaj(3)); peer.setCnxnFactory(new NullServerCnxnFactory()); File version2 = new File(tmpDir, "version-2"); version2.mkdir(); new FileOutputStream(new File(version2, "currentEpoch")).write("0\n".getBytes()); new FileOutputStream(new File(version2, "acceptedEpoch")).write("0\n".getBytes()); return peer; } }