/* * Copyright (C) 2006-2016 DLR, Germany * * All rights reserved * * http://www.rcenvironment.de/ */ package de.rcenvironment.core.communication.testutils; import java.util.concurrent.Semaphore; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import de.rcenvironment.core.communication.common.CommunicationException; import de.rcenvironment.core.communication.configuration.NodeConfigurationService; import de.rcenvironment.core.communication.model.InitialNodeInformation; import de.rcenvironment.core.communication.model.NetworkContactPoint; import de.rcenvironment.core.communication.transport.spi.NetworkTransportProvider; import de.rcenvironment.core.toolkitbridge.transitional.ConcurrencyUtils; import de.rcenvironment.toolkit.modules.concurrency.api.AsyncTaskService; import de.rcenvironment.toolkit.modules.concurrency.api.TaskDescription; /** * Base class for {@link VirtualInstance} that provides management of the instance life cycle and the configured test properties. * * @author Robert Mischke */ public abstract class VirtualInstanceSkeleton implements CommonVirtualInstanceControl { /** * Internal state machine controlling the instance life cycle. Triggers asynchronous execution of startup/shutdown methods. * * @author Robert Mischke */ private class StateMachine { private VirtualInstanceState currentState = VirtualInstanceState.INITIAL; private Semaphore transitionalStateSemaphore = new Semaphore(1); public synchronized void onTargetStateRequested(VirtualInstanceState requestedState) throws InterruptedException { switch (requestedState) { case STARTED: // TODO allow restart from "stopped"? if (currentState == VirtualInstanceState.INITIAL || currentState == VirtualInstanceState.STOPPED) { enterState(VirtualInstanceState.STARTING); sharedThreadPool.execute(new Runnable() { @Override @TaskDescription("Communication Layer: Virtual instance startup") public void run() { try { performStartup(); enterState(VirtualInstanceState.STARTED); } catch (InterruptedException e) { log.debug("Virtual instance startup failed", e); } catch (CommunicationException e) { log.debug("Virtual instance startup failed", e); } } }); } else { log.warn("Virtual instance was instructed to 'start', but is in " + currentState + " state"); } break; case SIMULATED_CRASHING: if (currentState == VirtualInstanceState.STARTED) { enterState(VirtualInstanceState.SIMULATED_CRASHING); sharedThreadPool.execute(new Runnable() { @Override @TaskDescription("Communication Layer: Virtual instance crash simulation") public void run() { try { performSimulatedCrash(); enterState(VirtualInstanceState.STOPPED); } catch (InterruptedException e) { log.debug("Error while simulating virtual instance crash", e); } } }); } else { log.warn("Virtual instance was commanded to 'crash', but in " + currentState + " state (instead of STARTED)"); } break; case STOPPED: // TODO react on "stop" requests during startup? if (currentState == VirtualInstanceState.STARTED) { enterState(VirtualInstanceState.STOPPING); sharedThreadPool.execute(new Runnable() { @Override @TaskDescription("Communication Layer: Virtual instance shutdown") public void run() { try { performShutdown(); enterState(VirtualInstanceState.STOPPED); } catch (InterruptedException e) { log.debug("Virtual instance shutdown failed", e); } } }); } break; default: throw new IllegalArgumentException("Invalid state request: " + requestedState); } } public synchronized VirtualInstanceState getCurrentState() { return currentState; } // Note: this method MUST NOT be synchronized; otherwise, the VI monitor will be held // during the wait and prevent the expected state change to happen -- misc_ro public void waitForNonTransitionalState() throws InterruptedException { // wait until the semaphore is not held transitionalStateSemaphore.acquire(); // release immediately transitionalStateSemaphore.release(); } // Note: synchronized for access after asynchronous state changes private synchronized void enterState(VirtualInstanceState newState) throws InterruptedException { if (isTransitionalState(currentState)) { transitionalStateSemaphore.release(); } if (isTransitionalState(newState)) { transitionalStateSemaphore.acquire(); } currentState = newState; // log.debug(StringUtils.format("State change: Virtual instance '%s' is now %s", // nodeInformation.getLogName(), newState)); } private boolean isTransitionalState(VirtualInstanceState state) { // TODO make this an Enum property? return state == VirtualInstanceState.STARTING || state == VirtualInstanceState.STOPPING || state == VirtualInstanceState.SIMULATED_CRASHING; } } private static final AsyncTaskService sharedThreadPool = ConcurrencyUtils.getAsyncTaskService(); protected final InitialNodeInformation nodeInformation; protected final Log log = LogFactory.getLog(getClass()); private final NodeConfigurationServiceTestStub nodeConfigurationService; private final StateMachine stateMachine = new StateMachine(); /** * Creates a virtual instance with the given log/display name. * * @param predefinedInstanceId an optional predefined instance id's string form; if null, an instance id is created automatically * @param displayName the log/display name to use * @param isRelay whether the "is relay" flag of this node should be set */ public VirtualInstanceSkeleton(String predefinedInstanceId, String displayName, boolean isRelay) { nodeConfigurationService = new NodeConfigurationServiceTestStub(predefinedInstanceId, displayName, isRelay); nodeInformation = nodeConfigurationService.getInitialNodeInformation(); } /** * Adds a {@link NetworkContactPoint} at which a network server should be run by this instance. * * @param contactPoint the {@link NetworkContactPoint} that defines the server bind address, port and transport type */ public void addServerConfigurationEntry(NetworkContactPoint contactPoint) { nodeConfigurationService.addServerConfigurationEntry(contactPoint); } @Override public void addInitialNetworkPeer(NetworkContactPoint contactPoint) { nodeConfigurationService.addInitialNetworkPeer(contactPoint); } public VirtualInstanceState getCurrentState() { return stateMachine.getCurrentState(); } @Override public void setTargetState(VirtualInstanceState requestedState) throws InterruptedException { stateMachine.onTargetStateRequested(requestedState); } @Override public void waitForStateChangesToFinish() throws InterruptedException { stateMachine.waitForNonTransitionalState(); } @Override public void start() throws InterruptedException { setTargetState(VirtualInstanceState.STARTED); waitForStateChangesToFinish(); } @Override public void simulateCrash() throws InterruptedException { setTargetState(VirtualInstanceState.SIMULATED_CRASHING); waitForStateChangesToFinish(); } @Override public void shutDown() throws InterruptedException { setTargetState(VirtualInstanceState.STOPPED); waitForStateChangesToFinish(); } @Override public abstract void registerNetworkTransportProvider(NetworkTransportProvider provider); protected NodeConfigurationService getNodeConfigurationService() { return nodeConfigurationService; } protected abstract void performStartup() throws InterruptedException, CommunicationException; protected abstract void performShutdown() throws InterruptedException; protected abstract void performSimulatedCrash() throws InterruptedException; }