com.twitter.common.zookeeper.SingletonServiceTest.java Source code

Java tutorial

Introduction

Here is the source code for com.twitter.common.zookeeper.SingletonServiceTest.java

Source

// =================================================================================================
// Copyright 2011 Twitter, Inc.
// -------------------------------------------------------------------------------------------------
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this work except in compliance with the License.
// You may obtain a copy of the License in the LICENSE file, or 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.twitter.common.zookeeper;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;

import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.common.testing.TearDown;

import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.Watcher.Event.EventType;
import org.apache.zookeeper.Watcher.Event.KeeperState;
import org.easymock.Capture;
import org.easymock.EasyMock;
import org.easymock.IAnswer;
import org.easymock.IExpectationSetters;
import org.easymock.IMocksControl;
import org.junit.Before;
import org.junit.Test;

import com.twitter.common.base.ExceptionalCommand;
import com.twitter.common.zookeeper.Candidate.Leader;
import com.twitter.common.zookeeper.Group.JoinException;
import com.twitter.common.zookeeper.ServerSet.EndpointStatus;
import com.twitter.common.zookeeper.SingletonService.DefeatOnDisconnectLeader;
import com.twitter.common.zookeeper.SingletonService.LeaderControl;
import com.twitter.common.zookeeper.SingletonService.LeadershipListener;
import com.twitter.common.zookeeper.testing.BaseZooKeeperTest;

import static com.twitter.common.testing.easymock.EasyMockTest.createCapture;
import static org.easymock.EasyMock.capture;
import static org.easymock.EasyMock.createControl;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.expectLastCall;
import static org.junit.Assert.fail;

public class SingletonServiceTest extends BaseZooKeeperTest {
    private static final int PORT_A = 1234;
    private static final int PORT_B = 8080;
    private static final InetSocketAddress PRIMARY_ENDPOINT = InetSocketAddress.createUnresolved("foo", PORT_A);
    private static final Map<String, InetSocketAddress> AUX_ENDPOINTS = ImmutableMap.of("http-admin",
            InetSocketAddress.createUnresolved("foo", PORT_B));

    private IMocksControl control;
    private SingletonService.LeadershipListener listener;
    private ServerSet serverSet;
    private ServerSet.EndpointStatus endpointStatus;
    private Candidate candidate;
    private ExceptionalCommand<Group.JoinException> abdicate;

    private SingletonService service;

    @Before
    @SuppressWarnings("unchecked")
    public void mySetUp() throws IOException {
        control = createControl();
        addTearDown(new TearDown() {
            @Override
            public void tearDown() {
                control.verify();
            }
        });
        listener = control.createMock(SingletonService.LeadershipListener.class);
        serverSet = control.createMock(ServerSet.class);
        candidate = control.createMock(Candidate.class);
        endpointStatus = control.createMock(ServerSet.EndpointStatus.class);
        abdicate = control.createMock(ExceptionalCommand.class);

        service = new SingletonService(serverSet, candidate);
    }

    private void newLeader(final String hostName, Capture<Leader> leader, LeadershipListener listener)
            throws Exception {

        service.lead(InetSocketAddress.createUnresolved(hostName, PORT_A),
                ImmutableMap.of("http-admin", InetSocketAddress.createUnresolved(hostName, PORT_B)), listener);

        // This actually elects the leader.
        leader.getValue().onElected(abdicate);
    }

    private void newLeader(String hostName, Capture<Leader> leader) throws Exception {
        newLeader(hostName, leader, listener);
    }

    private IExpectationSetters<EndpointStatus> expectJoin() throws Exception {
        return expect(serverSet.join(PRIMARY_ENDPOINT, AUX_ENDPOINTS));
    }

    @Test
    public void testLeadAdvertise() throws Exception {
        Capture<Leader> leaderCapture = createCapture();

        expect(candidate.offerLeadership(capture(leaderCapture))).andReturn(null);
        Capture<LeaderControl> controlCapture = createCapture();
        listener.onLeading(capture(controlCapture));

        expectJoin().andReturn(endpointStatus);
        endpointStatus.leave();
        abdicate.execute();

        control.replay();

        newLeader("foo", leaderCapture);
        controlCapture.getValue().advertise();
        controlCapture.getValue().leave();
    }

