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 static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.persistence.EntityManager;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import com.sixsq.slipstream.connector.local.LocalConnector;
import com.sixsq.slipstream.es.CljElasticsearchHelper;
import com.sixsq.slipstream.event.Event;
import com.sixsq.slipstream.exceptions.CannotAdvanceFromTerminalStateException;
import com.sixsq.slipstream.exceptions.ConfigurationException;
import com.sixsq.slipstream.exceptions.InvalidStateException;
import com.sixsq.slipstream.exceptions.SlipStreamClientException;
import com.sixsq.slipstream.exceptions.SlipStreamException;
import com.sixsq.slipstream.exceptions.ValidationException;
import com.sixsq.slipstream.factory.RunFactory;
import com.sixsq.slipstream.persistence.CloudImageIdentifier;
import com.sixsq.slipstream.persistence.DeploymentModule;
import com.sixsq.slipstream.persistence.ImageModule;
import com.sixsq.slipstream.persistence.Node;
import com.sixsq.slipstream.persistence.Package;
import com.sixsq.slipstream.persistence.PersistenceUtil;
import com.sixsq.slipstream.persistence.Run;
import com.sixsq.slipstream.persistence.RunType;
import com.sixsq.slipstream.persistence.User;
import com.sixsq.slipstream.persistence.UserParameter;
import com.sixsq.slipstream.util.CommonTestUtil;
public class StateMachinetTest {
private ExtrinsicState globalExtrinsicState;
private Map<String, ExtrinsicState> nodeExtrinsicStates = new HashMap<String, ExtrinsicState>();
private Run run = null;
private static User user = null;
private final String cloudName = "dummy";
private final String orchName = "orchestrator-" + cloudName;
protected static String cloudServiceName = new LocalConnector()
.getCloudServiceName();
@BeforeClass
public static void beforeClass() throws ConfigurationException,
ValidationException {
Event.muteForTests();
CljElasticsearchHelper.createAndInitTestDb();
user = CommonTestUtil.createTestUser();
CommonTestUtil.addSshKeys(user);
}
@AfterClass
public static void tearDownAfterClass() throws ValidationException {
CommonTestUtil.deleteUser(user);
removeRuns();
List<Run> runs = Run.listAll();
assertEquals(0, runs.size());
}
private static void removeRuns() throws ValidationException {
for (Run run : Run.listAll()) {
run.remove();
}
}
private void generateDeploymentAndRun(String[] nodeNames) throws SlipStreamException {
// Run can be created only on a deployment module with nodes.
DeploymentModule module = generateDummyDeploymentModuleWithNode(nodeNames);
run = RunFactory.getRun(module, RunType.Orchestration, user);
run = run.store();
}
private DeploymentModule generateDummyDeploymentModuleWithNode(String[] nodeNames) throws ValidationException,
ConfigurationException {
DeploymentModule module = new DeploymentModule("setUp");
ImageModule image = new ImageModule("foo_image");
image.setIsBase(true);
image.setImageId("123", cloudName);
image.store();
if (nodeNames.length > 0) {
for (String nodename : nodeNames) {
Node node = new Node(nodename, image);
node.setCloudService(cloudName);
node = (Node) node.store();
module.setNode(node);
}
} else {
// Add fake node if none were provided. Run needs nodes on the deployment module.
Node node = new Node("fake", image);
node.setCloudService(cloudName);
node = (Node) node.store();
module.setNode(node);
}
return module;
}
private StateMachine createStateMachine(String[] nodeInstanceNames) throws SlipStreamException {
generateDeploymentAndRun(nodeInstanceNamesToNodeNames(nodeInstanceNames));
StateMachine sc = createStateContext(nodeInstanceNames);
return sc;
}
private String[] nodeInstanceNamesToNodeNames(String[] nodeInstanceNames) {
String[] nodeNames = new String[nodeInstanceNames.length];
for (int i = 0; i < nodeNames.length; i++) {
nodeNames[i] = nodeInstanceNames[i].replaceAll("\\.[0-9]$", "");
}
return nodeNames;
}
@After
public void tearDown() {
globalExtrinsicState = null;
if (run != null) {
run.remove();
run = null;
}
}
@Test
public void initialEmptyState() throws IllegalArgumentException,
SecurityException, ClassNotFoundException, InstantiationException,
IllegalAccessException, InvocationTargetException,
NoSuchMethodException, SlipStreamException {
StateMachine sc = createStateMachine(new String[] {});
assertEquals(States.Initializing, sc.getState());
}
@Test
public void initialState() throws InvalidStateException,
SlipStreamException {
StateMachine sc = createStateMachine(new String[] { "n1.1" });
assertEquals(States.Initializing, sc.getState());
}
@Test
public void globalProvisioningState() throws SlipStreamException {
StateMachine sc = createStateMachine(new String[] { "n1.1" });
sc.start();
assertEquals(States.Provisioning, sc.getState());
}
@Test
public void fullNominalWorkflow() throws SlipStreamException {
StateMachine sc = createStateMachine(new String[] { "n1.1", "n2.1" });
sc.start();
assertState(sc, States.Provisioning);
EntityManager em = sc.beginTransation();
sc.updateState("n1.1");
sc.commitTransaction(em);
assertState(sc, States.Provisioning);
sc.updateState("n2.1");
sc.updateState(orchName);
assertState(sc, States.Executing);
sc.updateState("n1.1");
assertState(sc, States.Executing);
sc.updateState("n2.1");
sc.updateState(orchName);
assertState(sc, States.SendingReports);
sc.updateState("n1.1");
assertState(sc, States.SendingReports);
sc.updateState("n2.1");
sc.updateState(orchName);
assertState(sc, States.Ready);
sc.updateState("n1.1");
assertState(sc, States.Ready);
sc.updateState("n2.1");
sc.updateState(orchName);
assertState(sc, States.Finalizing);
sc.updateState("n1.1");
sc.updateState(orchName);
em = PersistenceUtil.createEntityManager();
run = em.find(Run.class, run.getResourceUri());
assertThat(run.getRuntimeParameterValue("ss:state"),
is(States.Done.toString()));
em.close();
assertState(sc, States.Done);
}
private void assertState(StateMachine sc, States state) {
assertEquals(state, sc.getState());
}
@Test(expected = SlipStreamClientException.class)
public void inexistantNodeName() throws SlipStreamException {
StateMachine sc = createStateMachine(new String[] { "n1.1", "n2.1" });
sc.updateState("doesn-t-exist");
}
@Test
public void invalidNodeName() throws SlipStreamException {
createStateMachine(new String[] { "111_starting_with_int.1" });
}
@Test
public void doneIsFinal() throws SlipStreamException {
createStateMachine(new String[] { "n1.1"});
ExtrinsicState extrinsicState = getNodeExtrinsicState("n1.1");
State done = new DoneState(extrinsicState);
assertTrue(done.isFinal());
}
@Test
public void failureDuringProvisioning() throws IllegalArgumentException,
SecurityException, ClassNotFoundException, InstantiationException,
IllegalAccessException, InvocationTargetException,
NoSuchMethodException, SlipStreamException {
StateMachine sc = createStateMachine(new String[] { "n1.1", "n2.1" });
sc.start();
assertEquals(States.Provisioning, sc.getState());
sc.updateState("n1.1");
assertEquals(States.Provisioning, sc.getState());
sc.fail("n2.1");
assertEquals(States.SendingReports, sc.getState());
sc.updateState("n2.1");
}
@Test
public void failureDuringExecuting() throws InvalidStateException,
SlipStreamException {
String failingNodeName = "n1.1";
StateMachine sc = createStateMachine(new String[] { failingNodeName, "n2.1" });
assertEquals(States.Initializing, sc.getState());
sc.start();
assertEquals(States.Provisioning, sc.getState());
sc.updateState(failingNodeName);
assertEquals(States.Provisioning, sc.getState());
sc.updateState("n2.1");
sc.updateState(orchName);
assertEquals(States.Executing, sc.getState());
sc.failCurrentState(failingNodeName);
assertEquals(true, sc.isFailing());
assertEquals(States.Executing, sc.getState());
assertTrue(sc.isFailing());
sc.updateState("n2.1");
sc.updateState(orchName);
assertEquals(States.Executing, sc.getState());
assertTrue(sc.isFailing());
sc.updateState(failingNodeName);
assertEquals(States.SendingReports, sc.getState());
sc.updateState(failingNodeName);
assertEquals(States.SendingReports, sc.getState());
sc.updateState("n2.1");
sc.updateState(orchName);
assertEquals(States.Ready, sc.getState());
sc.updateState(failingNodeName);
assertEquals(States.Ready, sc.getState());
sc.updateState("n2.1");
sc.updateState(orchName);
assertEquals(States.Finalizing, sc.getState());
sc.updateState("n2.1");
sc.updateState(orchName);
assertEquals(States.Aborted, sc.getState());
}
@Test
public void onErrorKeepRunningInBuildImage() throws InvalidStateException,
SlipStreamException {
User user = CommonTestUtil.createUser("user1");
user.setKeepRunning(UserParameter.KEEP_RUNNING_ALWAYS);
user.store();
ImageModule parent = new ImageModule("test/parent");
Set<CloudImageIdentifier> cloudImageIdentifiers = new HashSet<CloudImageIdentifier>();
cloudImageIdentifiers.add(new CloudImageIdentifier(parent, cloudServiceName, "image-id"));
parent.setCloudImageIdentifiers(cloudImageIdentifiers);
parent.store();
ImageModule module = new ImageModule("test/image-module");
module.setModuleReference(parent);
module.setPackage(new Package("hello"));
Run run = RunFactory.getRun(module, RunType.Machine, user);
run.store();
StateMachine sc = StateMachine.getStateMachine(run);
assertEquals(States.Initializing, sc.getState());
sc.start();
assertEquals(States.Provisioning, sc.getState());
sc.tryAdvanceState(true);
assertEquals(States.Executing, sc.getState());
run = Run.abort("Error in build image", run.getUuid());
sc = StateMachine.getStateMachine(run);
assertTrue(sc.isFailing());
sc.tryAdvanceState(true);
assertEquals(States.SendingReports, sc.getState());
sc.tryAdvanceState(true);
assertEquals(States.Ready, sc.getState());
sc.tryAdvanceState(true);
assertEquals(States.Finalizing, sc.getState());
sc.tryAdvanceState(true);
assertEquals(States.Aborted, sc.getState());
run.remove();
parent.remove();
user.remove();
}
@Test
public void onSuccessKeepRunningInBuildImage() throws InvalidStateException,
SlipStreamException {
User user = CommonTestUtil.createUser("user1");
user.setKeepRunning(UserParameter.KEEP_RUNNING_ALWAYS);
user.store();
ImageModule parent = new ImageModule("test/parent");
Set<CloudImageIdentifier> cloudImageIdentifiers = new HashSet<CloudImageIdentifier>();
cloudImageIdentifiers.add(new CloudImageIdentifier(parent, cloudServiceName, "image-id"));
parent.setCloudImageIdentifiers(cloudImageIdentifiers);
parent.store();
ImageModule module = new ImageModule("test/image-module");
module.setModuleReference(parent);
module.setPackage(new Package("hello"));
Run run = RunFactory.getRun(module, RunType.Machine, user);
run.store();
StateMachine sc = StateMachine.getStateMachine(run);
assertEquals(States.Initializing, sc.getState());
sc.start();
assertEquals(States.Provisioning, sc.getState());
sc.tryAdvanceState(true);
assertEquals(States.Executing, sc.getState());
assertTrue(!sc.isFailing());
sc.tryAdvanceState(true);
assertEquals(States.SendingReports, sc.getState());
sc.tryAdvanceState(true);
assertEquals(States.Ready, sc.getState());
sc.tryAdvanceState(true);
assertEquals(States.Finalizing, sc.getState());
sc.tryAdvanceState(true);
assertEquals(States.Done, sc.getState());
run.remove();
parent.remove();
user.remove();
}
@Test(expected = CannotAdvanceFromTerminalStateException.class)
public void cannotAdvanceFromTerminalState()
throws IllegalArgumentException, SecurityException,
ClassNotFoundException, InstantiationException,
IllegalAccessException, InvocationTargetException,
NoSuchMethodException, SlipStreamException {
StateMachine sc = createStateMachine(new String[] { "n1.1" });
sc.start();
EntityManager em = sc.beginTransation();
sc.setState(States.Done, true);
sc.commitTransaction(em);
assertThat(sc.getState(), is(States.Done));
em = PersistenceUtil.createEntityManager();
run = em.find(Run.class, run.getResourceUri());
assertThat(run.getRuntimeParameterValue("ss:state"),
is(States.Done.toString()));
em.close();
sc.updateState("n1.1");
}
private StateMachine createStateContext(String[] nodes)
throws InvalidStateException, SlipStreamException {
Map<String, State> nodeStates = new HashMap<String, State>();
for (String nodeName : nodes) {
State state = createState(nodeName);
nodeStates.put(nodeName, state);
}
if (globalExtrinsicState == null) {
globalExtrinsicState = StateMachine.createGlobalExtrinsicState(run);
}
State globalState = StateFactory.createInstance(
globalExtrinsicState.getState(), globalExtrinsicState);
StateMachine sc = new StateMachine(nodeStates, globalState, run);
return sc;
}
private State createState(String nodeName) throws SlipStreamException,
InvalidStateException {
ExtrinsicState extrinsicState = getNodeExtrinsicState(nodeName);
return StateFactory.createInstance(extrinsicState.getState(),
extrinsicState);
}
private ExtrinsicState getNodeExtrinsicState(String nodeName)
throws SlipStreamException {
if (!nodeExtrinsicStates.containsKey(nodeName)) {
ExtrinsicState extrinsicState = StateMachine
.createNodeExtrinsicState(run, nodeName);
nodeExtrinsicStates.put(nodeName, extrinsicState);
}
return nodeExtrinsicStates.get(nodeName);
}
}