package com.sixsq.slipstream.statemachine; /* * +=================================================================+ * SlipStream Server (WAR) * ===== * Copyright (C) 2013 SixSq Sarl (sixsq.com) * ===== * 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. * -=================================================================- */ import java.util.HashMap; import java.util.Map; import java.util.logging.Logger; import javax.persistence.EntityManager; import org.restlet.data.Status; import org.restlet.resource.ResourceException; import com.sixsq.slipstream.exceptions.CannotAdvanceFromTerminalStateException; import com.sixsq.slipstream.exceptions.InvalidStateException; import com.sixsq.slipstream.exceptions.NotFoundException; import com.sixsq.slipstream.exceptions.SlipStreamClientException; import com.sixsq.slipstream.exceptions.SlipStreamException; import com.sixsq.slipstream.exceptions.ValidationException; import com.sixsq.slipstream.persistence.PersistenceUtil; import com.sixsq.slipstream.persistence.Run; import com.sixsq.slipstream.persistence.RuntimeParameter; /** * Unit tests: * @see StateMachinetTest * @see StateMachineMultiThreadingTest * */ public class StateMachine { private static Logger logger = Logger.getLogger("com.sixsq.slipstream.statemachine"); private static final int MAX_RECURSION = 100; private Run run; private State globalState; private Map<String, State> nodeStates = new HashMap<String, State>(); public StateMachine(Map<String, State> nodeStates, State globalState, Run run) { this.globalState = globalState; this.nodeStates = nodeStates; this.run = run; } public StateMachine(Run run) throws InvalidStateException { recreateStateFromRun(run); } public static ExtrinsicState createNodeExtrinsicState(Run run, String node) { String key; String nodePrefix = node + RuntimeParameter.NODE_PROPERTY_SEPARATOR; key = nodePrefix + RuntimeParameter.COMPLETE_KEY; RuntimeParameter completed = run.getRuntimeParameters().get(key); key = nodePrefix + RuntimeParameter.ABORT_KEY; RuntimeParameter failing = run.getRuntimeParameters().get(key); key = nodePrefix + RuntimeParameter.IS_ORCHESTRATOR_KEY; RuntimeParameter isOrchestrator = run.getRuntimeParameters().get(key); key = nodePrefix + RuntimeParameter.SCALE_STATE_KEY; RuntimeParameter scaleState = run.getRuntimeParameters().get(key); RuntimeParameter state = run.getRuntimeParameters().get(RuntimeParameter.GLOBAL_STATE_KEY); ExtrinsicState extrinsicState = new ExtrinsicState(completed, failing, isOrchestrator, state, scaleState); return extrinsicState; } public static ExtrinsicState createGlobalExtrinsicState(Run run) { RuntimeParameter globalCompleteRuntimeParameter = run .getRuntimeParameters().get(RuntimeParameter.GLOBAL_COMPLETE_KEY); RuntimeParameter globalFailingRuntimeParameter = run .getRuntimeParameters().get(RuntimeParameter.GLOBAL_ABORT_KEY); RuntimeParameter globalStateRuntimeParameter = run .getRuntimeParameters().get(RuntimeParameter.GLOBAL_STATE_KEY); ExtrinsicState globalExtrinsicState = new ExtrinsicState( globalCompleteRuntimeParameter, globalFailingRuntimeParameter, globalStateRuntimeParameter); return globalExtrinsicState; } public static StateMachine createStateMachine(Run run) { StateMachine sc; try { sc = getStateMachine(run); } catch (InvalidStateException e) { throw new ResourceException(Status.CLIENT_ERROR_CONFLICT, e.getMessage()); } catch (SlipStreamClientException e) { throw new ResourceException(Status.CLIENT_ERROR_CONFLICT, e.getMessage()); } return sc; } public static StateMachine getStateMachine(Run run) throws ValidationException, InvalidStateException, NotFoundException { return new StateMachine(run); } private void recreateStateFromRun(Run run) throws InvalidStateException { this.run = run; nodeStates = new HashMap<String, State>(); for (String node : run.getNodeInstanceNamesList()) { ExtrinsicState extrinsicState = createNodeExtrinsicState(run, node); State nodeState = StateFactory.createInstance(extrinsicState); nodeStates.put(node, nodeState); } ExtrinsicState globalExtrinsicState = createGlobalExtrinsicState(run); globalState = StateFactory.createInstance(globalExtrinsicState); } // Note: Used only for unit tests public void start() throws SlipStreamException { EntityManager em = beginTransation(); completeAllNodesState(); commitTransaction(em); } // Note: Used only for unit tests private void completeAllNodesState() throws SlipStreamClientException { for (String nodeName : nodeStates.keySet()) { setNodeStateCompleted(nodeName); } attemptToAdvanceState(); } public States updateState(String nodeName) throws SlipStreamClientException, InvalidStateException { completeCurrentState(nodeName); tryAdvanceState(); return globalState.getState(); } private void completeCurrentState(String nodeName) throws InvalidStateException, SlipStreamClientException { EntityManager em = beginTransation(); setNodeStateCompleted(nodeName); commitTransaction(em); } private void tryAdvanceState() throws InvalidStateException, CannotAdvanceFromTerminalStateException { tryAdvanceState(false); } public void tryAdvanceState(boolean force) throws InvalidStateException, CannotAdvanceFromTerminalStateException { tryAdvanceToState(globalState.nextState, force); } public void tryAdvanceToProvisionning() throws InvalidStateException, CannotAdvanceFromTerminalStateException { if (!States.Ready.equals(globalState.getState())) { throw new InvalidStateException("Transition from " + globalState + " to Provisioning not allowed."); } else if (!run.isMutable()) { throw new InvalidStateException( "Transition from " + globalState + " to Provisioning not allowed in an imutable Run"); } attemptToAdvanceToState(States.Provisioning, true); } public void tryAdvanceToFinalizing() throws InvalidStateException, CannotAdvanceFromTerminalStateException { if (canCancel()){ throw new InvalidStateException("Transition from " + globalState + " to Finalizing not allowed."); } tryAdvanceToState(States.Finalizing, true); } public void tryAdvanceToDone() throws InvalidStateException, CannotAdvanceFromTerminalStateException { if (canCancel()){ throw new InvalidStateException("Transition from " + globalState + " to Done not allowed."); } tryAdvanceToState(States.Done, true); } public void tryAdvanceToCancelled() throws InvalidStateException, CannotAdvanceFromTerminalStateException { if (! canCancel()){ throw new InvalidStateException("Transition from " + globalState + " to Cancelled not allowed."); } tryAdvanceToState(States.Cancelled, true); } public boolean canCancel() { return ! States.canTerminate().contains(globalState.getState()); } private void tryAdvanceToState(States state, boolean force) throws InvalidStateException, CannotAdvanceFromTerminalStateException { tryAdvanceToState(state, force, MAX_RECURSION); } private void tryAdvanceToState(States state, boolean force, int recursions) throws InvalidStateException, CannotAdvanceFromTerminalStateException { EntityManager em = null; try { em = beginTransation(); attemptToAdvanceToState(state, force); commitTransaction(em); } catch (RuntimeException ex) { if (em != null) { if (em.getTransaction() != null && em.getTransaction().isActive()) { em.getTransaction().rollback(); } em.close(); } if (recursions > 0) { logger.warning("Error in tryAdvanceToState " + state + ". Retrying..."); tryAdvanceToState(state, force, recursions - 1); } else { throw ex; } } if (em.isOpen()) { em.close(); } } private Run getRun() { return globalState.getExtrinsicState().getRun(); } private void setNodeStateCompleted(String nodeName) throws SlipStreamClientException { try { nodeStates.get(nodeName).setStateCompleted(true); } catch (NullPointerException ex) { throw (new SlipStreamClientException("Failed to find nodename: " + nodeName)); } } private void attemptToAdvanceState() throws CannotAdvanceFromTerminalStateException { attemptToAdvanceToState(globalState.nextState, false); } private void attemptToAdvanceToState(States nextState, boolean force) throws CannotAdvanceFromTerminalStateException { if (globalState.isFinal()) { throw (new CannotAdvanceFromTerminalStateException()); } try { if (force || checkSynchronizedConditionMet()) { setState(nextState, force); } } catch (InvalidStateException e) { return; } } private void setState(States newState) throws InvalidStateException { setState(newState, false); } protected void setState(States newState, boolean force) throws InvalidStateException { globalState = assignNodeState(globalState, newState); run.setState(globalState.getState()); run.setLastStateChange(); resetNodesStateCompleted(); if (globalState.isFinal()) { run.setEnd(); } } private State assignNodeState(State state, States newState) throws InvalidStateException { return StateFactory.createInstance(newState, state.getExtrinsicState()); } private boolean checkSynchronizedConditionMet() throws InvalidStateException { boolean onlyOrch = globalState.synchronizedForOrchestrators(); boolean recoveryMode = Run.isInRecoveryMode(run); if (globalState.synchronizedForEveryone() || onlyOrch) { for (Map.Entry<String, State> node : nodeStates.entrySet()) { if (!node.getValue().isRemoved() && ((!onlyOrch && !recoveryMode) || ((onlyOrch || recoveryMode) && node.getValue().isOrchestrator())) ) { checkStateCompleted(node.getKey()); } } } return true; } private void checkStateCompleted(String nodeName) throws InvalidStateException { State state = nodeStates.get(nodeName); if (!state.isStateCompleted()) { throw (new InvalidStateException( "Synchronization condition not met for node: " + nodeName)); } } public void failCurrentState(String nodeName) throws InvalidStateException { EntityManager em = beginTransation(); nodeStates.get(nodeName).setFailing(true); globalState.setFailing(true); commitTransaction(em); } protected EntityManager beginTransation() throws InvalidStateException { EntityManager em = PersistenceUtil.createEntityManager(); em.getTransaction().begin(); Run run = em.find(Run.class, getRun().getResourceUri()); recreateStateFromRun(run); return em; } protected void commitTransaction(EntityManager em) { em.getTransaction().commit(); em.close(); } private void alineAllNodesStateToGlobalState() throws InvalidStateException { for (String nodeName : nodeStates.keySet()) { nodeStates.put( nodeName, assignNodeState(nodeStates.get(nodeName), globalState.getState())); } } private void resetNodesStateCompleted() { for (State nodeState : nodeStates.values()) { nodeState.setStateCompleted(false); } } public States getState() { return globalState.getState(); } public void fail(String nodeName) throws InvalidStateException { EntityManager em = beginTransation(); State state = nodeStates.get(nodeName); state.setFailing(true); globalState.setFailing(true); setState(States.SendingReports); alineAllNodesStateToGlobalState(); commitTransaction(em); } public boolean isFailing() { return globalState.isFailing(); } }