/*
* Copyright (C) 2006-2016 DLR, Germany
*
* All rights reserved
*
* http://www.rcenvironment.de/
*/
package de.rcenvironment.core.communication.testutils.templates;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.io.Serializable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
import org.junit.Ignore;
import org.junit.Test;
import de.rcenvironment.core.communication.common.CommunicationException;
import de.rcenvironment.core.communication.common.SerializationException;
import de.rcenvironment.core.communication.connection.api.ConnectionSetup;
import de.rcenvironment.core.communication.connection.api.ConnectionSetupState;
import de.rcenvironment.core.communication.connection.impl.ConnectionSetupServiceImpl;
import de.rcenvironment.core.communication.model.NetworkContactPoint;
import de.rcenvironment.core.communication.model.NetworkResponse;
import de.rcenvironment.core.communication.testutils.AbstractTransportBasedTest;
import de.rcenvironment.core.communication.testutils.ConnectionSetupStateTracker;
import de.rcenvironment.core.communication.testutils.VirtualInstance;
import de.rcenvironment.core.communication.testutils.VirtualInstanceGroup;
import de.rcenvironment.core.communication.transport.virtual.testutils.VirtualTopology;
import de.rcenvironment.core.toolkitbridge.transitional.ConcurrencyUtils;
import de.rcenvironment.core.utils.common.StringUtils;
import de.rcenvironment.toolkit.modules.concurrency.api.AsyncTaskService;
import de.rcenvironment.toolkit.modules.concurrency.api.RunnablesGroup;
/**
* A common base class that defines common tests to verify proper transport operation. Subclasses implement
* {@link #defineTestConfiguration()} to create a transport-specific test.
*
* @author Robert Mischke
*/
public abstract class AbstractManualConnectionTest extends AbstractTransportBasedTest {
private static final int SHCDC_TEST_NUM_CONNECTION_ATTEMPTS = 1000;
private static final int CHCDC_TEST_NUM_THREADS = 50;
private static final int CHCDC_TEST_NUM_CONNECTION_ATTEMPTS_PER_THREAD = 20;
private static final int MRM_TEST_NUM_MESSAGES = 100000;
private static final int MRM_TEST_NUM_SENDER_THREADS = 20;
private static final int MRM_TEST_SINGLE_MESSAGE_TIMEOUT = 1000;
private static final String COMMON_SERVER_NODE_NAME = "server";
private static final int COMMON_SLEEP_TIME_AFTER_SERVER_SHUTDOWN = 2000;
private static final int COMMON_STATE_CHANGE_WAIT_MSEC = 10 * 1000;
private static final long COMMON_WAIT_TIME_BEFORE_DISCONNECTING = 100;
private static final int COMMON_WAIT_TIME_BEFORE_PRINTING_3RD_STATS = 2000;
private static final int COMMON_WAIT_TIME_BEFORE_PRINTING_2ND_STATS = 5000;
/**
* Tests many connections build-up/tear-down cycles in a row. Especially useful for stability testing, and checking for memory leaks.
*
* @throws InterruptedException on uncaught errors
* @throws TimeoutException on uncaught errors
*/
@Test
@Ignore
public void sequentialHighConnectDisconnectCount() throws InterruptedException, TimeoutException {
VirtualInstance client = new VirtualInstance("client");
VirtualInstance server = new VirtualInstance(COMMON_SERVER_NODE_NAME);
client.registerNetworkTransportProvider(transportProvider);
server.registerNetworkTransportProvider(transportProvider);
server.addServerConfigurationEntry(contactPointGenerator.createContactPoint());
server.start();
client.start();
ConnectionSetupServiceImpl connectionSetupService = (ConnectionSetupServiceImpl) client.getConnectionSetupService();
ConnectionSetup setup = connectionSetupService.createConnectionSetup(server.getDefaultContactPoint(), "", false);
ConnectionSetupStateTracker stateTracker = new ConnectionSetupStateTracker(setup);
connectionSetupService.addConnectionSetupListener(stateTracker);
try {
for (int i = 0; i < SHCDC_TEST_NUM_CONNECTION_ATTEMPTS; i++) {
log.debug("Starting connection attempt " + (i + 1));
final String observedRemoteIdString = performConnectDisconnectCycle(setup, stateTracker);
assertEquals(server.getInstanceNodeSessionId().getInstanceNodeSessionIdString(), observedRemoteIdString);
}
} finally {
String statistics = ConcurrencyUtils.getThreadPoolManagement().getFormattedStatistics(true);
log.debug(statistics);
connectionSetupService.removeConnectionSetupListener(stateTracker);
server.shutDown();
Thread.sleep(COMMON_SLEEP_TIME_AFTER_SERVER_SHUTDOWN); // avoid irrelevant interruption exception
}
}
/**
* Tests many connection attempts with a wrong protocol version (for testing against connection/memory leaks).
*
* @throws InterruptedException on uncaught errors
* @throws TimeoutException on uncaught errors
*/
@Test
@Ignore
public void sequentialWrongProtocolConnectionAttempts() throws InterruptedException, TimeoutException {
VirtualInstance client = new VirtualInstance("client");
VirtualInstance server = new VirtualInstance(COMMON_SERVER_NODE_NAME);
client.registerNetworkTransportProvider(transportProvider);
server.registerNetworkTransportProvider(transportProvider);
server.addServerConfigurationEntry(contactPointGenerator.createContactPoint());
client.simulateCustomProtocolVersion("wrongClientVersion");
server.start();
client.start();
ConnectionSetupServiceImpl connectionSetupService = (ConnectionSetupServiceImpl) client.getConnectionSetupService();
ConnectionSetup setup = connectionSetupService.createConnectionSetup(server.getDefaultContactPoint(), "", false);
ConnectionSetupStateTracker stateTracker = new ConnectionSetupStateTracker(setup);
connectionSetupService.addConnectionSetupListener(stateTracker);
try {
for (int i = 0; i < SHCDC_TEST_NUM_CONNECTION_ATTEMPTS; i++) {
log.debug("Starting connection attempt " + (i + 1));
ConnectionSetupState initialState = setup.getState();
assertTrue(ConnectionSetupState.DISCONNECTED == initialState);
setup.signalStartIntent();
stateTracker.awaitAndExpect(ConnectionSetupState.CONNECTING, COMMON_STATE_CHANGE_WAIT_MSEC);
stateTracker.awaitAndExpect(ConnectionSetupState.DISCONNECTED, COMMON_STATE_CHANGE_WAIT_MSEC);
}
} finally {
connectionSetupService.removeConnectionSetupListener(stateTracker);
server.shutDown();
Thread.sleep(COMMON_SLEEP_TIME_AFTER_SERVER_SHUTDOWN); // avoid irrelevant interruption exception
}
}
/**
* Tests many connections build-up/tear-down cycles in parallel by multiple "clients". Especially useful for load/stability testing.
*
* @throws InterruptedException on uncaught errors
*/
@Test
@Ignore
public void concurrentHighConnectDisconnectCount() throws InterruptedException {
final AtomicInteger attemptCount = new AtomicInteger();
final AtomicInteger successCount = new AtomicInteger();
final VirtualInstance server = new VirtualInstance(COMMON_SERVER_NODE_NAME, false); // no relay
server.registerNetworkTransportProvider(transportProvider);
server.addServerConfigurationEntry(contactPointGenerator.createContactPoint());
server.start();
try {
RunnablesGroup runnablesGroup = ConcurrencyUtils.getFactory().createRunnablesGroup();
for (int i = 1; i <= CHCDC_TEST_NUM_THREADS; i++) {
final int i2 = i;
runnablesGroup.add(new Runnable() {
@Override
public void run() {
String clientId = "client-" + i2;
try {
VirtualInstance client = new VirtualInstance(clientId);
client.registerNetworkTransportProvider(transportProvider);
client.start();
ConnectionSetupServiceImpl connectionSetupService =
(ConnectionSetupServiceImpl) client.getConnectionSetupService();
for (int j = 1; j <= CHCDC_TEST_NUM_CONNECTION_ATTEMPTS_PER_THREAD; j++) {
attemptCount.incrementAndGet();
String attemptId = "connSetup-" + i2 + "-" + j;
ConnectionSetup setup =
connectionSetupService.createConnectionSetup(server.getDefaultContactPoint(), attemptId, false);
ConnectionSetupStateTracker stateTracker = new ConnectionSetupStateTracker(setup);
connectionSetupService.addConnectionSetupListener(stateTracker);
log.debug("Starting connection attempt: " + attemptId);
try {
final String observedRemoteIdString = performConnectDisconnectCycle(setup, stateTracker);
assertEquals(server.getInstanceNodeSessionIdString(), observedRemoteIdString);
successCount.incrementAndGet();
} catch (TimeoutException e) {
log.error(StringUtils.format("Connect attempt %d of thread %d failed with a timeout: %s", i2, j,
e.toString()));
}
connectionSetupService.removeConnectionSetupListener(stateTracker);
}
client.shutDown();
} catch (InterruptedException e) {
log.error("Client " + clientId + " failed", e);
throw new RuntimeException(e);
} catch (AssertionError e) {
log.error("Client " + clientId + " failed", e);
throw new RuntimeException(e);
}
}
});
}
runnablesGroup.executeParallel();
} finally {
String statistics = ConcurrencyUtils.getThreadPoolManagement().getFormattedStatistics(true);
log.debug("Immediately after end of test:\n" + statistics);
Thread.sleep(COMMON_WAIT_TIME_BEFORE_PRINTING_2ND_STATS);
String statistics2 = ConcurrencyUtils.getThreadPoolManagement().getFormattedStatistics(true);
log.debug("After a short delay:\n" + statistics2);
server.shutDown();
Thread.sleep(COMMON_WAIT_TIME_BEFORE_PRINTING_3RD_STATS); // avoid irrelevant interruption exception
String statistics3 = ConcurrencyUtils.getThreadPoolManagement().getFormattedStatistics(true);
log.debug("After server shutdown:\n" + statistics3);
log.debug(StringUtils.format("Attempt/success count: %d/%d", attemptCount.get(), successCount.get()));
assertEquals(CHCDC_TEST_NUM_THREADS * CHCDC_TEST_NUM_CONNECTION_ATTEMPTS_PER_THREAD, attemptCount.get());
assertEquals(CHCDC_TEST_NUM_THREADS * CHCDC_TEST_NUM_CONNECTION_ATTEMPTS_PER_THREAD, successCount.get());
}
}
/**
* Tests many messages being sent from a client to another client via a relay node; for testing against resource leaks in message
* sending or forwarding.
*
* @throws Exception on uncaught errors
*/
@Test
@Ignore
public void manyRoutedMessages() throws Exception {
final VirtualInstance client1 = new VirtualInstance("client1");
final VirtualInstance client2 = new VirtualInstance("client2");
final VirtualInstance server = new VirtualInstance(COMMON_SERVER_NODE_NAME, true); // relay
VirtualTopology topology = new VirtualTopology(client1, client2, server);
VirtualInstanceGroup allNodes = topology.getAsGroup();
allNodes.registerNetworkTransportProvider(transportProvider);
NetworkContactPoint serverNCP = contactPointGenerator.createContactPoint();
server.addServerConfigurationEntry(serverNCP);
allNodes.start();
topology.connect(0, 2);
topology.connect(1, 2);
topology.waitUntilReachable(0, 1, COMMON_STATE_CHANGE_WAIT_MSEC); // client1->client2
try {
log.info("Starting to send messages");
final Semaphore maxParallelSendLimit = new Semaphore(MRM_TEST_NUM_SENDER_THREADS);
final AsyncTaskService threadPool = ConcurrencyUtils.getAsyncTaskService();
for (int i = 0; i < MRM_TEST_NUM_MESSAGES; i++) {
maxParallelSendLimit.acquire();
final String requestContent = Integer.toString(i);
threadPool.execute(new Runnable() {
@Override
public void run() {
NetworkResponse response;
try {
response = client1.performRoutedRequest(requestContent, client2.getInstanceNodeSessionId(),
MRM_TEST_SINGLE_MESSAGE_TIMEOUT);
Serializable responseContent = response.getDeserializedContent();
assertTrue(responseContent.toString().startsWith(requestContent));
// note: intentionally not reached on errors
maxParallelSendLimit.release();
} catch (CommunicationException | InterruptedException | ExecutionException | TimeoutException
| SerializationException e) {
log.error("Error while sending message", e);
}
}
});
}
log.info("Finished sending messages");
} finally {
allNodes.shutDown();
Thread.sleep(COMMON_SLEEP_TIME_AFTER_SERVER_SHUTDOWN); // avoid irrelevant interruption exception
}
}
/**
* @return the instance session id reported by the remote node
*/
private String performConnectDisconnectCycle(ConnectionSetup setup, ConnectionSetupStateTracker stateTracker)
throws InterruptedException, AssertionError, TimeoutException {
ConnectionSetupState initialState = setup.getState();
assertTrue(ConnectionSetupState.DISCONNECTED == initialState);
setup.signalStartIntent();
stateTracker.awaitAndExpect(ConnectionSetupState.CONNECTING, COMMON_STATE_CHANGE_WAIT_MSEC);
stateTracker.awaitAndExpect(ConnectionSetupState.CONNECTED, COMMON_STATE_CHANGE_WAIT_MSEC);
final String remoteInstanceSessionIdString = setup.getCurrentChannel().getRemoteNodeInformation().getInstanceNodeSessionIdString();
Thread.sleep(COMMON_WAIT_TIME_BEFORE_DISCONNECTING);
// TODO also test message round-trip here?
setup.signalStopIntent();
stateTracker.awaitAndExpect(ConnectionSetupState.DISCONNECTING, COMMON_STATE_CHANGE_WAIT_MSEC);
stateTracker.awaitAndExpect(ConnectionSetupState.DISCONNECTED, COMMON_STATE_CHANGE_WAIT_MSEC);
// return remote id for potential verification
return remoteInstanceSessionIdString;
}
}