Commit 068907b5 authored by Joerg Domaschka's avatar Joerg Domaschka

outsourced container transitions to new classes. added error awareness to...

outsourced container transitions to new classes. added error awareness to state machines and state machine builder. wrote test cases for that
parent de1606f1
package de.uniulm.omi.cloudiator.lance.container.standard;
import java.util.Arrays;
import de.uniulm.omi.cloudiator.lance.lca.container.ContainerException;
import de.uniulm.omi.cloudiator.lance.lca.container.ContainerStatus;
import de.uniulm.omi.cloudiator.lance.lca.registry.RegistrationException;
import de.uniulm.omi.cloudiator.lance.util.state.ErrorAwareTransitionBuilder;
import de.uniulm.omi.cloudiator.lance.util.state.TransitionAction;
import de.uniulm.omi.cloudiator.lance.util.state.TransitionException;
final class BootstrapTransitionAction implements TransitionAction {
private final ErrorAwareContainer<?> theContainer;
BootstrapTransitionAction(ErrorAwareContainer<?> container) {
theContainer = container;
}
@Override
public void transit(Object[] params) throws TransitionException {
try {
checkForBootstrapParameters(params);
theContainer.logic.doInit(null);
theContainer.postBootstrapAction();
theContainer.registerStatus(ContainerStatus.BOOTSTRAPPED);
} catch (ContainerException | RegistrationException ce) {
ErrorAwareContainer.getLogger().error("could not initialise container", ce);
throw new TransitionException(ce);
}
}
static void checkForBootstrapParameters(Object[] o){
//if(o == null || o.length == 0 || o.length > 1 || !(o[0] instanceof OperatingSystem)) throw new IllegalArgumentException(Arrays.toString(o));
if(o == null || o.length > 0)
throw new IllegalArgumentException(Arrays.toString(o));
// return (OperatingSystem) o[0];
return;
}
public static void create(ErrorAwareTransitionBuilder<ContainerStatus> transitionBuilder,
ErrorAwareContainer<?> container) {
CreateTransitionAction action = new CreateTransitionAction(container);
// FIXME: add error handling //
transitionBuilder.setStartState(ContainerStatus.CREATED).
setIntermediateState(ContainerStatus.BOOTSTRAPPING, false).
setEndState(ContainerStatus.BOOTSTRAPPED).
addTransitionAction(action);
transitionBuilder.buildAndRegister();
}
public static boolean isSuccessfullEndState(ContainerStatus stat) {
return stat == ContainerStatus.BOOTSTRAPPED;
}
public static boolean isKnownErrorState(ContainerStatus stat) {
return stat == ContainerStatus.BOOTSTRAPPING_FAILED;
}
}
package de.uniulm.omi.cloudiator.lance.container.standard;
import java.util.Arrays;
import de.uniulm.omi.cloudiator.lance.lca.container.ContainerException;
import de.uniulm.omi.cloudiator.lance.lca.container.ContainerStatus;
import de.uniulm.omi.cloudiator.lance.lca.registry.RegistrationException;
import de.uniulm.omi.cloudiator.lance.util.state.ErrorAwareTransitionBuilder;
import de.uniulm.omi.cloudiator.lance.util.state.TransitionAction;
import de.uniulm.omi.cloudiator.lance.util.state.TransitionException;
final class CreateTransitionAction implements TransitionAction {
private final ErrorAwareContainer<?> theContainer;
CreateTransitionAction(ErrorAwareContainer<?> container) {
theContainer = container;
}
@Override
public void transit(Object[] params) throws TransitionException {
try {
theContainer.preCreateAction();
checkForCreationParameters(params);
theContainer.logic.doCreate();
theContainer.postCreateAction();
theContainer.registerStatus(ContainerStatus.CREATED);
} catch (ContainerException | RegistrationException ce) {
throw new TransitionException(ce);
}
}
private static void checkForCreationParameters(Object[] o){
//if(o == null || o.length == 0 || o.length > 1 || !(o[0] instanceof OperatingSystem)) throw new IllegalArgumentException(Arrays.toString(o));
if(o == null || o.length > 0)
throw new IllegalArgumentException(Arrays.toString(o));
// return (OperatingSystem) o[0];
return;
}
public static void create(ErrorAwareTransitionBuilder<ContainerStatus> transitionBuilder,
ErrorAwareContainer<?> container) {
CreateTransitionAction action = new CreateTransitionAction(container);
// FIXME: add error handling //
transitionBuilder.setStartState(ContainerStatus.NEW).
setIntermediateState(ContainerStatus.CREATING, false).
setEndState(ContainerStatus.CREATED).
setErrorState(ContainerStatus.CREATION_FAILED).
addTransitionAction(action);
transitionBuilder.buildAndRegister();
}
public static boolean isSuccessfullEndState(ContainerStatus stat) {
return stat == ContainerStatus.CREATED;
}
public static boolean isKnownErrorState(ContainerStatus stat) {
return stat == ContainerStatus.CREATION_FAILED;
}
}
package de.uniulm.omi.cloudiator.lance.container.standard;
import de.uniulm.omi.cloudiator.lance.lca.container.ContainerException;
import de.uniulm.omi.cloudiator.lance.lca.container.ContainerStatus;
import de.uniulm.omi.cloudiator.lance.lca.registry.RegistrationException;
import de.uniulm.omi.cloudiator.lance.util.state.ErrorAwareTransitionBuilder;
import de.uniulm.omi.cloudiator.lance.util.state.TransitionAction;
import de.uniulm.omi.cloudiator.lance.util.state.TransitionException;
final class DestroyTransitionAction implements TransitionAction {
private final ErrorAwareContainer<?> theContainer;
DestroyTransitionAction(ErrorAwareContainer<?> container) {
theContainer = container;
}
@Override
public void transit(Object[] params) throws TransitionException {
theContainer.network.stopPortUpdaters();
try {
boolean forceShutdown = false;
try {
theContainer.preDestroyAction();
} catch (Exception ex) {
ErrorAwareContainer.getLogger().error("could not shut down component; trying to force shut down of container", ex);
forceShutdown = true;
}
theContainer.logic.doDestroy(forceShutdown);
theContainer.registerStatus(ContainerStatus.DESTROYED);
} catch (ContainerException | RegistrationException ce) {
ErrorAwareContainer.getLogger().error("could not shut down container;", ce);
throw new TransitionException(ce);
} finally {
try {
theContainer.setNetworking();
} catch (ContainerException e) {
ErrorAwareContainer.getLogger().error("could not update networking", e);
}
try {
theContainer.network.publishLocalData(theContainer.containerId);
} catch (ContainerException e) {
ErrorAwareContainer.getLogger().error("could not publish local data", e);
}
}
}
static void create(ErrorAwareTransitionBuilder<ContainerStatus> transitionBuilder,
ErrorAwareContainer<?> container) {
DestroyTransitionAction action = new DestroyTransitionAction(container);
// FIXME: add error handler //
transitionBuilder.setStartState(ContainerStatus.READY).
setIntermediateState(ContainerStatus.SHUTTING_DOWN, false).
setEndState(ContainerStatus.DESTROYED).
setErrorState(ContainerStatus.UNKNOWN).
addTransitionAction(action);
transitionBuilder.buildAndRegister();
}
public static boolean isKnownErrorState(ContainerStatus stat) {
return stat == ContainerStatus.UNKNOWN;
}
public static boolean isSuccessfullEndState(ContainerStatus stat) {
return stat == ContainerStatus.DESTROYED;
}
}
/*
* Copyright (c) 2014-2015 University of Ulm
*
* See the NOTICE file distributed with this work for additional information
* regarding copyright ownership. 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 de.uniulm.omi.cloudiator.lance.container.standard;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import de.uniulm.omi.cloudiator.lance.lca.GlobalRegistryAccessor;
import de.uniulm.omi.cloudiator.lance.lca.container.ContainerController;
import de.uniulm.omi.cloudiator.lance.lca.container.ComponentInstanceId;
import de.uniulm.omi.cloudiator.lance.lca.container.ContainerException;
import de.uniulm.omi.cloudiator.lance.lca.container.ContainerStatus;
import de.uniulm.omi.cloudiator.lance.lca.container.port.NetworkHandler;
import de.uniulm.omi.cloudiator.lance.lca.container.port.PortRegistryTranslator;
import de.uniulm.omi.cloudiator.lance.lca.registry.RegistrationException;
import de.uniulm.omi.cloudiator.lance.lifecycle.LifecycleController;
import de.uniulm.omi.cloudiator.lance.lifecycle.LifecycleException;
import de.uniulm.omi.cloudiator.lance.lifecycle.LifecycleStore;
import de.uniulm.omi.cloudiator.lance.util.state.ErrorAwareStateMachine;
import de.uniulm.omi.cloudiator.lance.util.state.ErrorAwareStateMachineBuilder;
import de.uniulm.omi.cloudiator.lance.util.state.TransitionAction;
import static de.uniulm.omi.cloudiator.lance.container.standard.StandardContainerHelper.checkForBootstrapParameters;
import static de.uniulm.omi.cloudiator.lance.container.standard.StandardContainerHelper.checkForCreationParameters;
// FIXME: move status updates to network handler to this class instead of keeping
// them in the individual container classes
// FIXME: update component instance status each time a state has been reached
// FIXME: introduce error states to life cycle handling
public final class ErrorAwareContainer<T extends ContainerLogic> implements ContainerController {
private static final Logger LOGGER = LoggerFactory.getLogger(ErrorAwareContainer.class);
static Logger getLogger() {
return LOGGER;
}
private final ErrorAwareStateMachine<ContainerStatus> stateMachine;
private final GlobalRegistryAccessor accessor;
final T logic;
final ComponentInstanceId containerId;
final NetworkHandler network;
final LifecycleController controller;
private ErrorAwareStateMachine<ContainerStatus> buildUpStateMachine() {
ErrorAwareStateMachineBuilder<ContainerStatus> builder =
new ErrorAwareStateMachineBuilder<>(ContainerStatus.NEW, ContainerStatus.UNKNOWN);
CreateTransitionAction.create(builder.getTransitionBuilder(), this);
BootstrapTransitionAction.create(builder.getTransitionBuilder(), this);
InitTransitionAction.create(builder.getTransitionBuilder(), this);
DestroyTransitionAction.create(builder.getTransitionBuilder(), this);
return builder.build();
}
public ErrorAwareContainer(ComponentInstanceId id, T logicParam, NetworkHandler networkParam,
LifecycleController controllerParam, GlobalRegistryAccessor accessorParam) {
containerId = id;
logic = logicParam;
network = networkParam;
controller = controllerParam;
accessor = accessorParam;
stateMachine = buildUpStateMachine();
}
@Override
public ComponentInstanceId getId() {
return containerId;
}
@Override
public ContainerStatus getState() {
return stateMachine.getState();
}
@Override
public void create() {
stateMachine.transit(ContainerStatus.NEW, ContainerStatus.CREATED, new Object[]{});
}
@Override
public void awaitCreation() {
ContainerStatus stat = stateMachine.waitForEndOfCurrentTransition();
if(CreateTransitionAction.isSuccessfullEndState(stat)) {
return;
}
if(CreateTransitionAction.isKnownErrorState(stat)) {
throw new IllegalStateException(stateMachine.collectExceptions());
}
}
@Override
public void bootstrap() {
stateMachine.transit(ContainerStatus.CREATED, ContainerStatus.BOOTSTRAPPED, new Object[]{});
}
@Override
public void awaitBootstrap() {
ContainerStatus stat = stateMachine.waitForEndOfCurrentTransition();
if(BootstrapTransitionAction.isSuccessfullEndState(stat)) {
return;
}
if(BootstrapTransitionAction.isKnownErrorState(stat)) {
throw new IllegalStateException(stateMachine.collectExceptions());
}
}
@Override
public void init(LifecycleStore store) {
stateMachine.transit(ContainerStatus.BOOTSTRAPPED, ContainerStatus.READY, new Object[]{store});
}
@Override
public void awaitInitialisation() {
stateMachine.waitForEndOfCurrentTransition();
ContainerStatus stat = stateMachine.waitForEndOfCurrentTransition();
if(InitTransitionAction.isSuccessfullEndState(stat)) {
return;
}
if(InitTransitionAction.isKnownErrorState(stat)) {
throw new IllegalStateException(stateMachine.collectExceptions());
}
}
@Override
public void tearDown() {
stateMachine.transit(ContainerStatus.READY, ContainerStatus.DESTROYED, new Object[]{});
}
@Override
public void awaitDestruction() {
stateMachine.waitForEndOfCurrentTransition();
ContainerStatus stat = stateMachine.waitForEndOfCurrentTransition();
if(DestroyTransitionAction.isSuccessfullEndState(stat)) {
return;
}
if(DestroyTransitionAction.isKnownErrorState(stat)) {
throw new IllegalStateException(stateMachine.collectExceptions());
}
}
void setNetworking() throws ContainerException {
String address = logic.getLocalAddress();
try {
network.initPorts(address);
} catch (RegistrationException re) {
throw new ContainerException("cannot access registry.", re);
}
}
void preCreateAction() throws ContainerException {
setNetworking();
}
void postCreateAction() throws ContainerException {
// add dummy values so that other components are aware of this instance,
// but can see that it is not ready for use yet.
network.publishLocalData(containerId);
}
void postBootstrapAction() throws ContainerException {
String address = logic.getLocalAddress();
if (address == null)
throw new ContainerException("container has no IP address set after bootstrapping.");
network.updateAddress(PortRegistryTranslator.PORT_HIERARCHY_2, address);
network.iterateOverInPorts(logic.getPortMapper());
network.pollForNeededConnections();
}
void preInitAction() throws LifecycleException {
controller.blockingInit();
controller.blockingInstall();
controller.blockingConfigure();
controller.blockingStart();
}
void postInitAction() throws ContainerException {
// only now that we have started, can the ports be
// retrieved and then registered at the registry //
network.publishLocalData(containerId);
network.startPortUpdaters(controller);
}
void preDestroyAction() throws ContainerException {
controller.blockingStop();
}
void registerStatus(ContainerStatus status) throws RegistrationException {
accessor.updateContainerState(containerId, status);
}
}
package de.uniulm.omi.cloudiator.lance.container.standard;
import de.uniulm.omi.cloudiator.lance.lca.container.ContainerException;
import de.uniulm.omi.cloudiator.lance.lca.container.ContainerStatus;
import de.uniulm.omi.cloudiator.lance.lca.registry.RegistrationException;
import de.uniulm.omi.cloudiator.lance.lifecycle.LifecycleException;
import de.uniulm.omi.cloudiator.lance.util.state.ErrorAwareTransitionBuilder;
import de.uniulm.omi.cloudiator.lance.util.state.TransitionAction;
import de.uniulm.omi.cloudiator.lance.util.state.TransitionException;
final class InitTransitionAction implements TransitionAction {
private final ErrorAwareContainer<?> theContainer;
InitTransitionAction(ErrorAwareContainer<?> container) {
theContainer = container;
}
@Override
public void transit(Object[] params) throws TransitionException {
//FIXME: add code for starting from snapshot (skip init and install steps)
// has to be realised at a different place
try {
theContainer.preInitAction();
theContainer.logic.completeInit();
theContainer.postInitAction();
theContainer.registerStatus(ContainerStatus.READY);
} catch (ContainerException | LifecycleException | RegistrationException ce) {
ErrorAwareContainer.getLogger().error("could not initialise container;", ce);
}
}
public static void create(ErrorAwareTransitionBuilder<ContainerStatus> transitionBuilder,
ErrorAwareContainer<?> container) {
CreateTransitionAction action = new CreateTransitionAction(container);
// FIXME: add error handler //
transitionBuilder.setStartState(ContainerStatus.BOOTSTRAPPED).
setIntermediateState(ContainerStatus.INITIALISING, false).
setEndState(ContainerStatus.READY).
setErrorState(ContainerStatus.INITIALISATION_FAILED).
addTransitionAction(action);
transitionBuilder.buildAndRegister();
}
public static boolean isKnownErrorState(ContainerStatus stat) {
return stat == ContainerStatus.INITIALISATION_FAILED;
}
public static boolean isSuccessfullEndState(ContainerStatus stat) {
return stat == ContainerStatus.READY;
}
}
......@@ -26,20 +26,20 @@ public interface ContainerController {
ContainerStatus getState();
void create();
void create() throws ContainerException;
void init(LifecycleStore store);
void init(LifecycleStore store) throws ContainerException;
void tearDown();
void tearDown() throws ContainerException;
void awaitInitialisation();
void awaitInitialisation() throws ContainerException;
void awaitCreation();
void awaitCreation() throws ContainerException;
void awaitDestruction();
void awaitDestruction() throws ContainerException;
void bootstrap();
void bootstrap() throws ContainerException;
void awaitBootstrap();
void awaitBootstrap() throws ContainerException;
}
package de.uniulm.omi.cloudiator.lance.util.state;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.Stack;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public final class ErrorAwareStateMachine<T extends Enum<?> & State > {
private final static Logger LOGGER = LoggerFactory.getLogger(ErrorAwareStateMachine.class);
private static final Object[] DEFAULT_PARAMS = new Object[0];
private T status;
private ErrorAwareStateTransition<T> ongoingTransition = null;
private Future<?> endOfTransition = null;
// only one transition at a time.
private final ExecutorService executor = Executors.newFixedThreadPool(1);
private final T init;
private final T genericErrorState;
private final Set<T> states = new HashSet<>();
private final List<ErrorAwareStateTransition<T>> transitions;
private final TransitionState callback = new TransitionState();
private final Stack<Throwable> collectedExceptions = new Stack<>();
ErrorAwareStateMachine(T init, T genericErrorState, List<T> states, List<ErrorAwareStateTransition<T>> st) {
assert init != null : "init state cannot be null";
this.init = init;
status = init;
this.transitions = st;
this. genericErrorState = genericErrorState;
this.states.addAll(states);
}
/**
* @param startState the start state of the transition
* @param params set of application specific parameters to be passed through to the
* transition
*/
public synchronized void transit(T fromState, T toState, Object[] params) {
ErrorAwareStateTransition<T> transition = findTransition(fromState, toState);
if(transition.isIntermediateOrEndState(status))
return ; // we are already done //
if(!transition.isStartState(status))
throw new IllegalStateException("we are in the wrong state: " + status);
if(endOfTransition != null)
throw new IllegalStateException("we are in the wrong state: endOfTransition is set");
// everything is fine. let's invoke the transition //
ongoingTransition = transition;