/** * Licensed to the zk1931 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 com.github.zk1931.jzab; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import com.github.zk1931.jzab.proto.ZabMessage.Message; import com.github.zk1931.jzab.proto.ZabMessage.Message.MessageType; import org.junit.Assert; import org.junit.Test; import org.slf4j.LoggerFactory; import org.slf4j.Logger; class TestReceiver implements Transport.Receiver { final List<Zxid> committedZxids = new ArrayList<Zxid>(); final CountDownLatch latch; private static final Logger LOG = LoggerFactory.getLogger(TestReceiver.class); public TestReceiver() { latch = null; } public TestReceiver(int expectedCommits) { latch = new CountDownLatch(expectedCommits); } @Override public void onReceived(String source, Message message) { if (message.getType() == MessageType.COMMIT) { Zxid zxid = MessageBuilder.fromProtoZxid(message.getCommit().getZxid()); LOG.info("Got COMMIT {}", zxid); committedZxids.add(zxid); latch.countDown(); } } @Override public void onDisconnected(String peerId) { } } /** * Tests for AckProcessor. */ public class AckProcessorTest extends TestBase { private static final Logger LOG = LoggerFactory.getLogger(AckProcessorTest.class); private MessageTuple createAck(String source, Zxid zxid) { Message ack = MessageBuilder.buildAck(zxid); return new MessageTuple(source, ack); } private MessageTuple createJoin(String source, Zxid zxid) { Message join = MessageBuilder.buildJoin(Zxid.ZXID_NOT_EXIST); return new MessageTuple(source, join, zxid); } private MessageTuple createRemove(String serverId, Zxid zxid) { Message remove = MessageBuilder.buildRemove(serverId); return new MessageTuple(serverId, remove, zxid); } @Test(timeout=3000) public void testAllAck() throws Exception { // The cluster contains both server1 and server2, both of them // acknowledeged <0, 0>, then <0, 0> should be committed on both sides. String server1 = getUniqueHostPort(); String server2 = getUniqueHostPort(); TestReceiver receiver1 = new TestReceiver(1); TestReceiver receiver2 = new TestReceiver(1); NettyTransport transport1 = new NettyTransport(server1, receiver1, getDirectory()); NettyTransport transport2 = new NettyTransport(server2, receiver2, getDirectory()); PeerHandler ph1 = new PeerHandler(server1, transport1, 10000); PeerHandler ph2 = new PeerHandler(server2, transport2, 10000); ph1.startBroadcastingTask(); ph2.startBroadcastingTask(); List<String> peers = new ArrayList<String>(); peers.add(server1); peers.add(server2); HashMap<String, PeerHandler> quorumMap = new HashMap<String, PeerHandler>(); quorumMap.put(server1, ph1); quorumMap.put(server2, ph2); ClusterConfiguration cnf = new ClusterConfiguration(Zxid.ZXID_NOT_EXIST, peers, server1); AckProcessor ackProcessor = new AckProcessor(quorumMap, cnf); Zxid z1 = new Zxid(0, 0); ackProcessor.processRequest(createAck(server1, z1)); ackProcessor.processRequest(createAck(server2, z1)); // Waits COMMIT is sent to both servers. receiver1.latch.await(); receiver2.latch.await(); ackProcessor.shutdown(); } @Test(timeout=3000) public void testQuorumAck() throws Exception { // The cluster contains three servers, two of them acknowledeged // <0, 0>, then <0, 0> should stll be committed on server1 and server2. String server1 = getUniqueHostPort(); String server2 = getUniqueHostPort(); String server3 = getUniqueHostPort(); TestReceiver receiver1 = new TestReceiver(1); TestReceiver receiver2 = new TestReceiver(1); TestReceiver receiver3 = new TestReceiver(1); NettyTransport transport1 = new NettyTransport(server1, receiver1, getDirectory()); NettyTransport transport2 = new NettyTransport(server2, receiver2, getDirectory()); NettyTransport transport3 = new NettyTransport(server3, receiver3, getDirectory()); PeerHandler ph1 = new PeerHandler(server1, transport1, 10000); PeerHandler ph2 = new PeerHandler(server2, transport2, 10000); PeerHandler ph3 = new PeerHandler(server3, transport3, 10000); ph1.startBroadcastingTask(); ph2.startBroadcastingTask(); ph3.startBroadcastingTask(); List<String> peers = new ArrayList<String>(); peers.add(server1); peers.add(server2); peers.add(server3); HashMap<String, PeerHandler> quorumMap = new HashMap<String, PeerHandler>(); quorumMap.put(server1, ph1); quorumMap.put(server2, ph2); quorumMap.put(server3, ph3); ClusterConfiguration cnf = new ClusterConfiguration(Zxid.ZXID_NOT_EXIST, peers, server1); AckProcessor ackProcessor = new AckProcessor(quorumMap, cnf); Zxid z1 = new Zxid(0, 0); ackProcessor.processRequest(createAck(server1, z1)); ackProcessor.processRequest(createAck(server2, z1)); // Waits COMMIT is sent to both servers. receiver1.latch.await(); receiver2.latch.await(); Assert.assertFalse(receiver3.latch.await(500, TimeUnit.MILLISECONDS)); ackProcessor.shutdown(); } @Test(timeout=3000) public void testMinorityAck() throws Exception { // The cluster contains three servers, one of them acknowledeged // <0, 0>, then <0, 0> shouldn't be committed on all the servers. String server1 = getUniqueHostPort(); String server2 = getUniqueHostPort(); String server3 = getUniqueHostPort(); TestReceiver receiver1 = new TestReceiver(1); TestReceiver receiver2 = new TestReceiver(1); TestReceiver receiver3 = new TestReceiver(1); NettyTransport transport1 = new NettyTransport(server1, receiver1, getDirectory()); NettyTransport transport2 = new NettyTransport(server2, receiver2, getDirectory()); NettyTransport transport3 = new NettyTransport(server3, receiver3, getDirectory()); PeerHandler ph1 = new PeerHandler(server1, transport1, 10000); PeerHandler ph2 = new PeerHandler(server2, transport2, 10000); PeerHandler ph3 = new PeerHandler(server3, transport3, 10000); ph1.startBroadcastingTask(); ph2.startBroadcastingTask(); ph3.startBroadcastingTask(); List<String> peers = new ArrayList<String>(); peers.add(server1); peers.add(server2); peers.add(server3); HashMap<String, PeerHandler> quorumMap = new HashMap<String, PeerHandler>(); quorumMap.put(server1, ph1); quorumMap.put(server2, ph2); quorumMap.put(server3, ph3); ClusterConfiguration cnf = new ClusterConfiguration(Zxid.ZXID_NOT_EXIST, peers, server1); AckProcessor ackProcessor = new AckProcessor(quorumMap, cnf); Zxid z1 = new Zxid(0, 0); ackProcessor.processRequest(createAck(server1, z1)); // The transaction shouldn't been committed. Assert.assertFalse(receiver1.latch.await(500, TimeUnit.MILLISECONDS)); Assert.assertFalse(receiver2.latch.await(10, TimeUnit.MILLISECONDS)); Assert.assertFalse(receiver3.latch.await(10, TimeUnit.MILLISECONDS)); ackProcessor.shutdown(); } @Test(timeout=3000) public void testDifferentAck() throws Exception { // The cluster contains three servers, server1 acks <0, 2>, server2 acks // <0, 1>, server3 acks <0, 0>, then <0, 1> should be committed. String server1 = getUniqueHostPort(); String server2 = getUniqueHostPort(); String server3 = getUniqueHostPort(); TestReceiver receiver1 = new TestReceiver(1); TestReceiver receiver2 = new TestReceiver(1); TestReceiver receiver3 = new TestReceiver(1); NettyTransport transport1 = new NettyTransport(server1, receiver1, getDirectory()); NettyTransport transport2 = new NettyTransport(server2, receiver2, getDirectory()); NettyTransport transport3 = new NettyTransport(server3, receiver3, getDirectory()); PeerHandler ph1 = new PeerHandler(server1, transport1, 10000); PeerHandler ph2 = new PeerHandler(server2, transport2, 10000); PeerHandler ph3 = new PeerHandler(server3, transport3, 10000); ph1.startBroadcastingTask(); ph2.startBroadcastingTask(); ph3.startBroadcastingTask(); List<String> peers = new ArrayList<String>(); peers.add(server1); peers.add(server2); peers.add(server3); HashMap<String, PeerHandler> quorumMap = new HashMap<String, PeerHandler>(); quorumMap.put(server1, ph1); quorumMap.put(server2, ph2); quorumMap.put(server3, ph3); ClusterConfiguration cnf = new ClusterConfiguration(Zxid.ZXID_NOT_EXIST, peers, server1); AckProcessor ackProcessor = new AckProcessor(quorumMap, cnf); Zxid z1 = new Zxid(0, 0); ackProcessor.processRequest(createAck(server1, new Zxid(0, 2))); ackProcessor.processRequest(createAck(server2, new Zxid(0, 1))); ackProcessor.processRequest(createAck(server3, new Zxid(0, 0))); // Waits COMMIT is sent to both servers. receiver1.latch.await(); receiver2.latch.await(); receiver3.latch.await(); Assert.assertEquals(new Zxid(0, 1), receiver1.committedZxids.get(0)); Assert.assertEquals(new Zxid(0, 1), receiver2.committedZxids.get(0)); Assert.assertEquals(new Zxid(0, 0), receiver3.committedZxids.get(0)); ackProcessor.shutdown(); } @Test(timeout=3000) public void testJoin() throws Exception { // Initially, the cluster only contains server1, then server2 joins with // COP zxid <0, 2>, then server1 acks <0, 10>, server1 should only get // committed zxid <0, 1> since <0, 2> and <0, 3> belong to new // configuration of quorum size 2. String server1 = getUniqueHostPort(); String server2 = getUniqueHostPort(); TestReceiver receiver1 = new TestReceiver(1); TestReceiver receiver2 = new TestReceiver(1); NettyTransport transport1 = new NettyTransport(server1, receiver1, getDirectory()); NettyTransport transport2 = new NettyTransport(server2, receiver2, getDirectory()); PeerHandler ph1 = new PeerHandler(server1, transport1, 10000); PeerHandler ph2 = new PeerHandler(server2, transport2, 10000); ph1.startBroadcastingTask(); ph2.startBroadcastingTask(); List<String> peers = new ArrayList<String>(); peers.add(server1); HashMap<String, PeerHandler> quorumMap = new HashMap<String, PeerHandler>(); quorumMap.put(server1, ph1); ClusterConfiguration cnf = new ClusterConfiguration(Zxid.ZXID_NOT_EXIST, peers, server1); AckProcessor ackProcessor = new AckProcessor(quorumMap, cnf); // Update "original" quorumset so the cloned quorumMap in AckProcessor can // access this one. quorumMap.put(server2, ph2); ackProcessor.processRequest(createJoin(server2, new Zxid(0, 2))); ackProcessor.processRequest(createAck(server1, new Zxid(0, 10))); // Waits COMMIT is sent to both servers. receiver1.latch.await(); Assert.assertFalse(receiver2.latch.await(100, TimeUnit.MILLISECONDS)); // The transactions before COP(<0, 2>) can be committed. Assert.assertEquals(new Zxid(0, 1), receiver1.committedZxids.get(0)); // There should be only one committed message. Assert.assertEquals(1, receiver1.committedZxids.size()); Assert.assertEquals(0, receiver2.committedZxids.size()); ackProcessor.shutdown(); } @Test(timeout=3000) public void testRemove() throws Exception { // Initially, the cluster contains two servers, then AckProcessor gets // REMOVE message for server2 with Zxid <0, 2>. Later server1 acks <0, 3>, // Although <0, 3> has not been acknowledged by quorum of old configuration // it will still be committed since it's committed in new configuration. String server1 = getUniqueHostPort(); String server2 = getUniqueHostPort(); TestReceiver receiver1 = new TestReceiver(1); TestReceiver receiver2 = new TestReceiver(1); NettyTransport transport1 = new NettyTransport(server1, receiver1, getDirectory()); NettyTransport transport2 = new NettyTransport(server2, receiver2, getDirectory()); PeerHandler ph1 = new PeerHandler(server1, transport1, 10000); PeerHandler ph2 = new PeerHandler(server2, transport2, 10000); ph1.startBroadcastingTask(); ph2.startBroadcastingTask(); List<String> peers = new ArrayList<String>(); peers.add(server1); peers.add(server2); HashMap<String, PeerHandler> quorumMap = new HashMap<String, PeerHandler>(); quorumMap.put(server1, ph1); quorumMap.put(server2, ph2); ClusterConfiguration cnf = new ClusterConfiguration(Zxid.ZXID_NOT_EXIST, peers, server1); AckProcessor ackProcessor = new AckProcessor(quorumMap, cnf); ackProcessor.processRequest(createRemove(server2, new Zxid(0, 2))); ackProcessor.processRequest(createAck(server1, new Zxid(0, 3))); ackProcessor.processRequest(createAck(server2, new Zxid(0, 2))); // Waits COMMIT is sent to both servers. receiver1.latch.await(); receiver2.latch.await(); // The transactions before COP(<0, 2>) can be committed. Assert.assertEquals(new Zxid(0, 3), receiver1.committedZxids.get(0)); Assert.assertEquals(new Zxid(0, 2), receiver2.committedZxids.get(0)); // There should be only one committed message. Assert.assertEquals(1, receiver1.committedZxids.size()); Assert.assertEquals(1, receiver2.committedZxids.size()); ackProcessor.shutdown(); } @Test(timeout=3000) public void testRemoveItself() throws Exception { // The cluster has only one server, and the server removes itself from the // cluster. String server1 = getUniqueHostPort(); TestReceiver receiver1 = new TestReceiver(1); NettyTransport transport1 = new NettyTransport(server1, receiver1, getDirectory()); PeerHandler ph1 = new PeerHandler(server1, transport1, 10000); ph1.startBroadcastingTask(); List<String> peers = new ArrayList<String>(); peers.add(server1); HashMap<String, PeerHandler> quorumMap = new HashMap<String, PeerHandler>(); quorumMap.put(server1, ph1); ClusterConfiguration cnf = new ClusterConfiguration(Zxid.ZXID_NOT_EXIST, peers, server1); AckProcessor ackProcessor = new AckProcessor(quorumMap, cnf); Zxid z1 = new Zxid(0, 0); ackProcessor.processRequest(createRemove(server1, z1)); ackProcessor.processRequest(createAck(server1, z1)); // Waits COMMIT is sent to both servers. receiver1.latch.await(); ackProcessor.shutdown(); } @Test(timeout=3000) public void testJoinInNewEpoch() throws Exception { // The joining of the server is the first transaction in the new epoch. String server1 = getUniqueHostPort(); String server2 = getUniqueHostPort(); TestReceiver receiver1 = new TestReceiver(2); TestReceiver receiver2 = new TestReceiver(1); NettyTransport transport1 = new NettyTransport(server1, receiver1, getDirectory()); NettyTransport transport2 = new NettyTransport(server2, receiver2, getDirectory()); PeerHandler ph1 = new PeerHandler(server1, transport1, 10000); PeerHandler ph2 = new PeerHandler(server2, transport2, 10000); ph1.startBroadcastingTask(); ph2.startBroadcastingTask(); List<String> peers = new ArrayList<String>(); peers.add(server1); HashMap<String, PeerHandler> quorumMap = new HashMap<String, PeerHandler>(); quorumMap.put(server1, ph1); ClusterConfiguration cnf = new ClusterConfiguration(Zxid.ZXID_NOT_EXIST, peers, server1); AckProcessor ackProcessor = new AckProcessor(quorumMap, cnf); ackProcessor.processRequest(createAck(server1, new Zxid(0, 0))); ackProcessor.processRequest(createAck(server1, new Zxid(0, 1))); // Server2 joins in. quorumMap.put(server2, ph2); ackProcessor.processRequest(createJoin(server2, new Zxid(1, 0))); ackProcessor.processRequest(createAck(server1, new Zxid(1, 0))); receiver1.latch.await(); boolean isCountedDown = receiver2.latch.await(500, TimeUnit.MILLISECONDS); // server2 shouldn't get any COMMIT message. Assert.assertFalse(isCountedDown); // server1 should only get the COMMIT before COP. Assert.assertEquals(receiver1.committedZxids.size(), 2); ackProcessor.shutdown(); } }