/**
* Copyright (C) 2015-2016 Intel Corporation.
*
* @author: Andrew Brown <andrew.brown@intel.com>
* @author: Wei Yu <w.yu@intel.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* A copy of the GNU Lesser General Public License is in the file COPYING.
*/
package net.named_data.jndn.tests;
import net.named_data.jndn.transport.AsyncTcpTransport;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
public class TestAsyncTcpTransport {
private static final Logger LOGGER = Logger.getLogger(TestAsyncTcpTransport.class.getName());
private static final int PORT = 3098;
private static int count = 0;
private static ServerSocket server = null;
public static void main(String[] args) {
/* disable logging from AsyncTcpTransport to not pollute the console output*/
Logger.getLogger(AsyncTcpTransport.class.getName()).setLevel(Level.OFF);
TestAsyncTcpTransport instance = new TestAsyncTcpTransport();
instance.testHappyPath();
instance.testWithoutServerInitiallyOn();
instance.testWithServerRebootWhileIdling();
instance.testWithServerRebootWhileSending();
LOGGER.info("Tests complete");
}
/**
* Test that reconnection logic does not break normal operation, i.e. make sure we didn't break the happy path
*/
public void testHappyPath() {
// start the mock nfd first
startMockNfd(PORT);
// reset the count
count = 0;
// create pool for tcp transport
ScheduledExecutorService pool = Executors.newScheduledThreadPool(3);
// create and start tcp transport
AsyncTcpTransport transport = createAndStartClient(pool);
// start sending pings
ScheduledExecutorService pool2 = Executors.newSingleThreadScheduledExecutor();
send(pool2, transport, "ping");
// allow enough time to receive some ping
for (int i = 0; i < 10; i++) {
sleep(1000);
if (count > 0) {
break;
}
}
// stop server
stopMockNfd(transport);
// shutdown the pools
shutdownPool(pool);
shutdownPool(pool2);
// make sure we received some pings
assert(count > 0);
}
/**
* Test to mimic when the NFD is not up initially (but then starts) and the transport attempts to open and send data
*/
public void testWithoutServerInitiallyOn() {
// create the pool
ScheduledExecutorService pool = Executors.newScheduledThreadPool(3);
// create and start the transport
AsyncTcpTransport transport = createAndStartClient(pool);
// start sending pings
ScheduledExecutorService pool2 = Executors.newSingleThreadScheduledExecutor();
send(pool2, transport, "ping1");
// now start the mock nfd
startMockNfd(PORT);
// reset count so we know if count > 0 the transport reconnects successfully
count = 0;
// allow reconnect enough time to complete
for (int i = 0; i < 10; i++) {
sleep(1000);
if (count > 0) {
break;
}
}
// stop server
stopMockNfd(transport);
// shutdown the pools
shutdownPool(pool);
shutdownPool(pool2);
// we have received some pings
assert(count > 0);
}
/**
* This tests a client constantly sending data and the reboot of mock NFD which triggers the reconnect
*/
public void testWithServerRebootWhileSending() {
// start the mock nfd
startMockNfd(PORT);
// create the pool for transport
ScheduledExecutorService pool = Executors.newScheduledThreadPool(3);
// create and start the transport
AsyncTcpTransport transport = createAndStartClient(pool);
// start sending pings
ScheduledExecutorService pool2 = Executors.newSingleThreadScheduledExecutor();
send(pool2, transport, "ping2");
// reboot the mock nfd by closing the current channel
stopMockNfd(transport);
// reset the count so if it is > 0 we know the reconnect is success
count = 0;
// allow reconnect enough time to complete
for (int i = 0; i < 10; i++) {
sleep(1000);
if (count > 0) {
break;
}
}
// shutdown the server
stopMockNfd(transport);
// cleanup pools
shutdownPool(pool);
shutdownPool(pool2);
// make sure we received some pings
assert(count > 0);
}
/**
* this is to test reboot the nfd while client is not sending any data and client will reconnect after the next few
* send request
*/
public void testWithServerRebootWhileIdling() {
// start the mock nfd
startMockNfd(PORT);
// create the pool for transport
ScheduledExecutorService pool = Executors.newScheduledThreadPool(3);
// create and start the transport
AsyncTcpTransport transport = createAndStartClient(pool);
// start sending pings
ScheduledExecutorService pool2 = Executors.newSingleThreadScheduledExecutor();
send(pool2, transport, "ping3");
// shutdown the send data pool
shutdownPool(pool2);
// close current channel of the mock nfd
stopMockNfd(transport);
// reset the count
count = 0;
// start sending again
ScheduledExecutorService pool3 = Executors.newSingleThreadScheduledExecutor();
send(pool3, transport, "ping4");
// wait long enough for client to reconnect and send some pings
for (int i = 0; i < 10; i++) {
sleep(1000);
if (count > 0) {
break;
}
}
// stop server
stopMockNfd(transport);
// shutdown the pools
shutdownPool(pool);
shutdownPool(pool3);
// we should see something from client after reconnect
assert(count > 0);
}
private AsyncTcpTransport createAndStartClient(ScheduledExecutorService pool) {
AsyncTcpTransport transport = new AsyncTcpTransport(pool);
AsyncTcpTransport.ConnectionInfo cinfo = new AsyncTcpTransport.ConnectionInfo("localhost", PORT, true);
try {
transport.connect(cinfo, null, null);
} catch (IOException e) {
LOGGER.log(Level.WARNING, e.getMessage());
}
return transport;
}
private void shutdownPool(ScheduledExecutorService pool) {
pool.shutdownNow();
}
private void send(final ScheduledExecutorService pool, final AsyncTcpTransport transport, final String ping) {
pool.schedule(new Runnable() {
public void run() {
try {
transport.send(ByteBuffer.wrap((ping + "\n").getBytes()));
} catch (IOException e) {
LOGGER.log(Level.WARNING, e.getMessage());
}
send(pool, transport, ping);
}
}, 1, TimeUnit.SECONDS);
}
private void sleep(long ms) {
try {
Thread.sleep(ms);
} catch (InterruptedException e) {
LOGGER.log(Level.WARNING, e.getMessage());
}
}
private void stopMockNfd(AsyncTcpTransport transport) {
try {
transport.send(ByteBuffer.wrap("close\n".getBytes()));
} catch (IOException e) {
LOGGER.log(Level.WARNING, e.getMessage());
}
sleep(3000);
}
private void startMockNfd(final int port) {
if (server == null) {
Thread nfd = new Thread(new Runnable() {
public void run() {
try {
server = new ServerSocket(port);
while (true) {
LOGGER.log(Level.INFO, "before accept new connection");
new MockNfdChannel(server.accept());
sleep(1000);
}
} catch (IOException e) {
LOGGER.log(Level.WARNING, e.getMessage());
}
}
});
nfd.setDaemon(true);
nfd.start();
}
}
private class MockNfdChannel extends Thread {
private final Socket client_;
MockNfdChannel(Socket client) {
this.client_ = client;
start();
}
public void run() {
try {
LineNumberReader reader = new LineNumberReader(new InputStreamReader(client_.getInputStream()));
String line;
while ((line = reader.readLine()) != null) {
if ("close".equals(line)) {
LOGGER.log(Level.INFO, "exit received");
break;
} else {
LOGGER.log(Level.INFO, "received:" + line);
count++;
}
}
reader.close();
} catch (IOException e) {
LOGGER.log(Level.WARNING, e.getMessage());
} finally {
if (client_ != null) {
try {
client_.close();
} catch (IOException e) {
LOGGER.log(Level.WARNING, e.getMessage());
}
}
}
}
}
}