/* * Copyright (C) 2012 Facebook, Inc. * * Licensed 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.facebook.zookeeper.election; import com.facebook.testing.MockExecutor; import com.facebook.zookeeper.mock.MockWatcher; import com.facebook.zookeeper.mock.MockZkConnectionManager; import com.facebook.zookeeper.mock.MockZooKeeper; import com.facebook.zookeeper.mock.MockZooKeeperDataStore; import com.facebook.zookeeper.mock.MockZooKeeperFactory; import com.facebook.zookeeper.VariablePayload; import org.apache.zookeeper.CreateMode; import org.apache.zookeeper.KeeperException; import org.apache.zookeeper.WatchedEvent; import org.apache.zookeeper.Watcher.Event.EventType; import org.testng.Assert; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import java.util.List; public class TestZkLeaderElection { private static final String testData1 = "testData1"; private static final String testData2 = "testData2"; private static final String testData3 = "testData3"; private static final String electionRoot = "/root/work/election"; private static final String candidateBaseName = "candidate"; private MockZooKeeperDataStore dataStore; private MockZooKeeperFactory mockZooKeeperFactory; private MockZkConnectionManager mockZkConnectionManager1; private MockZkConnectionManager mockZkConnectionManager2; private MockZkConnectionManager mockZkConnectionManager3; private ZkLeaderElection zkLeaderElection1; private ZkLeaderElection zkLeaderElection2; private ZkLeaderElection zkLeaderElection3; private MockLeaderElectionCallback mockLeaderElectionCallback1; private MockLeaderElectionCallback mockLeaderElectionCallback2; private MockLeaderElectionCallback mockLeaderElectionCallback3; private VariablePayload variablePayload1; private VariablePayload variablePayload2; private VariablePayload variablePayload3; private MockExecutor mockExecutor1; private MockExecutor mockExecutor2; private MockExecutor mockExecutor3; private MockZooKeeper zk1; private MockZooKeeper zk2; private MockZooKeeper zk3; @BeforeMethod(alwaysRun = true) public void setUp() throws Exception { dataStore = new MockZooKeeperDataStore(); mockZooKeeperFactory = new MockZooKeeperFactory(dataStore); mockZkConnectionManager1 = new MockZkConnectionManager(mockZooKeeperFactory); mockZkConnectionManager1.refreshClient(); // Generate a new client zk1 = mockZooKeeperFactory.getLastZooKeeper(); zk1.triggerConnect(); mockZkConnectionManager2 = new MockZkConnectionManager(mockZooKeeperFactory); mockZkConnectionManager2.refreshClient(); // Generate a new client zk2 = mockZooKeeperFactory.getLastZooKeeper(); zk2.triggerConnect(); mockZkConnectionManager3 = new MockZkConnectionManager(mockZooKeeperFactory); mockZkConnectionManager3.refreshClient(); // Generate a new client zk3 = mockZooKeeperFactory.getLastZooKeeper(); zk3.triggerConnect(); // Create ZkLeaderElection1 mockExecutor1 = new MockExecutor(); variablePayload1 = new VariablePayload(testData1); mockLeaderElectionCallback1 = new MockLeaderElectionCallback(); zkLeaderElection1 = new ZkLeaderElection( mockZkConnectionManager1, electionRoot, candidateBaseName, variablePayload1, mockLeaderElectionCallback1, mockExecutor1 ); // Create ZkLeaderElection2 mockExecutor2 = new MockExecutor(); variablePayload2 = new VariablePayload(testData2); mockLeaderElectionCallback2 = new MockLeaderElectionCallback(); zkLeaderElection2 = new ZkLeaderElection( mockZkConnectionManager2, electionRoot, candidateBaseName, variablePayload2, mockLeaderElectionCallback2, mockExecutor2 ); // Create ZkLeaderElection3 mockExecutor3 = new MockExecutor(); variablePayload3 = new VariablePayload(testData3); mockLeaderElectionCallback3 = new MockLeaderElectionCallback(); zkLeaderElection3 = new ZkLeaderElection( mockZkConnectionManager3, electionRoot, candidateBaseName, variablePayload3, mockLeaderElectionCallback3, mockExecutor3 ); // Set up election root zk1.create("/root", null, null, CreateMode.PERSISTENT); zk1.create("/root/work", null, null, CreateMode.PERSISTENT); zk1.create("/root/work/election", null, null, CreateMode.PERSISTENT); } @Test(groups = "fast", expectedExceptions = KeeperException.ConnectionLossException.class) public void testNoConnection() throws Exception { zk1.triggerDisconnect(); zkLeaderElection1.enter(); } @Test(groups = "fast", expectedExceptions = KeeperException.NoNodeException.class) public void testMissingRoot() throws Exception { zk1.delete(electionRoot, -1); zkLeaderElection1.enter(); } @Test(groups = "fast") public void testEnter() throws Exception { // Verify no candidates MockWatcher mockWatcher = new MockWatcher(); List<String> candidates = zk1.getChildren(electionRoot, mockWatcher); Assert.assertTrue(candidates.isEmpty()); // Enter the candidate and run any callbacks zkLeaderElection1.enter(); mockExecutor1.drain(); // Check that candidate is created candidates = zk1.getChildren(electionRoot, null); Assert.assertEquals(candidates.size(), 1); // Check that candidate is elected Assert.assertTrue(mockLeaderElectionCallback1.isElected()); mockLeaderElectionCallback1.resetElected(); Assert.assertEquals(zkLeaderElection1.getLeader(), candidates.get(0)); // Check data contents String data = VariablePayload.decode( zk1.getData(electionRoot + "/" + candidates.get(0), null, null) ); Assert.assertEquals(data, testData1); // Check that external watch was notified of candidate creation Assert.assertEquals(mockWatcher.getEventQueue().size(), 1); WatchedEvent watchedEvent = mockWatcher.getEventQueue().remove(); Assert.assertEquals(watchedEvent.getType(), EventType.NodeChildrenChanged); Assert.assertEquals(watchedEvent.getPath(), electionRoot); } @Test(groups = "fast") public void testMultipleEnter() throws Exception { zkLeaderElection1.enter(); mockExecutor1.drain(); // Check that candidate is elected Assert.assertTrue(mockLeaderElectionCallback1.isElected()); mockLeaderElectionCallback1.resetElected(); zkLeaderElection1.enter(); mockExecutor1.drain(); // Check that candidate is notified of election win again Assert.assertTrue(mockLeaderElectionCallback1.isElected()); mockLeaderElectionCallback1.resetElected(); // Only one candidate should be entered Assert.assertEquals(zk1.getChildren(electionRoot, null).size(), 1); } @Test(groups = "fast") public void testPrematureWithdraw() throws Exception { // No exception should be thrown if the candidate does not exist zkLeaderElection1.withdraw(); mockExecutor1.drain(); } @Test(groups = "fast") public void testWithdraw() throws Exception { zkLeaderElection1.enter(); mockExecutor1.drain(); // Verify candidate MockWatcher mockWatcher = new MockWatcher(); List<String> candidates = zk1.getChildren(electionRoot, mockWatcher); Assert.assertEquals(candidates.size(), 1); Assert.assertTrue(mockLeaderElectionCallback1.isElected()); mockLeaderElectionCallback1.resetElected(); zkLeaderElection1.withdraw(); mockExecutor1.drain(); candidates = zk1.getChildren(electionRoot, null); Assert.assertTrue(candidates.isEmpty()); Assert.assertEquals(zkLeaderElection1.getLeader(), null); // Manual withdraw should not trigger a "removed" callback Assert.assertFalse(mockLeaderElectionCallback1.isRemoved()); Assert.assertEquals(mockWatcher.getEventQueue().size(), 1); WatchedEvent watchedEvent = mockWatcher.getEventQueue().remove(); Assert.assertEquals(watchedEvent.getType(), EventType.NodeChildrenChanged); Assert.assertEquals(watchedEvent.getPath(), electionRoot); } @Test(groups = "fast") public void testMultipleWithdraw() throws Exception { zkLeaderElection1.enter(); mockExecutor1.drain(); zkLeaderElection1.withdraw(); mockExecutor1.drain(); // Check candidate is removed Assert.assertTrue(zk1.getChildren(electionRoot, null).isEmpty()); zkLeaderElection1.withdraw(); mockExecutor1.drain(); // Check that there is still no candidate Assert.assertTrue(zk1.getChildren(electionRoot, null).isEmpty()); } @Test(groups = "fast") public void testCycle() throws Exception { zkLeaderElection1.enter(); mockExecutor1.drain(); // Check that candidate is elected Assert.assertTrue(mockLeaderElectionCallback1.isElected()); mockLeaderElectionCallback1.resetElected(); zkLeaderElection1.cycle(); mockExecutor1.drain(); // Check that candidate is re-elected Assert.assertTrue(mockLeaderElectionCallback1.isElected()); mockLeaderElectionCallback1.resetElected(); // Check no remove was signaled Assert.assertFalse(mockLeaderElectionCallback1.isRemoved()); Assert.assertEquals(zk1.getChildren(electionRoot, null).size(), 1); } @Test(groups = "fast") public void testEarlyCycle() throws Exception { zkLeaderElection1.cycle(); mockExecutor1.drain(); // Check that candidate is elected Assert.assertTrue(mockLeaderElectionCallback1.isElected()); mockLeaderElectionCallback1.resetElected(); // Check no remove was signaled Assert.assertFalse(mockLeaderElectionCallback1.isRemoved()); Assert.assertEquals(zk1.getChildren(electionRoot, null).size(), 1); } @Test(groups = "fast") public void testMultiEnter() throws Exception { zkLeaderElection1.enter(); mockExecutor1.drain(); // Check that candidate1 is elected Assert.assertTrue(mockLeaderElectionCallback1.isElected()); mockLeaderElectionCallback1.resetElected(); zkLeaderElection2.enter(); mockExecutor2.drain(); // Check that candidate2 was not elected Assert.assertFalse(mockLeaderElectionCallback2.isElected()); // Check that both candidates are in the election Assert.assertEquals(zk1.getChildren(electionRoot, null).size(), 2); // Check that they both agree on the winner Assert.assertEquals(zkLeaderElection1.getLeader(), zkLeaderElection2.getLeader()); // Check that the reported winner is candidate1 Assert.assertEquals( VariablePayload.decode(zk1.getData( electionRoot + "/" + zkLeaderElection1.getLeader(), null, null )), testData1 ); } @Test(groups = "fast") public void testMultiCycle() throws Exception { zkLeaderElection1.enter(); mockExecutor1.drain(); // Check that candidate1 is elected Assert.assertTrue(mockLeaderElectionCallback1.isElected()); mockLeaderElectionCallback1.resetElected(); zkLeaderElection2.enter(); mockExecutor2.drain(); // Check that candidate2 was not elected Assert.assertFalse(mockLeaderElectionCallback2.isElected()); zkLeaderElection1.cycle(); mockExecutor1.drain(); mockExecutor2.drain(); // Check that candidate2 was elected and not candidate1 Assert.assertTrue(mockLeaderElectionCallback2.isElected()); mockLeaderElectionCallback2.resetElected(); Assert.assertFalse(mockLeaderElectionCallback1.isElected()); // Check that both candidates are in the election Assert.assertEquals(zk1.getChildren(electionRoot, null).size(), 2); // Check that they both agree on the winner Assert.assertEquals(zkLeaderElection1.getLeader(), zkLeaderElection2.getLeader()); // Check that the reported winner is candidate2 Assert.assertEquals( VariablePayload.decode(zk1.getData( electionRoot + "/" + zkLeaderElection1.getLeader(), null, null )), testData2 ); } @Test(groups = "fast") public void testMultiWithExpire() throws Exception { zkLeaderElection1.enter(); mockExecutor1.drain(); // Check that candidate1 is elected Assert.assertTrue(mockLeaderElectionCallback1.isElected()); mockLeaderElectionCallback1.resetElected(); zkLeaderElection2.enter(); mockExecutor2.drain(); // Check that candidate2 was not elected Assert.assertFalse(mockLeaderElectionCallback2.isElected()); zk1.triggerSessionExpiration(); mockExecutor1.drain(); mockExecutor2.drain(); // Check that candidate2 was elected Assert.assertTrue(mockLeaderElectionCallback2.isElected()); mockLeaderElectionCallback2.resetElected(); // Check that only candidate2 is in the election Assert.assertEquals(zk2.getChildren(electionRoot, null).size(), 1); // Check that the reported winner is candidate2 Assert.assertEquals( VariablePayload.decode(zk2.getData( electionRoot + "/" + zkLeaderElection2.getLeader(), null, null )), testData2 ); } @Test(groups = "fast") public void testAdminRemove() throws Exception { zkLeaderElection1.enter(); mockExecutor1.drain(); // Check that candidate1 is elected Assert.assertTrue(mockLeaderElectionCallback1.isElected()); mockLeaderElectionCallback1.resetElected(); zkLeaderElection2.enter(); mockExecutor2.drain(); // Check that candidate2 was not elected Assert.assertFalse(mockLeaderElectionCallback2.isElected()); // Delete candidate1 via external forces zk1.delete(electionRoot + "/" + zkLeaderElection1.getLeader(), -1); mockExecutor1.drain(); mockExecutor2.drain(); //Check that candidate1 got a removed signal Assert.assertTrue(mockLeaderElectionCallback1.isRemoved()); mockLeaderElectionCallback1.resetRemoved(); // Check that candidate2 was elected Assert.assertTrue(mockLeaderElectionCallback2.isElected()); mockLeaderElectionCallback2.resetElected(); // Check that only candidate2 is in the election Assert.assertEquals(zk2.getChildren(electionRoot, null).size(), 1); // Check that the reported winner is candidate2 Assert.assertEquals( VariablePayload.decode(zk2.getData( electionRoot + "/" + zkLeaderElection2.getLeader(), null, null )), testData2 ); } @Test(groups = "fast") public void testMultiSuccession() throws Exception { zkLeaderElection1.enter(); mockExecutor1.drain(); // Check that candidate1 is elected Assert.assertTrue(mockLeaderElectionCallback1.isElected()); mockLeaderElectionCallback1.resetElected(); zkLeaderElection2.enter(); mockExecutor2.drain(); // Check that candidate2 was not elected Assert.assertFalse(mockLeaderElectionCallback2.isElected()); zkLeaderElection3.enter(); mockExecutor3.drain(); // Check that candidate3 was not elected Assert.assertFalse(mockLeaderElectionCallback3.isElected()); // Check that all candidates are in the election Assert.assertEquals(zk1.getChildren(electionRoot, null).size(), 3); // Check that they all agree on the winner Assert.assertEquals(zkLeaderElection1.getLeader(), zkLeaderElection2.getLeader()); Assert.assertEquals(zkLeaderElection2.getLeader(), zkLeaderElection3.getLeader()); zkLeaderElection2.withdraw(); mockExecutor2.drain(); mockExecutor3.drain(); // Check that no election callbacks happened Assert.assertFalse(mockLeaderElectionCallback1.isElected()); Assert.assertFalse(mockLeaderElectionCallback2.isElected()); Assert.assertFalse(mockLeaderElectionCallback3.isElected()); // Check that there are now two candidates in the election Assert.assertEquals(zk1.getChildren(electionRoot, null).size(), 2); zkLeaderElection1.withdraw(); mockExecutor1.drain(); mockExecutor3.drain(); // Check that candidate3 got the election callback Assert.assertTrue(mockLeaderElectionCallback3.isElected()); mockLeaderElectionCallback3.resetElected(); // Check that the reported winner is candidate3 Assert.assertEquals( VariablePayload.decode(zk1.getData( electionRoot + "/" + zkLeaderElection1.getLeader(), null, null )), testData3 ); } }