/** * 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.test; import static org.apache.zookeeper.test.ClientBase.CONNECTION_TIMEOUT; import java.util.concurrent.CountDownLatch; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.apache.zookeeper.CreateMode; import org.apache.zookeeper.KeeperException.ConnectionLossException; import org.apache.zookeeper.PortAssignment; import org.apache.zookeeper.WatchedEvent; import org.apache.zookeeper.Watcher; import org.apache.zookeeper.Watcher.Event.KeeperState; import org.apache.zookeeper.ZooDefs.Ids; import org.apache.zookeeper.ZooKeeper; import org.apache.zookeeper.ZooKeeper.States; import org.apache.zookeeper.server.quorum.QuorumPeerTestBase; import org.junit.Assert; import org.junit.Test; public class ObserverTest extends QuorumPeerTestBase implements Watcher{ protected static final Logger LOG = LoggerFactory.getLogger(ObserverTest.class); CountDownLatch latch; ZooKeeper zk; WatchedEvent lastEvent = null; /** * This test ensures two things: * 1. That Observers can successfully proxy requests to the ensemble. * 2. That Observers don't participate in leader elections. * The second is tested by constructing an ensemble where a leader would * be elected if and only if an Observer voted. * @throws Exception */ @Test public void testObserver() throws Exception { ClientBase.setupTestEnv(); // We expect two notifications before we want to continue latch = new CountDownLatch(2); final int PORT_QP1 = PortAssignment.unique(); final int PORT_QP2 = PortAssignment.unique(); final int PORT_OBS = PortAssignment.unique(); final int PORT_QP_LE1 = PortAssignment.unique(); final int PORT_QP_LE2 = PortAssignment.unique(); final int PORT_OBS_LE = PortAssignment.unique(); final int CLIENT_PORT_QP1 = PortAssignment.unique(); final int CLIENT_PORT_QP2 = PortAssignment.unique(); final int CLIENT_PORT_OBS = PortAssignment.unique(); String quorumCfgSection = "electionAlg=3\n" + "server.1=127.0.0.1:" + (PORT_QP1) + ":" + (PORT_QP_LE1) + "\nserver.2=127.0.0.1:" + (PORT_QP2) + ":" + (PORT_QP_LE2) + "\nserver.3=127.0.0.1:" + (PORT_OBS)+ ":" + (PORT_OBS_LE) + ":observer"; String obsCfgSection = quorumCfgSection + "\npeerType=observer"; MainThread q1 = new MainThread(1, CLIENT_PORT_QP1, quorumCfgSection); MainThread q2 = new MainThread(2, CLIENT_PORT_QP2, quorumCfgSection); MainThread q3 = new MainThread(3, CLIENT_PORT_OBS, obsCfgSection); q1.start(); q2.start(); q3.start(); Assert.assertTrue("waiting for server 1 being up", ClientBase.waitForServerUp("127.0.0.1:" + CLIENT_PORT_QP1, CONNECTION_TIMEOUT)); Assert.assertTrue("waiting for server 2 being up", ClientBase.waitForServerUp("127.0.0.1:" + CLIENT_PORT_QP2, CONNECTION_TIMEOUT)); Assert.assertTrue("waiting for server 3 being up", ClientBase.waitForServerUp("127.0.0.1:" + CLIENT_PORT_OBS, CONNECTION_TIMEOUT)); zk = new ZooKeeper("127.0.0.1:" + CLIENT_PORT_OBS, ClientBase.CONNECTION_TIMEOUT, this); zk.create("/obstest", "test".getBytes(),Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); // Assert that commands are getting forwarded correctly Assert.assertEquals(new String(zk.getData("/obstest", null, null)), "test"); // Now check that other commands don't blow everything up zk.sync("/", null, null); zk.setData("/obstest", "test2".getBytes(), -1); zk.getChildren("/", false); Assert.assertEquals(zk.getState(), States.CONNECTED); LOG.info("Shutting down server 2"); // Now kill one of the other real servers q2.shutdown(); Assert.assertTrue("Waiting for server 2 to shut down", ClientBase.waitForServerDown("127.0.0.1:"+CLIENT_PORT_QP2, ClientBase.CONNECTION_TIMEOUT)); LOG.info("Server 2 down"); // Now the resulting ensemble shouldn't be quorate latch.await(); Assert.assertNotSame("Client is still connected to non-quorate cluster", KeeperState.SyncConnected,lastEvent.getState()); LOG.info("Latch returned"); try { Assert.assertFalse("Shouldn't get a response when cluster not quorate!", new String(zk.getData("/obstest", null, null)).equals("test")); } catch (ConnectionLossException c) { LOG.info("Connection loss exception caught - ensemble not quorate (this is expected)"); } latch = new CountDownLatch(1); LOG.info("Restarting server 2"); // Bring it back q2 = new MainThread(2, CLIENT_PORT_QP2, quorumCfgSection); q2.start(); LOG.info("Waiting for server 2 to come up"); Assert.assertTrue("waiting for server 2 being up", ClientBase.waitForServerUp("127.0.0.1:" + CLIENT_PORT_QP2, CONNECTION_TIMEOUT)); LOG.info("Server 2 started, waiting for latch"); latch.await(); // It's possible our session expired - but this is ok, shows we // were able to talk to the ensemble Assert.assertTrue("Client didn't reconnect to quorate ensemble (state was" + lastEvent.getState() + ")", (KeeperState.SyncConnected==lastEvent.getState() || KeeperState.Expired==lastEvent.getState())); LOG.info("Shutting down all servers"); q1.shutdown(); q2.shutdown(); q3.shutdown(); LOG.info("Closing zk client"); zk.close(); Assert.assertTrue("Waiting for server 1 to shut down", ClientBase.waitForServerDown("127.0.0.1:"+CLIENT_PORT_QP1, ClientBase.CONNECTION_TIMEOUT)); Assert.assertTrue("Waiting for server 2 to shut down", ClientBase.waitForServerDown("127.0.0.1:"+CLIENT_PORT_QP2, ClientBase.CONNECTION_TIMEOUT)); Assert.assertTrue("Waiting for server 3 to shut down", ClientBase.waitForServerDown("127.0.0.1:"+CLIENT_PORT_OBS, ClientBase.CONNECTION_TIMEOUT)); } /** * Implementation of watcher interface. */ public void process(WatchedEvent event) { lastEvent = event; latch.countDown(); LOG.info("Latch got event :: " + event); } /** * This test ensures that an Observer does not elect itself as a leader, or * indeed come up properly, if it is the lone member of an ensemble. * @throws Exception */ @Test public void testObserverOnly() throws Exception { ClientBase.setupTestEnv(); final int CLIENT_PORT_QP1 = PortAssignment.unique(); String quorumCfgSection = "server.1=127.0.0.1:" + (PortAssignment.unique()) + ":" + (PortAssignment.unique()) + ":observer\npeerType=observer\n"; MainThread q1 = new MainThread(1, CLIENT_PORT_QP1, quorumCfgSection); q1.start(); q1.join(ClientBase.CONNECTION_TIMEOUT); Assert.assertFalse(q1.isAlive()); } /** * Ensure that observer only comes up when a proper ensemble is configured. * (and will not come up with standalone server). */ @Test public void testObserverWithStandlone() throws Exception { ClientBase.setupTestEnv(); final int CLIENT_PORT_QP1 = PortAssignment.unique(); String quorumCfgSection = "server.1=127.0.0.1:" + (PortAssignment.unique()) + ":" + (PortAssignment.unique()) + ":observer\n" + "server.2=127.0.0.1:" + (PortAssignment.unique()) + ":" + (PortAssignment.unique()) + "\npeerType=observer\n"; MainThread q1 = new MainThread(1, CLIENT_PORT_QP1, quorumCfgSection); q1.start(); q1.join(ClientBase.CONNECTION_TIMEOUT); Assert.assertFalse(q1.isAlive()); } }