    @Test
    public void teatLeadLeaveNoAdvertise() throws Exception {
        Capture<Leader> leaderCapture = createCapture();

        expect(candidate.offerLeadership(capture(leaderCapture))).andReturn(null);
        abdicate.execute();

        Capture<LeaderControl> controlCapture = createCapture();
        listener.onLeading(capture(controlCapture));

        control.replay();

        newLeader("foo", leaderCapture);
        controlCapture.getValue().leave();
    }

    @Test
    public void testLeadJoinFailure() throws Exception {
        Capture<Leader> leaderCapture = new Capture<Leader>();

        expect(candidate.offerLeadership(capture(leaderCapture))).andReturn(null);
        Capture<LeaderControl> controlCapture = createCapture();
        listener.onLeading(capture(controlCapture));

        expectJoin().andThrow(new Group.JoinException("Injected join failure.", new Exception()));
        abdicate.execute();

        control.replay();

        newLeader("foo", leaderCapture);

        try {
            controlCapture.getValue().advertise();
            fail("Join should have failed.");
        } catch (JoinException e) {
            // Expected.
        }

        controlCapture.getValue().leave();
    }

    @Test(expected = IllegalStateException.class)
    public void testMultipleAdvertise() throws Exception {
        Capture<Leader> leaderCapture = createCapture();

        expect(candidate.offerLeadership(capture(leaderCapture))).andReturn(null);
        Capture<LeaderControl> controlCapture = createCapture();
        listener.onLeading(capture(controlCapture));

        expectJoin().andReturn(endpointStatus);

        control.replay();

        newLeader("foo", leaderCapture);
        controlCapture.getValue().advertise();
        controlCapture.getValue().advertise();
    }

    @Test(expected = IllegalStateException.class)
    public void testMultipleLeave() throws Exception {
        Capture<Leader> leaderCapture = createCapture();

        expect(candidate.offerLeadership(capture(leaderCapture))).andReturn(null);
        Capture<LeaderControl> controlCapture = createCapture();
        listener.onLeading(capture(controlCapture));

        expectJoin().andReturn(endpointStatus);
        endpointStatus.leave();
        abdicate.execute();

        control.replay();

        newLeader("foo", leaderCapture);
        controlCapture.getValue().advertise();
        controlCapture.getValue().leave();
        controlCapture.getValue().leave();
    }

    @Test(expected = IllegalStateException.class)
    public void testAdvertiseAfterLeave() throws Exception {
        Capture<Leader> leaderCapture = createCapture();

        expect(candidate.offerLeadership(capture(leaderCapture))).andReturn(null);
        Capture<LeaderControl> controlCapture = createCapture();
        listener.onLeading(capture(controlCapture));

        abdicate.execute();

        control.replay();

        newLeader("foo", leaderCapture);
        controlCapture.getValue().leave();
        controlCapture.getValue().advertise();
    }

    @Test
    public void testLeadMulti() throws Exception {
        List<Capture<Leader>> leaderCaptures = Lists.newArrayList();
        List<Capture<LeaderControl>> leaderControlCaptures = Lists.newArrayList();

        for (int i = 0; i < 5; i++) {
            Capture<Leader> leaderCapture = new Capture<Leader>();
            leaderCaptures.add(leaderCapture);
            Capture<LeaderControl> controlCapture = createCapture();
            leaderControlCaptures.add(controlCapture);

            expect(candidate.offerLeadership(capture(leaderCapture))).andReturn(null);
            listener.onLeading(capture(controlCapture));
            InetSocketAddress primary = InetSocketAddress.createUnresolved("foo" + i, PORT_A);
            Map<String, InetSocketAddress> aux = ImmutableMap.of("http-admin",
                    InetSocketAddress.createUnresolved("foo" + i, PORT_B));
            expect(serverSet.join(primary, aux)).andReturn(endpointStatus);
            endpointStatus.leave();
            abdicate.execute();
        }

        control.replay();

        for (int i = 0; i < 5; i++) {
            final String leaderName = "foo" + i;
            newLeader(leaderName, leaderCaptures.get(i));
            leaderControlCaptures.get(i).getValue().advertise();
            leaderControlCaptures.get(i).getValue().leave();
        }
    }

