/**
* Copyright 2014 Comcast Cable Communications Management, LLC
*
* 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.comcast.viper.flume2storm.zookeeper;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import junit.framework.Assert;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.server.ZooKeeperServer;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.comcast.viper.flume2storm.utility.forwarder.TCPForwarder;
import com.comcast.viper.flume2storm.utility.forwarder.TCPForwarderBuilder;
import com.comcast.viper.flume2storm.utility.test.TestCondition;
import com.comcast.viper.flume2storm.utility.test.TestUtils;
/**
* This tests the state machine of the {@link ZkClient}. We use a listener in
* order to test the recovery mechanism, through the creation of an ephemeral
* node on init
*/
public class ZkClientStateMachineTest extends ZkClientTestUtils {
protected static final Logger LOG = LoggerFactory.getLogger(ZkClientStateMachineTest.class);
protected static final int FORWARDER_PORT = TestUtils.getAvailablePort();
private MyZkClientListener listener;
private TCPForwarder forwarder;
private ZkTestServer zkServer;
protected static class MyZkClientListener implements ZkClientListener {
protected static final Logger LOG = LoggerFactory.getLogger(MyZkClientListener.class);
protected static final String NODE_PATH = "/test";
protected static final byte[] NODE_DATA = "test".getBytes();
protected final AtomicInteger initialization = new AtomicInteger(0);
protected final AtomicInteger temrination = new AtomicInteger(0);
protected final AtomicInteger connection = new AtomicInteger(0);
protected final AtomicInteger disconnection = new AtomicInteger(0);
protected final AtomicBoolean nodeExitsOnConnection = new AtomicBoolean(false);
private static ZkOperation zkOps() {
return new ZkOperation(zkClient, NODE_PATH);
}
@Override
public void initialize() {
LOG.debug("initialize!");
initialization.incrementAndGet();
// Creating node on init, but only if it's not already there
if (!nodeExitsOnConnection.get()) {
try {
LOG.debug("Creating ephemeral node");
zkOps().createNode(new ZkNodeCreationArg().setData(NODE_DATA).setCreateMode(CreateMode.EPHEMERAL));
} catch (final Exception e) {
Assert.fail(e.getMessage());
}
}
}
@Override
public void terminate() {
LOG.debug("terminate!");
temrination.incrementAndGet();
try {
LOG.debug("Deleting ephemeral node");
zkOps().deleteNode();
} catch (final Exception e) {
Assert.fail(e.getMessage());
}
}
@Override
public void onConnection() {
LOG.debug("onConnection!");
connection.incrementAndGet();
// testing if node was created on connection
try {
final boolean nodeExists = zkOps().nodeExists();
LOG.debug("On connection, node {} exists returns {}", NODE_PATH, nodeExists);
nodeExitsOnConnection.set(nodeExists);
} catch (final Exception e) {
Assert.fail(e.getMessage());
}
}
@Override
public void onDisconnection() {
LOG.debug("onDisconnection!");
disconnection.incrementAndGet();
}
public void assertStatus(final int nbConnection, final int nbInitialization, final int nbTermination,
final int nbDisconnection, final boolean checkNodeExists, final Boolean nodeExistsOnConnection)
throws Exception {
Assert.assertEquals(nbConnection, connection.get());
Assert.assertEquals(nbInitialization, initialization.get());
Assert.assertEquals(nbTermination, temrination.get());
Assert.assertEquals(nbDisconnection, disconnection.get());
if (checkNodeExists) {
Assert.assertTrue(zkOps().nodeExists());
}
if (nodeExistsOnConnection != null) {
Assert.assertEquals(nodeExistsOnConnection.booleanValue(), this.nodeExitsOnConnection.get());
}
}
}
@Before
public void init() throws Exception {
final TCPForwarderBuilder forwarderBuilder = new TCPForwarderBuilder();
forwarderBuilder.setListenAddress(HOST).setInputPort(FORWARDER_PORT).setOutputServer(HOST).setOutputPort(PORT);
forwarder = forwarderBuilder.build();
forwarder.start();
while (!forwarder.isActive()) {
Thread.sleep(100);
}
zkServer = new ZkTestServer(PORT);
listener = new MyZkClientListener();
zkClient = new ZkClient(listener);
ZkClientConfiguration zkConfig = new ZkClientConfiguration();
zkConfig.setConnectionStr(HOST + ":" + FORWARDER_PORT);
zkConfig.setSessionTimeout(2 * ZooKeeperServer.DEFAULT_TICK_TIME);
zkClient.configure(zkConfig);
assertZkClientStopped();
listener.assertStatus(0, 0, 0, 0, false, null);
}
@After
public void terminate() throws Exception {
forwarder.stop();
zkServer.stop();
zkServer.cleanup();
}
// TODO test invalid server? (disconnected after connecting)
// TODO restore this part
@Ignore
@Test
public void testClientStates() throws Exception {
LOG.info("\n###\n### Test step: Starting client (no Zk server)...");
Assert.assertTrue(zkClient.start());
waitZkClientStarted();
listener.assertStatus(0, 0, 0, 0, false, null);
LOG.info("\n###\n### Test step: Starting client twice...");
Assert.assertFalse(zkClient.start());
assertZkClientStarted();
listener.assertStatus(0, 0, 0, 0, false, null);
Thread.sleep(A_LITTLE);
LOG.info("\n###\n### Test step: Stopping client...");
Assert.assertTrue(zkClient.stop());
assertZkClientStopped();
listener.assertStatus(0, 0, 0, 0, false, null);
LOG.info("\n###\n### Test step: Stopping client twice...");
Assert.assertFalse(zkClient.stop());
assertZkClientStopped();
listener.assertStatus(0, 0, 0, 0, false, null);
LOG.info("\n###\n### Test step: Restarting client (still no Zk server)...");
Assert.assertTrue(zkClient.start());
waitZkClientStarted();
listener.assertStatus(0, 0, 0, 0, false, null);
LOG.info("\n###\n### Test step: Starting ZK...");
zkServer.start();
LOG.info("\n###\n### Test step: Waiting that the ZkClient connects the server...");
waitZkClientConnected();
waitZkClientSetup(60000);
listener.assertStatus(1, 1, 0, 0, true, false);
Thread.sleep(A_LITTLE);
LOG.info("\n###\n### Test step: Stopping client...");
Assert.assertTrue(zkClient.stop());
assertZkClientStopped();
listener.assertStatus(1, 1, 1, 1, false, null);
LOG.info("\n###\n### Test step: Restarting client...");
Assert.assertTrue(zkClient.start());
waitZkClientSetup();
listener.assertStatus(2, 2, 1, 1, true, false);
LOG.info("\n###\n### Test step: Stopping ZK Server for a small inturruption (< session timeout)...");
zkServer.stop();
waitZkClientDisconnected();
// Programming note: The disconnection call back happens after the state
// change and therefore, we need to wait a little extra
Assert.assertTrue(TestUtils.waitFor(new TestCondition() {
@Override
public boolean evaluate() {
return listener.disconnection.get() == 2;
}
}, TEST_TIMEOUT));
listener.assertStatus(2, 2, 1, 2, false, null);
Thread.sleep(A_LITTLE);
LOG.info("\n###\n### Test step: Restarting ZK Server before session times out...");
zkServer.start();
waitZkClientSetup(TEST_TIMEOUT);
listener.assertStatus(3, 2, 1, 2, true, true);
// Recording session timeout
final Integer sessionTO = zkClient.getNegotiatedSessionTimeout();
Assert.assertTrue(sessionTO >= ZooKeeperServer.DEFAULT_TICK_TIME);
LOG.info("\n###\n### Test step: Stopping ZK Server for a long inturruption (> session timeout)...");
zkServer.stop();
waitZkClientDisconnected();
// Programming note: The disconnection call back happens after the state
// change and therefore, we need to wait a little extra
Assert.assertTrue(TestUtils.waitFor(new TestCondition() {
@Override
public boolean evaluate() {
return listener.disconnection.get() == 3;
}
}, TEST_TIMEOUT));
listener.assertStatus(3, 2, 1, 3, false, null);
LOG.info("Waiting {} ms", sessionTO * 2);
Thread.sleep(sessionTO * 2);
LOG.info("\n###\n### Test step: Restarting ZK Server after session times out...");
zkServer.start();
waitZkClientSetup(TEST_TIMEOUT);
listener.assertStatus(4, 2, 1, 3, true, true);
LOG.info("\n###\n### Test step: Freezing proxy between client and server for a little while (< Session TO)...");
forwarder.freeze();
Thread.sleep(A_LITTLE);
forwarder.resume();
Assert.assertTrue(MyZkClientListener.zkOps().nodeExists());
listener.assertStatus(4, 2, 1, 3, true, true);
LOG.info("\n###\n### Test step: Freezing proxy between client and server for a longer while (> Session TO)...");
forwarder.freeze();
Thread.sleep(sessionTO * 2);
assertZkClientStarted();
listener.assertStatus(4, 2, 1, 4, false, null);
forwarder.resume();
waitZkClientSetup(TEST_TIMEOUT * 5);
listener.assertStatus(5, 3, 1, 4, true, false);
LOG.info("\n###\n### Test step: Terminating test...");
zkClient.stop();
}
}