package com.linkedin.databus.groupleader.impl.zkclient;
/*
*
* Copyright 2013 LinkedIn Corp. All rights reserved
*
* 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.
*
*/
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertTrue;
import static org.testng.Assert.fail;
import java.io.File;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import org.I0Itec.zkclient.ZkClient;
import org.I0Itec.zkclient.ZkServer;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.testng.Assert;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import com.linkedin.databus.core.util.FileUtils;
import com.linkedin.databus.core.util.Utils;
import com.linkedin.databus.groupleader.pub.AcceptLeadershipCallback;
import com.linkedin.databus.groupleader.pub.GroupLeadershipConnection;
import com.linkedin.databus.groupleader.pub.GroupLeadershipConnectionFactory;
import com.linkedin.databus.groupleader.pub.GroupLeadershipSession;
import com.linkedin.databus2.test.TestUtil;
/*
* IMPORTANT NOTE : If you add any new test case methods, make sure you wrap it with LockObject.
* This class is NOT thread-safe without it but TestNg expects the test class to be thread-safe.
*/
public class TestLeaderElect
{
private static final Logger LOG = Logger.getLogger(TestLeaderElect.class);
public static final Object LockObject = new Object();
public static String getRequiredStringProperty(String propname)
{
String val = System.getProperty(propname);
if (val == null)
{
throw new IllegalArgumentException("Missing property: " + propname);
}
LOG.info("Property " + propname + "=" + val);
return val;
}
public static int getRequiredIntProperty(String propname)
{
String stringVal = getRequiredStringProperty(propname);
int intVal = Integer.parseInt(stringVal);
return intVal;
}
public static boolean getRequiredBooleanProperty(String propname)
{
String stringVal = getRequiredStringProperty(propname);
boolean booleanVal = Boolean.parseBoolean(stringVal);
return booleanVal;
}
protected boolean _startLocalZookeeper;
protected String _zkTestDataParentDir;
protected String _zkTestDataRootDir;
protected String _zkServerList;
protected int _sessionTimeoutMillis;
protected int _connectTimeoutMillis;
protected int _zkServerTickTime = 2000;
protected List<ZkServer> _localZkServers;
protected GroupLeadershipConnectionFactory _groupLeadershipConnFactory;
protected GroupLeadershipConnection _adminLeadershipConn;
protected ZkClient _adminZkClient;
@BeforeClass
public void classSetup()
{
TestUtil.setupLoggingWithTimestampedFile(true, "/tmp/TestLeaderElect_", ".log",
Level.INFO);
}
protected void setUp() throws Exception
{
//temporary
System.setProperty("startLocalZookeeper","true");
//.setProperty("zkServerList","localhost:2191,localhost:3192,localhost:4193");
final int zkPort = Utils.getAvailablePort(2191);
File zkroot = FileUtils.createTempDir("TestLeaderElect_zkroot");
LOG.info("starting ZK on port " + zkPort + " and datadir " + zkroot.getAbsolutePath());
System.setProperty("zkServerList","localhost:" + zkPort);
System.setProperty("sessionTimeoutMillis","10000");
System.setProperty("connectTimeoutMillis","5000");
System.setProperty("zkTestDataParentDir", zkroot.getAbsolutePath());
_startLocalZookeeper = getRequiredBooleanProperty("startLocalZookeeper");
_zkTestDataParentDir = getRequiredStringProperty("zkTestDataParentDir");
_zkServerList = getRequiredStringProperty("zkServerList");
_sessionTimeoutMillis = getRequiredIntProperty("sessionTimeoutMillis");
_connectTimeoutMillis = getRequiredIntProperty("connectTimeoutMillis");
if (_startLocalZookeeper)
{
_zkTestDataParentDir = _zkTestDataParentDir.trim();
if (_zkTestDataParentDir.length() == 0)
{
throw new IllegalArgumentException("You must specify a valid zkTestDataParentDir when startLocalZookeeper=true");
}
File zkTestDataParentDirFile = new File(_zkTestDataParentDir);
if (!zkTestDataParentDirFile.exists())
{
throw new IllegalArgumentException("Specified zkTestDataParentDir does not exist: " + _zkTestDataParentDir);
}
_zkTestDataRootDir = _zkTestDataParentDir + "/zkroot";
List<Integer> localPortsList = LeaderElectUtils.parseLocalPorts(_zkServerList);
if (localPortsList.size() > 1)
{
throw new IllegalArgumentException("Currently we only suppport starting ONE local zookeeper server");
}
_localZkServers = LeaderElectUtils.startLocalZookeeper(localPortsList, _zkTestDataRootDir, _zkServerTickTime);
}
_groupLeadershipConnFactory = new GroupLeadershipConnectionFactoryZkClientImpl(
_zkServerList,
_sessionTimeoutMillis,
_connectTimeoutMillis);
_adminLeadershipConn = _groupLeadershipConnFactory.getConnection();
_adminZkClient = ((GroupLeadershipConnectionZkClientImpl) _adminLeadershipConn).getZkClient();
}
protected void tearDown() throws Exception
{
if (null != _adminLeadershipConn)
{
_adminLeadershipConn.close();
}
if (_startLocalZookeeper && null != _localZkServers)
{
LeaderElectUtils.stopLocalZookeeper(_localZkServers);
}
}
protected String cleanZkGroupInfo(String zkBasePath, String groupName)
{
String groupNodePath = GroupLeadershipConnectionZkClientImpl.makeGroupNodePath(zkBasePath,groupName);
_adminZkClient.deleteRecursive(groupNodePath);
return groupNodePath;
}
/*
* IMPORTANT NOTE : If you add any new test case methods, make sure you wrap it with LockObject.
* This class is NOT thread-safe without it but TestNg expects the test class to be thread-safe.
*/
@Test
public void testLeaderElectSimple() throws Exception
{
synchronized(LockObject)
{
try
{
setUp();
String baseName = "/databus2.testGroupLeader";
String groupName = "TestLeaderElect.testLeaderElectSimple";
cleanZkGroupInfo(baseName,groupName);
final AtomicReference<String> callerMaintainedCurrentLeaderRef = new AtomicReference<String>();
AcceptLeadershipCallback saveCurrentLeaderCallback =
new AcceptLeadershipCallback()
{
@Override
public void doAcceptLeadership(GroupLeadershipSession groupLeadershipSession)
{
callerMaintainedCurrentLeaderRef.set(groupLeadershipSession.getMemberName());
}
};
GroupLeadershipConnection conn002 = _groupLeadershipConnFactory.getConnection();
Assert.assertNull(conn002.getLeaderName(baseName,groupName),"Leader should be null");
LOG.info("Group should not exist yet Test");
assertFalse(conn002.getGroupNames(baseName).contains(groupName));
GroupLeadershipSession sess002 = conn002.joinGroup(baseName,
groupName, "002", saveCurrentLeaderCallback);
assertEquals(groupName, sess002.getGroupName());
assertEquals("002", sess002.getMemberName());
assertEquals("002", (sess002.getLeaderName()));
LOG.info("002 should be leader Test");
assertFalse( ! sess002.isLeader());
assertEquals("002", callerMaintainedCurrentLeaderRef.get());
LOG.info("Group should exist Test");
assertFalse(! conn002.getGroupNames(baseName).contains(groupName));
assertEquals(groupName, conn002.getGroupLeadershipInfo(baseName,groupName).getGroupName());
assertEquals("002", (conn002.getGroupLeadershipInfo(baseName,groupName).getLeaderName()));
assertEquals(1, conn002.getGroupLeadershipInfo(baseName,groupName).getMemberNames().size());
System.err.printf("membership info=%s\n", conn002.getGroupLeadershipInfo(baseName, groupName).getMemberNames());
LOG.info("Member should be in group Test");
assertFalse(!conn002.getGroupLeadershipInfo(baseName,groupName).getMemberNames().contains("002"));
GroupLeadershipConnection conn001 = _groupLeadershipConnFactory.getConnection();
GroupLeadershipSession sess001 = conn001.joinGroup(baseName,
groupName, "001", saveCurrentLeaderCallback);
assertEquals(groupName, sess001.getGroupName());
assertEquals("001", sess001.getMemberName());
assertEquals("002", (sess001.getLeaderName()));
LOG.info("001 should not be leader");
assertFalse(sess001.isLeader());
assertEquals("002", callerMaintainedCurrentLeaderRef.get());
LOG.info("Group should exist");
assertFalse(! conn001.getGroupNames(baseName).contains(groupName));
assertEquals(groupName, conn001.getGroupLeadershipInfo(baseName,groupName).getGroupName());
assertEquals("002", (conn001.getGroupLeadershipInfo(baseName,groupName).getLeaderName()));
assertEquals(2, conn001.getGroupLeadershipInfo(baseName,groupName).getMemberNames().size());
LOG.info("Member should be in group");
assertFalse(! conn001.getGroupLeadershipInfo(baseName,groupName).getMemberNames().contains("001"));
LOG.info("Member should be in group");
assertFalse(!conn001.getGroupLeadershipInfo(baseName,groupName).getMemberNames().contains("002"));
GroupLeadershipConnection conn003 = _groupLeadershipConnFactory.getConnection();
GroupLeadershipSession sess003 = conn003.joinGroup(baseName,
groupName, "003", saveCurrentLeaderCallback);
assertEquals(groupName, sess003.getGroupName());
assertEquals("003", sess003.getMemberName());
assertEquals("002", (sess003.getLeaderName()));
LOG.info("003 should not be leader");
assertFalse( sess003.isLeader());
assertEquals("002", callerMaintainedCurrentLeaderRef.get());
LOG.info("Group should exist");
assertTrue(conn003.getGroupNames(baseName).contains(groupName));
assertEquals(groupName, conn003.getGroupLeadershipInfo(baseName,groupName).getGroupName());
assertEquals("002", (conn003.getGroupLeadershipInfo(baseName,groupName).getLeaderName()));
assertEquals(3, conn003.getGroupLeadershipInfo(baseName,groupName).getMemberNames().size());
LOG.info("Member should be in group");
assertTrue(conn003.getGroupLeadershipInfo(baseName,groupName).getMemberNames().contains("001"));
assertTrue(conn003.getGroupLeadershipInfo(baseName,groupName).getMemberNames().contains("002"));
assertTrue(conn003.getGroupLeadershipInfo(baseName,groupName).getMemberNames().contains("003"));
sess002.leaveGroup();
for (int i = 0; i < 60; i++)
{
if ("001".equals((_adminLeadershipConn.getGroupLeadershipInfo(baseName,groupName).getLeaderName())))
{
break;
}
Thread.sleep(500);
}
assertEquals(groupName, sess001.getGroupName());
assertEquals("001", sess001.getMemberName());
assertEquals("001", (sess001.getLeaderName()));
LOG.info("001 should be leader");
assertTrue(sess001.isLeader());
assertEquals("001", callerMaintainedCurrentLeaderRef.get());
LOG.info("Group should exist");
assertTrue(_adminLeadershipConn.getGroupNames(baseName).contains(groupName));
assertEquals(groupName, _adminLeadershipConn.getGroupLeadershipInfo(baseName,groupName).getGroupName());
assertEquals("001",(_adminLeadershipConn.getGroupLeadershipInfo(baseName,groupName).getLeaderName()));
assertEquals(2, _adminLeadershipConn.getGroupLeadershipInfo(baseName,groupName).getMemberNames().size());
LOG.info("Member should be in group");
assertTrue(_adminLeadershipConn.getGroupLeadershipInfo(baseName,groupName).getMemberNames().contains("001"));
assertTrue(_adminLeadershipConn.getGroupLeadershipInfo(baseName,groupName).getMemberNames().contains("003"));
assertEquals(groupName, sess003.getGroupName());
assertEquals("003", sess003.getMemberName());
assertEquals("001", sess003.getLeaderName());
LOG.info("003 should not be leader");
assertFalse(sess003.isLeader());
try
{
sess002.leaveGroup();
fail("Should have gotten IllegalStateException");
}
catch (IllegalStateException e)
{
// expected
}
sess003.leaveGroup();
sess001.leaveGroup();
conn003.close();
conn002.close();
conn001.close();
} finally {
tearDown();
}
}
}
}