    @Test
    public void testLeaderLeaves() throws Exception {
        control.replay();
        shutdownNetwork();
    }

    private static IAnswer<?> countDownAnswer(final CountDownLatch latch) {
        return new IAnswer<Void>() {
            @Override
            public Void answer() {
                latch.countDown();
                return null;
            }
        };
    }

    @Test
    public void testLeaderDisconnect() throws Exception {
        Capture<LeaderControl> controlCapture = createCapture();

        CountDownLatch leading = new CountDownLatch(1);
        listener.onLeading(capture(controlCapture));
        expectLastCall().andAnswer(countDownAnswer(leading));

        CountDownLatch defeated = new CountDownLatch(1);
        listener.onDefeated(null);
        expectLastCall().andAnswer(countDownAnswer(defeated));

        control.replay();

        ZooKeeperClient zkClient = createZkClient();
        serverSet = new ServerSetImpl(zkClient, "/fake/path");
        candidate = new CandidateImpl(new Group(zkClient, ZooKeeperUtils.OPEN_ACL_UNSAFE, "/fake/path"));
        DefeatOnDisconnectLeader leader = new DefeatOnDisconnectLeader(zkClient, listener);
        service = new SingletonService(serverSet, candidate);
        service.lead(InetSocketAddress.createUnresolved("foo", PORT_A),
                ImmutableMap.of("http-admin", InetSocketAddress.createUnresolved("foo", PORT_B)), leader);

        leading.await();

        shutdownNetwork();
        defeated.await();
    }

    @Test
    public void testNonLeaderDisconnect() throws Exception {
        CountDownLatch elected = new CountDownLatch(1);
        listener.onLeading(EasyMock.<LeaderControl>anyObject());
        expectLastCall().andAnswer(countDownAnswer(elected));
        listener.onDefeated(null);
        expectLastCall().anyTimes();

        control.replay();

        ZooKeeperClient zkClient = createZkClient();
        String path = "/fake/path";
        // Create a fake leading candidate node to ensure that the leader in this test is never
        // elected.
        ZooKeeperUtils.ensurePath(zkClient, ZooKeeperUtils.OPEN_ACL_UNSAFE, path);
        String leaderNode = zkClient.get().create(path + "/" + SingletonService.LEADER_ELECT_NODE_PREFIX,
                "fake_leader".getBytes(), ZooKeeperUtils.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT_SEQUENTIAL);

        serverSet = new ServerSetImpl(zkClient, path);
        candidate = SingletonService.createSingletonCandidate(zkClient, path, ZooKeeperUtils.OPEN_ACL_UNSAFE);
        DefeatOnDisconnectLeader leader = new DefeatOnDisconnectLeader(zkClient, listener);
        service = new SingletonService(serverSet, candidate);
        service.lead(InetSocketAddress.createUnresolved("foo", PORT_A),
                ImmutableMap.of("http-admin", InetSocketAddress.createUnresolved("foo", PORT_B)), leader);

        final CountDownLatch disconnected = new CountDownLatch(1);
        zkClient.register(new Watcher() {
            @Override
            public void process(WatchedEvent event) {
                if ((event.getType() == EventType.None) && (event.getState() == KeeperState.Disconnected)) {
                    disconnected.countDown();
                }
            }
        });

        shutdownNetwork();
        disconnected.await();

        restartNetwork();
        zkClient.get().delete(leaderNode, ZooKeeperUtils.ANY_VERSION);
        // Upon deletion of the fake leader node, the candidate should become leader.
        elected.await();
    }
}