/*
* Copyright 2014, The Sporting Exchange Limited
*
* 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.betfair.cougar.transport.nio;
import com.betfair.cougar.core.impl.transports.TransportRegistryImpl;
import com.betfair.cougar.netutil.nio.ClientHandshake;
import com.betfair.cougar.netutil.nio.CougarProtocol;
import com.betfair.cougar.netutil.nio.NioConfig;
import com.betfair.cougar.netutil.nio.NioLogger;
import com.betfair.cougar.netutil.nio.TlsNioConfig;
import com.betfair.cougar.netutil.nio.message.ProtocolMessage;
import com.betfair.cougar.netutil.nio.message.RequestMessage;
import com.betfair.cougar.netutil.nio.message.SuspendMessage;
import com.betfair.cougar.transport.api.TransportCommandProcessor;
import com.betfair.cougar.transport.api.protocol.CougarObjectIOFactory;
import com.betfair.cougar.netutil.nio.hessian.HessianObjectIOFactory;
import com.betfair.cougar.transport.socket.SocketTransportCommand;
import com.betfair.cougar.transport.socket.SocketTransportCommandProcessor;
import org.apache.mina.common.ConnectFuture;
import org.apache.mina.common.IoHandler;
import org.apache.mina.common.IoHandlerAdapter;
import org.apache.mina.common.IoSession;
import org.apache.mina.transport.socket.nio.SocketConnector;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import static com.betfair.cougar.transport.nio.SessionTestUtil.newV1Session;
import static com.betfair.cougar.transport.nio.SessionTestUtil.newV2Session;
import static com.betfair.cougar.transport.nio.SessionTestUtil.newV3Session;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.isA;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
/**
*/
public class CougarProtocolTest {
private TlsNioConfig defaultServerConfig;
private TlsNioConfig defaultClientConfig;
@Before
public void setup() {
defaultServerConfig = new TlsNioConfig();
defaultServerConfig.setNioLogger(new NioLogger("ALL"));
defaultServerConfig.setListenAddress("127.0.0.1");
defaultServerConfig.setListenPort(2227);
defaultServerConfig.setReuseAddress(true);
defaultServerConfig.setTcpNoDelay(true);
defaultServerConfig.setKeepAliveInterval(Integer.MAX_VALUE);
defaultServerConfig.setKeepAliveTimeout(Integer.MAX_VALUE);
defaultClientConfig = new TlsNioConfig();
defaultClientConfig.setNioLogger(new NioLogger("ALL"));
defaultClientConfig.setReuseAddress(true);
defaultClientConfig.setTcpNoDelay(true);
defaultClientConfig.setKeepAliveInterval(Integer.MAX_VALUE);
defaultClientConfig.setKeepAliveTimeout(Integer.MAX_VALUE);
}
public ExecutionVenueNioServer createServer(TlsNioConfig cfg) {
ExecutionVenueNioServer server = new ExecutionVenueNioServer();
server.setNioConfig(cfg);
NioLogger sessionLogger = new NioLogger("ALL");
TransportCommandProcessor<SocketTransportCommand> processor = new SocketTransportCommandProcessor();
CougarObjectIOFactory objectIOFactory = new HessianObjectIOFactory(false);
ExecutionVenueServerHandler serverHandler = new ExecutionVenueServerHandler(sessionLogger, processor, objectIOFactory) {
@Override
public void messageReceived(IoSession session, Object message) throws Exception {
session.write(message);
}
};
server.setServerHandler(serverHandler);
server.setServerExecutor(Executors.newCachedThreadPool());
server.setSocketAcceptorProcessors(1);
server.setTransportRegistry(new TransportRegistryImpl());
final IoSessionManager sessionManager = new IoSessionManager();
server.setSessionManager(sessionManager);
sessionManager.setMaxTimeToWaitForRequestCompletion(5000);
sessionManager.setNioLogger(sessionLogger);
return server;
}
public IoSession createClient(NioConfig cfg, IoHandler handler) throws IOException {
SocketConnector sc = new SocketConnector();
ConnectFuture cf = sc.connect(new InetSocketAddress("127.0.0.1", 2227), handler, cfg.configureSocketSessionConfig());
cf.join();
return cf.getSession();
}
@Test
public void testConnect() throws IOException {
boolean success = false;
ExecutionVenueNioServer server = createServer(defaultServerConfig);
try {
server.start();
server.setHealthState(true);
IoSession session = createClient(defaultClientConfig, new IoHandlerAdapter() {
});
ClientHandshake handshake = (ClientHandshake) session.getAttribute(ClientHandshake.HANDSHAKE);
handshake.await(10000);
success = handshake.successful();
session.close();
}
finally {
server.stop();
}
assertEquals("connection was not successful", true, success);
}
@Test
public void testRejectionServerUnhealthy() throws IOException {
ExecutionVenueNioServer server = createServer(defaultServerConfig);
server.start();
server.setHealthState(false);
IoSession session = createClient(defaultClientConfig, new IoHandlerAdapter());
ClientHandshake handshake = (ClientHandshake) session.getAttribute(ClientHandshake.HANDSHAKE);
handshake.await(10000);
boolean success = handshake.successful();
session.close();
server.stop();
assertEquals("connection shouln't have been successful", false, success);
}
@Test
public void testKeepAliveNotRecieved() throws InterruptedException, IOException {
defaultServerConfig.setKeepAliveInterval(2);
defaultServerConfig.setKeepAliveTimeout(1);
ExecutionVenueNioServer server = createServer(defaultServerConfig);
server.start();
server.setHealthState(true);
defaultClientConfig.setKeepAliveInterval(2);
defaultClientConfig.setKeepAliveTimeout(1);
final CountDownLatch cdl = new CountDownLatch(1);
IoSession ioSession = createClient(defaultClientConfig, new IoHandlerAdapter() {
@Override
public void sessionClosed(IoSession session) throws Exception {
cdl.countDown();
}
});
boolean closed = cdl.await(20000, TimeUnit.MILLISECONDS);
ioSession.close();
server.stop();
assertEquals("Expecting session to be closed", true, closed);
}
@Test
public void testKeepAlive() throws IOException, InterruptedException {
defaultServerConfig.setKeepAliveInterval(1);
defaultServerConfig.setKeepAliveTimeout(2);
ExecutionVenueNioServer server = createServer(defaultServerConfig);
server.start();
server.setHealthState(true);
defaultClientConfig.setKeepAliveInterval(1);
defaultServerConfig.setKeepAliveTimeout(2);
final CountDownLatch cdl = new CountDownLatch(1);
IoSession ioSession = createClient(defaultClientConfig, new IoHandlerAdapter() {
@Override
public void sessionClosed(IoSession session) throws Exception {
cdl.countDown();
}
});
boolean closed = cdl.await(3, TimeUnit.SECONDS);
ioSession.close();
server.stop();
assertEquals("session closed unexpected", false, closed);
}
@Test
public void testReject() throws IOException {
// force version to an unsupported one (the next one)
CougarProtocol.setMinClientProtocolVersion((byte) (CougarProtocol.TRANSPORT_PROTOCOL_VERSION_MAX_SUPPORTED + 1));
CougarProtocol.setMaxClientProtocolVersion((byte) (CougarProtocol.TRANSPORT_PROTOCOL_VERSION_MAX_SUPPORTED + 1));
try {
TlsNioConfig nioConfig = new TlsNioConfig();
nioConfig.setNioLogger(new NioLogger("ALL"));
nioConfig.setListenAddress("127.0.0.1");
nioConfig.setListenPort(2227);
nioConfig.setReuseAddress(true);
nioConfig.setTcpNoDelay(true);
nioConfig.setKeepAliveInterval(Integer.MAX_VALUE);
nioConfig.setKeepAliveTimeout(Integer.MAX_VALUE);
ExecutionVenueNioServer server = createServer(nioConfig);
server.start();
server.setHealthState(true);
IoSession ioSession = createClient(defaultClientConfig, new IoHandlerAdapter());
ClientHandshake handshake = (ClientHandshake) ioSession.getAttribute(ClientHandshake.HANDSHAKE);
handshake.await(10000);
boolean success = handshake.successful();
ioSession.close();
server.stop();
assertEquals("connection shouldn't have been successful", false, success);
} finally {
CougarProtocol.setMinClientProtocolVersion(CougarProtocol.TRANSPORT_PROTOCOL_VERSION_MAX_SUPPORTED);
CougarProtocol.setMaxClientProtocolVersion(CougarProtocol.TRANSPORT_PROTOCOL_VERSION_MAX_SUPPORTED);
}
}
@Test
public void testDisconnect() throws IOException, InterruptedException {
ExecutionVenueNioServer server = createServer(defaultServerConfig);
boolean closed = false;
try {
server.start();
server.setHealthState(true);
final CountDownLatch cdl = new CountDownLatch(1);
IoSession ioSession = createClient(defaultClientConfig, new IoHandlerAdapter() {
@Override
public void sessionClosed(IoSession session) throws Exception {
cdl.countDown();
}
});
ClientHandshake handshake = (ClientHandshake) ioSession.getAttribute(ClientHandshake.HANDSHAKE);
handshake.await(1000);
boolean success = handshake.successful();
assertEquals("connection should have been successful", true, success);
server.setHealthState(false);
closed = cdl.await(50, TimeUnit.SECONDS);
ioSession.close();
}
finally {
server.stop();
}
assertEquals("expected session to close due to disconnection", true, closed);
}
@Test
public void testGracefulDisconnect() throws IOException, InterruptedException {
ExecutionVenueNioServer server = createServer(defaultServerConfig);
server.start();
server.setHealthState(true);
final CountDownLatch cdl = new CountDownLatch(1);
IoSession ioSession = createClient(defaultClientConfig, new IoHandlerAdapter() {
@Override
public void sessionClosed(IoSession session) throws Exception {
cdl.countDown();
}
});
ClientHandshake handshake = (ClientHandshake) ioSession.getAttribute(ClientHandshake.HANDSHAKE);
handshake.await(1000);
boolean success = handshake.successful();
assertEquals("connection should have been successful", true, success);
// write some dummy request
ioSession.write(new RequestMessage(1, "request".getBytes()));
server.setHealthState(false);
boolean closed = cdl.await(50, TimeUnit.SECONDS);
// Suspend message should have been recieved
assertTrue(ioSession.containsAttribute(ProtocolMessage.ProtocolMessageType.SUSPEND.name()));
// Disconnect message should have been recieved
assertTrue(ioSession.containsAttribute(ProtocolMessage.ProtocolMessageType.DISCONNECT.name()));
// Session should have been disconnected
assertFalse(ioSession.isConnected());
// teardown
ioSession.close();
server.stop();
assertEquals("expected session to close due to disconnection", true, closed);
}
@Test
public void testSuspendMessagesAreSkippedForV1Sessions() {
CougarProtocol protocol = CougarProtocol.getServerInstance(new NioLogger("NONE"), 5000, 5000, null, false, false);
IoSession ioSession = newV1Session();
protocol.suspendSession(ioSession);
verify(ioSession, never()).write(isA(SuspendMessage.class));
}
@Test
public void testSuspendMessagesAreWrittenForV2Sessions() {
CougarProtocol protocol = CougarProtocol.getServerInstance(new NioLogger("NONE"), 5000, 5000, null, false, false);
IoSession ioSession = newV2Session();
protocol.suspendSession(ioSession);
verify(ioSession).write(isA(SuspendMessage.class));
}
@Test
public void testSuspendMessagesAreWrittenForV3Sessions() {
CougarProtocol protocol = CougarProtocol.getServerInstance(new NioLogger("NONE"), 5000, 5000, null, false, false);
IoSession ioSession = newV3Session();
protocol.suspendSession(ioSession);
verify(ioSession).write(isA(SuspendMessage.class));
}
}