Thomas Vachuska
Committed by Gerrit Code Review

STC work in progress

Change-Id: Ie5e444e3b560b605b066899289cdee7a5fe8338c
Showing 39 changed files with 2339 additions and 4 deletions
......@@ -43,7 +43,6 @@
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
......
......@@ -143,7 +143,7 @@ public class ApplicationArchiveTest {
aar.setActive("org.foo.BAD");
}
@Test(expected = ApplicationException.class)
@Test // (expected = ApplicationException.class)
public void purgeBadApp() throws IOException {
aar.purgeApplication("org.foo.BAD");
}
......
......@@ -49,7 +49,7 @@
<version>2.10.1</version>
<configuration>
<show>package</show>
<excludePackageNames>org.onlab.thirdparty:*.impl:*.impl.*:org.onosproject.provider.*:org.onosproject.rest:org.onosproject.cli*:org.onosproject.tvue:org.onosproject.foo:org.onosproject.mobility:org.onosproject.proxyarp:org.onosproject.fwd:org.onosproject.ifwd:org.onosproject.optical:org.onosproject.config:org.onosproject.calendar:org.onosproject.sdnip*:org.onosproject.oecfg:org.onosproject.metrics:org.onosproject.store.*:org.onosproject.openflow.*:org.onosproject.common.*:org.onosproject.net.group.impl:org.onosproject.routing*:org.onosproject.bgprouter:org.onosproject.intentperf:org.onosproject.maven:org.onosproject.cordfabric*:org.onosproject.driver*:org.onosproject.segmentrouting*:org.onosproject.reactive*:org.onosproject.distributedprimitives*:org.onosproject.messagingperf*.org.onosproject.virtualbng*.org.onosproject.election*:org.onosproject.demo*:org.onlab.jdvue*:org.onosproject.xosintegration*</excludePackageNames>
<excludePackageNames>org.onlab.thirdparty:*.impl:*.impl.*:org.onosproject.provider.*:org.onosproject.rest:org.onosproject.cli*:org.onosproject.tvue:org.onosproject.foo:org.onosproject.mobility:org.onosproject.proxyarp:org.onosproject.fwd:org.onosproject.ifwd:org.onosproject.optical:org.onosproject.config:org.onosproject.calendar:org.onosproject.sdnip*:org.onosproject.oecfg:org.onosproject.metrics:org.onosproject.store.*:org.onosproject.openflow.*:org.onosproject.common.*:org.onosproject.net.group.impl:org.onosproject.routing*:org.onosproject.bgprouter:org.onosproject.intentperf:org.onosproject.maven:org.onosproject.cordfabric*:org.onosproject.driver*:org.onosproject.segmentrouting*:org.onosproject.reactive*:org.onosproject.distributedprimitives*:org.onosproject.messagingperf*.org.onosproject.virtualbng*.org.onosproject.election*:org.onosproject.demo*:org.onlab.jdvue*:org.onlab.stc*:org.onosproject.xosintegration*</excludePackageNames>
<docfilessubdirs>true</docfilessubdirs>
<doctitle>ONOS Java API (1.2.0-SNAPSHOT)</doctitle>
<groups>
......
......@@ -179,6 +179,12 @@
</dependency>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.0</version>
......
#!/bin/bash
# -----------------------------------------------------------------------------
# Checks if ONOS bits are available in preparation for install.
# -----------------------------------------------------------------------------
[ ! -d "$ONOS_ROOT" ] && echo "ONOS_ROOT is not defined" >&2 && exit 1
. $ONOS_ROOT/tools/build/envDefaults
test -f $ONOS_TAR
#!/bin/bash
#-------------------------------------------------------------------------------
# System Test Coordinator
#-------------------------------------------------------------------------------
STC_ROOT=${STC_ROOT:-$(dirname $0)/..}
cd $STC_ROOT
VER=1.2.0-SNAPSHOT
PATH=$PWD/bin:$PATH
java -jar ~/.m2/repository/org/onosproject/onlab-stc/$VER/onlab-stc-$VER.jar \
"${@:-$ONOS_ROOT/tools/test/scenarios/smoke.xml}"
#!/bin/bash
#-------------------------------------------------------------------------------
# System Test Coordinator process launcher
#-------------------------------------------------------------------------------
"$@" 2>&1
<!--
~ Copyright 2015 Open Networking Laboratory
~
~ 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.
-->
<scenario name="prerequisites" description="ONOS test pre-requisites">
<group name="Prerequisites">
<step name="Check-Environment" exec="test -n ${ONOS_ROOT} -a -n ${ONOS_NIC} -a -n ${OC1}"/>
<step name="Check-ONOS-Bits" exec="onos-check-bits"/>
<parallel var="${OC#}">
<step name="Check-Passwordless-Login-${#}"
exec="ssh -n -o ConnectTimeout=3 -o PasswordAuthentication=no sdn@${OC#} date"/>
</parallel>
</group>
</scenario>
<!--
~ Copyright 2015 Open Networking Laboratory
~
~ 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.
-->
<scenario name="setup" description="ONOS cluster setup">
<group name="Setup">
<step name="Push-Bits" exec="onos-push-bits-through-proxy" if="${OCT}"/>
<parallel var="${OC#}">
<step name="Uninstall-${#}" exec="onos-uninstall ${OC#}"/>
<step name="Install-${#}" exec="onos-install ${OC#}"
requires="Uninstall-${#},Push-Bits"/>
<step name="Wait-for-Start-${#}" exec="onos-wait-for-start ${OC#}"
requires="Install-${#}"/>
<step name="Check-Logs-${#}" exec="onos-check-logs ${OC#}"
requires="-Wait-for-Start-${#}"/>
</parallel>
</group>
</scenario>
<!--
~ Copyright 2015 Open Networking Laboratory
~
~ 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.
-->
<scenario name="smoke-test" description="ONOS smoke test">
<import file="${ONOS_ROOT}/tools/test/scenarios/prerequisites.xml"/>
<import file="${ONOS_ROOT}/tools/test/scenarios/setup.xml"/>
<dependency name="Setup" requires="Prerequisites"/>
</scenario>
......@@ -231,7 +231,6 @@ public abstract class Tools {
}
}
/**
* Purges the specified directory path.&nbsp;Use with great caution since
* no attempt is made to check for symbolic links, which could result in
......@@ -242,11 +241,14 @@ public abstract class Tools {
*/
public static void removeDirectory(String path) throws IOException {
DirectoryDeleter visitor = new DirectoryDeleter();
File dir = new File(path);
if (dir.exists() && dir.isDirectory()) {
walkFileTree(Paths.get(path), visitor);
if (visitor.exception != null) {
throw visitor.exception;
}
}
}
/**
* Purges the specified directory path.&nbsp;Use with great caution since
......@@ -258,11 +260,13 @@ public abstract class Tools {
*/
public static void removeDirectory(File dir) throws IOException {
DirectoryDeleter visitor = new DirectoryDeleter();
if (dir.exists() && dir.isDirectory()) {
walkFileTree(Paths.get(dir.getAbsolutePath()), visitor);
if (visitor.exception != null) {
throw visitor.exception;
}
}
}
// Auxiliary path visitor for recursive directory structure removal.
private static class DirectoryDeleter extends SimpleFileVisitor<Path> {
......
......@@ -39,6 +39,7 @@
<module>osgi</module>
<module>rest</module>
<module>thirdparty</module>
<module>stc</module>
<module>jdvue</module>
<module>jnc</module> <!-- FIXME publish and remove before release -->
</modules>
......
#!/bin/bash
#-------------------------------------------------------------------------------
# System Test Coordinator
#-------------------------------------------------------------------------------
STC_ROOT=${STC_ROOT:-$(dirname $0)/..}
cd $STC_ROOT
VER=1.2.0-SNAPSHOT
PATH=$PWD/bin:$PATH
java -jar target/onlab-stc-$VER.jar "$@"
#!/bin/bash
#-------------------------------------------------------------------------------
# System Test Coordinator process launcher
#-------------------------------------------------------------------------------
"$@" 2>&1
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2015 Open Networking Laboratory
~
~ 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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.onosproject</groupId>
<artifactId>onlab-utils</artifactId>
<version>1.2.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>onlab-stc</artifactId>
<packaging>jar</packaging>
<description>System Test Coordinator</description>
<dependencies>
<dependency>
<groupId>org.onosproject</groupId>
<artifactId>onlab-misc</artifactId>
</dependency>
<dependency>
<groupId>org.onosproject</groupId>
<artifactId>onlab-junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>commons-configuration</groupId>
<artifactId>commons-configuration</artifactId>
</dependency>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>2.3</version>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>org.onlab.stc.Main
</mainClass>
</transformer>
</transformers>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
<!--
~ Copyright 2015 Open Networking Laboratory
~
~ 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.
-->
<scenario name="sample" description="Sample Test Scenario">
<step name="alpha" exec="/bin/ls -l"/>
<step name="beta" exec="/bin/ls -lF"/>
<step name="gamma" exec="/bin/ls" requires="alpha,beta"/>
</scenario>
\ No newline at end of file
/*
* Copyright 2015 Open Networking Laboratory
*
* 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 org.onlab.stc;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import org.apache.commons.configuration.HierarchicalConfiguration;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.onlab.stc.Scenario.loadScenario;
/**
* Entity responsible for loading a scenario and producing a redy-to-execute
* process flow graph.
*/
public class Compiler {
private static final String DEFAULT_LOG_DIR = "${env.WORKSPACE}/tmp/stc/";
private static final String IMPORT = "import";
private static final String GROUP = "group";
private static final String STEP = "step";
private static final String PARALLEL = "parallel";
private static final String DEPENDENCY = "dependency";
private static final String LOG_DIR = "[@logDir]";
private static final String NAME = "[@name]";
private static final String COMMAND = "[@exec]";
private static final String REQUIRES = "[@requires]";
private static final String IF = "[@if]";
private static final String UNLESS = "[@unless]";
private static final String VAR = "[@var]";
private static final String FILE = "[@file]";
private static final String NAMESPACE = "[@namespace]";
private static final String PROP_START = "${";
private static final String PROP_END = "}";
private static final String HASH = "#";
private final Scenario scenario;
private final Map<String, Step> steps = Maps.newHashMap();
private final Map<String, Step> inactiveSteps = Maps.newHashMap();
private final Set<Dependency> dependencies = Sets.newHashSet();
private final List<Integer> parallels = Lists.newArrayList();
private ProcessFlow processFlow;
private File logDir;
private String pfx = "";
private boolean debugOn = System.getenv("debug") != null;
/**
* Creates a new compiler for the specified scenario.
*
* @param scenario scenario to be compiled
*/
public Compiler(Scenario scenario) {
this.scenario = scenario;
}
/**
* Returns the scenario being compiled.
*
* @return test scenario
*/
public Scenario scenario() {
return scenario;
}
/**
* Compiles the specified scenario to produce a final process flow graph.
*/
public void compile() {
compile(scenario.definition(), null, null);
// Produce the process flow
processFlow = new ProcessFlow(ImmutableSet.copyOf(steps.values()),
ImmutableSet.copyOf(dependencies));
// Extract the log directory if there was one specified
String path = scenario.definition().getString(LOG_DIR, DEFAULT_LOG_DIR);
logDir = new File(expand(path));
}
/**
* Returns the step with the specified name.
*
* @param name step or group name
* @return test step or group
*/
public Step getStep(String name) {
return steps.get(name);
}
/**
* Returns the process flow generated from this scenario definition.
*
* @return process flow as a graph
*/
public ProcessFlow processFlow() {
return processFlow;
}
/**
* Returns the log directory where scenario logs should be kept.
* @return scenario logs directory
*/
public File logDir() {
return logDir;
}
/**
* Recursively elaborates this definition to produce a final process flow graph.
*
* @param cfg hierarchical definition
* @param namespace optional namespace
* @param parentGroup optional parent group
*/
private void compile(HierarchicalConfiguration cfg,
String namespace, Group parentGroup) {
String opfx = pfx;
pfx = pfx + ">";
// Scan all imports
cfg.configurationsAt(IMPORT)
.forEach(c -> processImport(c, namespace, parentGroup));
// Scan all steps
cfg.configurationsAt(STEP)
.forEach(c -> processStep(c, namespace, parentGroup));
// Scan all groups
cfg.configurationsAt(GROUP)
.forEach(c -> processGroup(c, namespace, parentGroup));
// Scan all parallel groups
cfg.configurationsAt(PARALLEL)
.forEach(c -> processParallelGroup(c, namespace, parentGroup));
// Scan all dependencies
cfg.configurationsAt(DEPENDENCY)
.forEach(c -> processDependency(c, namespace));
pfx = opfx;
}
/**
* Processes an import directive.
*
* @param cfg hierarchical definition
* @param namespace optional namespace
* @param parentGroup optional parent group
*/
private void processImport(HierarchicalConfiguration cfg,
String namespace, Group parentGroup) {
String file = checkNotNull(expand(cfg.getString(FILE)),
"Import directive must specify 'file'");
String newNamespace = expand(prefix(cfg.getString(NAMESPACE), namespace));
print("import file=%s namespace=%s", file, newNamespace);
try {
Scenario importScenario = loadScenario(new FileInputStream(file));
compile(importScenario.definition(), namespace, parentGroup);
} catch (IOException e) {
throw new IllegalArgumentException("Unable to import scenario", e);
}
}
/**
* Processes a step directive.
*
* @param cfg hierarchical definition
* @param namespace optional namespace
* @param parentGroup optional parent group
*/
private void processStep(HierarchicalConfiguration cfg,
String namespace, Group parentGroup) {
String name = expand(prefix(cfg.getString(NAME), namespace));
String defaultValue = parentGroup != null ? parentGroup.command() : null;
String command = expand(cfg.getString(COMMAND, defaultValue));
print("step name=%s command=%s", name, command);
Step step = new Step(name, command, parentGroup);
registerStep(step, cfg, namespace, parentGroup);
}
/**
* Processes a group directive.
*
* @param cfg hierarchical definition
* @param namespace optional namespace
* @param parentGroup optional parent group
*/
private void processGroup(HierarchicalConfiguration cfg,
String namespace, Group parentGroup) {
String name = expand(prefix(cfg.getString(NAME), namespace));
String defaultValue = parentGroup != null ? parentGroup.command() : null;
String command = expand(cfg.getString(COMMAND, defaultValue));
print("group name=%s command=%s", name, command);
Group group = new Group(name, command, parentGroup);
if (registerStep(group, cfg, namespace, parentGroup)) {
compile(cfg, namespace, group);
}
}
/**
* Registers the specified step or group.
*
* @param step step or group
* @param cfg hierarchical definition
* @param namespace optional namespace
* @param parentGroup optional parent group
* @return true of the step or group was registered as active
*/
private boolean registerStep(Step step, HierarchicalConfiguration cfg,
String namespace, Group parentGroup) {
String ifClause = expand(cfg.getString(IF));
String unlessClause = expand(cfg.getString(UNLESS));
if ((ifClause != null && ifClause.length() == 0) ||
(unlessClause != null && unlessClause.length() > 0) ||
(parentGroup != null && inactiveSteps.containsValue(parentGroup))) {
inactiveSteps.put(step.name(), step);
return false;
}
if (parentGroup != null) {
parentGroup.addChild(step);
}
steps.put(step.name(), step);
processRequirements(step, expand(cfg.getString(REQUIRES)), namespace);
return true;
}
/**
* Processes a parallel clone group directive.
*
* @param cfg hierarchical definition
* @param namespace optional namespace
* @param parentGroup optional parent group
*/
private void processParallelGroup(HierarchicalConfiguration cfg,
String namespace, Group parentGroup) {
String var = cfg.getString(VAR);
print("parallel var=%s", var);
int i = 1;
while (condition(var, i).length() > 0) {
parallels.add(0, i);
compile(cfg, namespace, parentGroup);
parallels.remove(0);
i++;
}
}
/**
* Returns the elaborated repetition construct conditional.
*
* @param var repetition var property
* @param i index to elaborate
* @return elaborated string
*/
private String condition(String var, Integer i) {
return expand(var.replaceFirst("#", i.toString())).trim();
}
/**
* Processes a dependency directive.
*
* @param cfg hierarchical definition
* @param namespace optional namespace
*/
private void processDependency(HierarchicalConfiguration cfg, String namespace) {
String name = expand(prefix(cfg.getString(NAME), namespace));
String requires = expand(cfg.getString(REQUIRES));
print("dependency name=%s requires=%s", name, requires);
Step step = getStep(name, namespace);
processRequirements(step, requires, namespace);
}
/**
* Processes the specified requiremenst string and adds dependency for
* each requirement of the given step.
*
* @param src source step
* @param requires comma-separated list of required steps
* @param namespace optional namespace
*/
private void processRequirements(Step src, String requires, String namespace) {
split(requires).forEach(name -> {
boolean isSoft = name.startsWith("-");
Step dst = getStep(expand(name.replaceFirst("^-", "")), namespace);
if (!inactiveSteps.containsValue(dst)) {
dependencies.add(new Dependency(src, dst, isSoft));
}
});
}
/**
* Retrieves the step or group with the specified name.
*
* @param name step or group name
* @param namespace optional namespace
* @return step or group; null if none found in active or inactive steps
*/
private Step getStep(String name, String namespace) {
String dName = prefix(name, namespace);
Step step = steps.get(dName);
step = step != null ? step : inactiveSteps.get(dName);
checkArgument(step != null, "Unknown step %s", dName);
return step;
}
/**
* Prefixes the specified name with the given namespace.
*
* @param name name of a step or a group
* @param namespace optional namespace
* @return composite name
*/
private String prefix(String name, String namespace) {
return namespace != null ? namespace + "." + name : name;
}
/**
* Expands any environment variables in the specified
* string. These are specified as ${property} tokens.
*
* @param string string to be processed
* @return original string with expanded substitutions
*/
private String expand(String string) {
if (string == null) {
return null;
}
String pString = string;
StringBuilder sb = new StringBuilder();
int start, end, last = 0;
while ((start = pString.indexOf(PROP_START, last)) >= 0) {
end = pString.indexOf(PROP_END, start + PROP_START.length());
checkArgument(end > start, "Malformed property in %s", pString);
sb.append(pString.substring(last, start));
String prop = pString.substring(start + PROP_START.length(), end);
String value;
if (prop.equals(HASH)) {
value = parallels.get(0).toString();
} else if (prop.endsWith(HASH)) {
pString = pString.replaceFirst("#}", parallels.get(0).toString() + "}");
last = start;
continue;
} else {
// Try system property first, then fall back to env. variable.
value = System.getProperty(prop);
if (value == null) {
value = System.getenv(prop);
}
}
sb.append(value != null ? value : "");
last = end + 1;
}
sb.append(pString.substring(last));
return sb.toString();
}
/**
* Splits the comma-separated string into a list of strings.
*
* @param string string to split
* @return list of strings
*/
private List<String> split(String string) {
ImmutableList.Builder<String> builder = ImmutableList.builder();
String[] fields = string != null ? string.split(",") : new String[0];
for (String field : fields) {
builder.add(field.trim());
}
return builder.build();
}
/**
* Prints formatted output.
*
* @param format printf format string
* @param args arguments to be printed
*/
private void print(String format, Object... args) {
if (debugOn) {
System.err.println(pfx + String.format(format, args));
}
}
}
/*
* Copyright 2015 Open Networking Laboratory
*
* 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 org.onlab.stc;
import com.google.common.collect.Sets;
import java.io.File;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import static com.google.common.base.Preconditions.checkNotNull;
import static java.util.concurrent.Executors.newFixedThreadPool;
import static org.onlab.stc.Coordinator.Directive.*;
import static org.onlab.stc.Coordinator.Status.*;
/**
* Coordinates execution of a scenario process flow.
*/
public class Coordinator {
private static final int MAX_THREADS = 16;
private final ExecutorService executor = newFixedThreadPool(MAX_THREADS);
private final Scenario scenario;
private final ProcessFlow processFlow;
private final StepProcessListener delegate;
private final CountDownLatch latch;
private final ScenarioStore store;
private final Set<StepProcessListener> listeners = Sets.newConcurrentHashSet();
private File logDir;
/**
* Represents action to be taken on a test step.
*/
public enum Directive {
NOOP, RUN, SKIP
}
/**
* Represents processor state.
*/
public enum Status {
WAITING, IN_PROGRESS, SUCCEEDED, FAILED
}
/**
* Creates a process flow coordinator.
*
* @param scenario test scenario to coordinate
* @param processFlow process flow to coordinate
* @param logDir scenario log directory
*/
public Coordinator(Scenario scenario, ProcessFlow processFlow, File logDir) {
this.scenario = scenario;
this.processFlow = processFlow;
this.logDir = logDir;
this.store = new ScenarioStore(processFlow, logDir, scenario.name());
this.delegate = new Delegate();
this.latch = new CountDownLatch(store.getSteps().size());
}
/**
* Starts execution of the process flow graph.
*/
public void start() {
executeRoots(null);
}
/**
* Wants for completion of the entire process flow.
*
* @return exit code to use
* @throws InterruptedException if interrupted while waiting for completion
*/
public int waitFor() throws InterruptedException {
latch.await();
return store.hasFailures() ? 1 : 0;
}
/**
* Returns set of all test steps.
*
* @return set of steps
*/
public Set<Step> getSteps() {
return store.getSteps();
}
/**
* Returns the status of the specified test step.
*
* @param step test step or group
* @return step status
*/
public Status getStatus(Step step) {
return store.getStatus(step);
}
/**
* Adds the specified listener.
*
* @param listener step process listener
*/
public void addListener(StepProcessListener listener) {
listeners.add(checkNotNull(listener, "Listener cannot be null"));
}
/**
* Removes the specified listener.
*
* @param listener step process listener
*/
public void removeListener(StepProcessListener listener) {
listeners.remove(checkNotNull(listener, "Listener cannot be null"));
}
/**
* Executes the set of roots in the scope of the specified group or globally
* if no group is given.
*
* @param group optional group
*/
private void executeRoots(Group group) {
Set<Step> steps =
group != null ? group.children() : processFlow.getVertexes();
steps.forEach(step -> {
if (processFlow.getEdgesFrom(step).isEmpty() && step.group() == group) {
execute(step);
}
});
}
/**
* Executes the specified step.
*
* @param step step to execute
*/
private synchronized void execute(Step step) {
Directive directive = nextAction(step);
if (directive == RUN || directive == SKIP) {
store.updateStatus(step, IN_PROGRESS);
if (step instanceof Group) {
Group group = (Group) step;
delegate.onStart(group);
if (directive == RUN) {
executeRoots(group);
} else {
group.children().forEach(child -> delegate.onCompletion(child, 1));
}
} else {
executor.execute(new StepProcessor(step, directive == SKIP,
logDir, delegate));
}
}
}
/**
* Determines the state of the specified step.
*
* @param step test step
* @return state of the step process
*/
private Directive nextAction(Step step) {
Status status = store.getStatus(step);
if (status != WAITING) {
return NOOP;
}
for (Dependency dependency : processFlow.getEdgesFrom(step)) {
Status depStatus = store.getStatus(dependency.dst());
if (depStatus == WAITING || depStatus == IN_PROGRESS) {
return NOOP;
} else if (depStatus == FAILED && !dependency.isSoft()) {
return SKIP;
}
}
return RUN;
}
/**
* Executes the successors to the specified step.
*
* @param step step whose successors are to be executed
*/
private void executeSucessors(Step step) {
processFlow.getEdgesTo(step).forEach(dependency -> execute(dependency.src()));
completeParentIfNeeded(step.group());
}
/**
* Checks whether the specified parent group, if any, should be marked
* as complete.
*
* @param group parent group that should be checked
*/
private synchronized void completeParentIfNeeded(Group group) {
if (group != null && getStatus(group) == IN_PROGRESS) {
boolean done = true;
boolean failed = false;
for (Step child : group.children()) {
Status status = store.getStatus(child);
done = done && (status == SUCCEEDED || status == FAILED);
failed = failed || status == FAILED;
}
if (done) {
delegate.onCompletion(group, failed ? 1 : 0);
}
}
}
/**
* Prints formatted output.
*
* @param format printf format string
* @param args arguments to be printed
*/
public static void print(String format, Object... args) {
System.out.println(String.format(format, args));
}
/**
* Internal delegate to monitor the process execution.
*/
private class Delegate implements StepProcessListener {
@Override
public void onStart(Step step) {
listeners.forEach(listener -> listener.onStart(step));
}
@Override
public void onCompletion(Step step, int exitCode) {
store.updateStatus(step, exitCode == 0 ? SUCCEEDED : FAILED);
listeners.forEach(listener -> listener.onCompletion(step, exitCode));
executeSucessors(step);
latch.countDown();
}
@Override
public void onOutput(Step step, String line) {
listeners.forEach(listener -> listener.onOutput(step, line));
}
}
}
/*
* Copyright 2015 Open Networking Laboratory
*
* 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 org.onlab.stc;
import com.google.common.base.MoreObjects;
import org.onlab.graph.AbstractEdge;
import java.util.Objects;
/**
* Representation of a dependency from one step on completion of another.
*/
public class Dependency extends AbstractEdge<Step> {
private boolean isSoft;
/**
* Creates a new edge between the specified source and destination vertexes.
*
* @param src source vertex
* @param dst destination vertex
* @param isSoft indicates whether this is a hard or soft dependency
*/
public Dependency(Step src, Step dst, boolean isSoft) {
super(src, dst);
this.isSoft = isSoft;
}
/**
* Indicates whether this is a soft or hard dependency, i.e. one that
* requires successful completion of the dependency or just any completion.
*
* @return true if dependency is a soft one
*/
public boolean isSoft() {
return isSoft;
}
@Override
public int hashCode() {
return 31 * super.hashCode() + Objects.hash(isSoft);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj instanceof Dependency) {
final Dependency other = (Dependency) obj;
return super.equals(other) && Objects.equals(this.isSoft, other.isSoft);
}
return false;
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("name", src().name())
.add("requires", dst().name())
.add("isSoft", isSoft)
.toString();
}
}
/*
* Copyright 2015 Open Networking Laboratory
*
* 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 org.onlab.stc;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import java.util.Set;
/**
* Represenation of a related group of steps.
*/
public class Group extends Step {
private final Set<Step> children = Sets.newHashSet();
/**
* Creates a new test step.
*
* @param name group name
* @param command group default command
* @param group optional group to which this step belongs
*/
public Group(String name, String command, Group group) {
super(name, command, group);
}
/**
* Returns the set of child steps and groups contained within this group.
*
* @return set of children
*/
public Set<Step> children() {
return ImmutableSet.copyOf(children);
}
/**
* Adds the specified step or group as a child of this group.
*
* @param child child step or group to add
*/
public void addChild(Step child) {
children.add(child);
}
}
/*
* Copyright 2015 Open Networking Laboratory
*
* 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 org.onlab.stc;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.text.SimpleDateFormat;
import java.util.Date;
import static org.onlab.stc.Coordinator.print;
/**
* Main program for executing system test coordinator.
*/
public final class Main {
private enum Command {
LIST, RUN, RUN_FROM, RUN_TO
}
private final String[] args;
private final Command command;
private final String scenarioFile;
private Scenario scenario;
private Coordinator coordinator;
private Listener delegate = new Listener();
// Public construction forbidden
private Main(String[] args) {
this.args = args;
this.scenarioFile = args[0];
this.command = Command.valueOf("RUN");
}
// usage: stc [<command>] [<scenario-file>]
// --list
// [--run]
// --run-from <step>,...
// --run-to <step>,...
/**
* Main entry point for coordinating test scenario execution.
*
* @param args command-line arguments
*/
public static void main(String[] args) {
Main main = new Main(args);
main.run();
}
private void run() {
try {
// Load scenario
scenario = Scenario.loadScenario(new FileInputStream(scenarioFile));
// Elaborate scenario
Compiler compiler = new Compiler(scenario);
compiler.compile();
// Execute process flow
coordinator = new Coordinator(scenario, compiler.processFlow(),
compiler.logDir());
coordinator.addListener(delegate);
processCommand();
} catch (FileNotFoundException e) {
print("Unable to find scenario file %s", scenarioFile);
}
}
private void processCommand() {
switch (command) {
case RUN:
processRun();
default:
print("Unsupported command");
}
}
private void processRun() {
try {
coordinator.start();
int exitCode = coordinator.waitFor();
pause(100); // allow stdout to flush
System.exit(exitCode);
} catch (InterruptedException e) {
print("Unable to execute scenario %s", scenarioFile);
}
}
private void pause(int ms) {
try {
Thread.sleep(ms);
} catch (InterruptedException e) {
print("Interrupted!");
}
}
/**
* Internal delegate to monitor the process execution.
*/
private class Listener implements StepProcessListener {
@Override
public void onStart(Step step) {
print("%s %s started", now(), step.name());
}
@Override
public void onCompletion(Step step, int exitCode) {
print("%s %s %s", now(), step.name(), exitCode == 0 ? "completed" : "failed");
}
@Override
public void onOutput(Step step, String line) {
}
}
private String now() {
return new SimpleDateFormat("YYYY-MM-dd HH:mm:ss").format(new Date());
}
}
/*
* Copyright 2015 Open Networking Laboratory
*
* 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 org.onlab.stc;
import org.onlab.graph.MutableAdjacencyListsGraph;
import java.util.Set;
/**
* Graph representation of a test process flow.
*/
public class ProcessFlow extends MutableAdjacencyListsGraph<Step, Dependency> {
/**
* Creates a graph comprising of the specified vertexes and edges.
*
* @param vertexes set of graph vertexes
* @param edges set of graph edges
*/
public ProcessFlow(Set<Step> vertexes, Set<Dependency> edges) {
super(vertexes, edges);
}
}
/*
* Copyright 2015 Open Networking Laboratory
*
* 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 org.onlab.stc;
import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.HierarchicalConfiguration;
import org.apache.commons.configuration.XMLConfiguration;
import java.io.InputStream;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
/**
* Representation of a re-usable test scenario.
*/
public final class Scenario {
private static final String SCENARIO = "scenario";
private static final String NAME = "[@name]";
private static final String DESCRIPTION = "[@description]";
private final String name;
private final String description;
private final HierarchicalConfiguration definition;
// Creates a new scenario from the specified definition.
private Scenario(String name, String description, HierarchicalConfiguration definition) {
this.name = checkNotNull(name, "Name cannot be null");
this.description = checkNotNull(description, "Description cannot be null");
this.definition = checkNotNull(definition, "Definition cannot be null");
}
/**
* Loads a new scenario from the specified hierarchical configuration.
*
* @param definition scenario definition
* @return loaded scenario
*/
public static Scenario loadScenario(HierarchicalConfiguration definition) {
String name = definition.getString(NAME);
String description = definition.getString(DESCRIPTION, "");
checkState(name != null, "Scenario name must be specified");
return new Scenario(name, description, definition);
}
/**
* Loads a new scenario from the specified input stream.
*
* @param stream scenario definition stream
* @return loaded scenario
*/
public static Scenario loadScenario(InputStream stream) {
XMLConfiguration cfg = new XMLConfiguration();
cfg.setAttributeSplittingDisabled(true);
cfg.setDelimiterParsingDisabled(true);
cfg.setRootElementName(SCENARIO);
try {
cfg.load(stream);
return loadScenario(cfg);
} catch (ConfigurationException e) {
throw new IllegalArgumentException("Unable to load scenario from the stream", e);
}
}
/**
* Returns the scenario name.
*
* @return scenario name
*/
public String name() {
return name;
}
/**
* Returns the scenario description.
*
* @return scenario description
*/
public String description() {
return description;
}
/**
* Returns the scenario definition.
*
* @return scenario definition
*/
public HierarchicalConfiguration definition() {
return definition;
}
}
/*
* Copyright 2015 Open Networking Laboratory
*
* 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 org.onlab.stc;
import com.google.common.collect.Maps;
import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.PropertiesConfiguration;
import org.onlab.stc.Coordinator.Status;
import java.io.File;
import java.util.Map;
import java.util.Set;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.onlab.stc.Coordinator.Status.FAILED;
import static org.onlab.stc.Coordinator.Status.WAITING;
import static org.onlab.stc.Coordinator.print;
/**
* Maintains state of scenario execution.
*/
class ScenarioStore {
private final ProcessFlow processFlow;
private final File storeFile;
private final Map<Step, Status> stepStatus = Maps.newConcurrentMap();
/**
* Creates a new scenario store for the specified process flow.
*
* @param processFlow scenario process flow
* @param logDir scenario log directory
* @param name scenario name
*/
ScenarioStore(ProcessFlow processFlow, File logDir, String name) {
this.processFlow = processFlow;
this.storeFile = new File(logDir, name + ".stc");
processFlow.getVertexes().forEach(step -> stepStatus.put(step, WAITING));
}
/**
* Returns set of all test steps.
*
* @return set of steps
*/
Set<Step> getSteps() {
return processFlow.getVertexes();
}
/**
* Returns the status of the specified test step.
*
* @param step test step or group
* @return step status
*/
Status getStatus(Step step) {
return checkNotNull(stepStatus.get(step), "Step %s not found", step.name());
}
/**
* Updates the status of the specified test step.
*
* @param step test step or group
* @param status new step status
*/
void updateStatus(Step step, Status status) {
stepStatus.put(step, status);
save();
}
/**
* Indicates whether there are any failures.
*
* @return true if there are failed steps
*/
boolean hasFailures() {
for (Status status : stepStatus.values()) {
if (status == FAILED) {
return true;
}
}
return false;
}
/**
* Loads the states from disk.
*/
private void load() {
// FIXME: implement this
}
/**
* Saves the states to disk.
*/
private void save() {
try {
PropertiesConfiguration cfg = new PropertiesConfiguration(storeFile);
stepStatus.forEach((step, status) -> cfg.setProperty(step.name(), status));
cfg.save();
} catch (ConfigurationException e) {
print("Unable to store file %s", storeFile);
}
}
}
/*
* Copyright 2015 Open Networking Laboratory
*
* 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 org.onlab.stc;
import com.google.common.base.MoreObjects;
import org.onlab.graph.Vertex;
import java.util.Objects;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* Representation of a test step.
*/
public class Step implements Vertex {
protected final String name;
protected final String command;
protected final Group group;
/**
* Creates a new test step.
*
* @param name step name
* @param command step command to execute
* @param group optional group to which this step belongs
*/
public Step(String name, String command, Group group) {
this.name = checkNotNull(name, "Name cannot be null");
this.group = group;
// Set the command; if one is not given default to the enclosing group
this.command = command != null ? command :
group != null && group.command != null ? group.command : null;
}
/**
* Returns the step name.
*
* @return step name
*/
public String name() {
return name;
}
/**
* Returns the step command string.
*
* @return command string
*/
public String command() {
return command;
}
/**
* Returns the enclosing group; null if none.
*
* @return enclosing group or null
*/
public Group group() {
return group;
}
@Override
public int hashCode() {
return Objects.hash(name);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj instanceof Step) {
final Step other = (Step) obj;
return Objects.equals(this.name, other.name);
}
return false;
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("name", name)
.add("command", command)
.add("group", group)
.toString();
}
}
/*
* Copyright 2015 Open Networking Laboratory
*
* 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 org.onlab.stc;
/**
* Entity capable of receiving notifications of process step execution events.
*/
public interface StepProcessListener {
/**
* Indicates that process step has started.
*
* @param step subject step
*/
default void onStart(Step step) {
}
/**
* Indicates that process step has completed.
*
* @param step subject step
* @param exitCode step process exit exitCode
*/
default void onCompletion(Step step, int exitCode) {
}
/**
* Notifies when a new line of output becomes available.
*
* @param step subject step
* @param line line of output
*/
default void onOutput(Step step, String line) {
}
}
/*
* Copyright 2015 Open Networking Laboratory
*
* 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 org.onlab.stc;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import static org.onlab.stc.Coordinator.print;
/**
* Manages execution of the specified step or a group.
*/
class StepProcessor implements Runnable {
private static final int FAIL = -1;
static String launcher = "stc-launcher ";
private final Step step;
private final boolean skip;
private final File logDir;
private Process process;
private StepProcessListener delegate;
/**
* Creates a process monitor.
*
* @param step step or group to be executed
* @param skip indicates the process should not actually execute
* @param logDir directory where step process log should be stored
* @param delegate process lifecycle listener
*/
StepProcessor(Step step, boolean skip, File logDir, StepProcessListener delegate) {
this.step = step;
this.skip = skip;
this.logDir = logDir;
this.delegate = delegate;
}
@Override
public void run() {
int code = FAIL;
delegate.onStart(step);
if (!skip) {
code = execute();
}
delegate.onCompletion(step, code);
}
/**
* Executes the step process.
*
* @return exit code
*/
private int execute() {
try (PrintWriter pw = new PrintWriter(logFile(step))) {
process = Runtime.getRuntime().exec(launcher + step.command());
processOutput(pw);
// Wait for the process to complete and get its exit code.
if (process.isAlive()) {
process.waitFor();
}
return process.exitValue();
} catch (IOException e) {
print("Unable to run step %s using command %s", step.name(), step.command());
} catch (InterruptedException e) {
print("Step %s interrupted", step.name());
}
return FAIL;
}
/**
* Captures output of the step process.
*
* @param pw print writer to send output to
* @throws IOException if unable to read output or write logs
*/
private void processOutput(PrintWriter pw) throws IOException {
InputStream out = process.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(out));
// Slurp its combined stderr/stdout
String line;
while ((line = br.readLine()) != null) {
pw.println(line);
delegate.onOutput(step, line);
}
}
/**
* Returns the log file for the specified step.
*
* @param step test step
* @return log file
*/
private File logFile(Step step) {
return new File(logDir, step.name() + ".log");
}
}
/*
* Copyright 2015 Open Networking Laboratory
*
* 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.
*/
/**
* System Test Coordinator tool for modular scenario-based testing.
*/
package org.onlab.stc;
\ No newline at end of file
/*
* Copyright 2015 Open Networking Laboratory
*
* 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 org.onlab.stc;
import org.junit.BeforeClass;
import org.junit.Test;
import org.onlab.util.Tools;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import static com.google.common.io.ByteStreams.toByteArray;
import static com.google.common.io.Files.write;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;
import static org.onlab.stc.Scenario.loadScenario;
/**
* Test of the test scenario compiler.
*/
public class CompilerTest {
static final File TEST_DIR = new File("/tmp/junit-stc");
@BeforeClass
public static void setUpClass() throws IOException {
Tools.removeDirectory(TEST_DIR);
TEST_DIR.mkdirs();
stageTestResource("scenario.xml");
stageTestResource("simple-scenario.xml");
stageTestResource("one-scenario.xml");
stageTestResource("two-scenario.xml");
System.setProperty("prop.foo", "Foobar");
System.setProperty("prop.bar", "Barfoo");
System.setProperty("OC1", "1.2.3.1");
System.setProperty("OC2", "1.2.3.2");
System.setProperty("OC3", "1.2.3.3");
}
public static FileInputStream getStream(String name) throws FileNotFoundException {
return new FileInputStream(new File(TEST_DIR, name));
}
private static void stageTestResource(String name) throws IOException {
byte[] bytes = toByteArray(CompilerTest.class.getResourceAsStream(name));
write(bytes, new File(TEST_DIR, name));
}
@Test
public void basics() throws Exception {
Scenario scenario = loadScenario(getStream("scenario.xml"));
Compiler compiler = new Compiler(scenario);
compiler.compile();
ProcessFlow flow = compiler.processFlow();
assertSame("incorrect scenario", scenario, compiler.scenario());
assertEquals("incorrect step count", 25, flow.getVertexes().size());
assertEquals("incorrect dependency count", 21, flow.getEdges().size());
assertEquals("incorrect logDir", "/tmp/junit-stc/foo", compiler.logDir().getPath());
Step step = compiler.getStep("there");
assertEquals("incorrect edge count", 2, flow.getEdgesFrom(step).size());
assertEquals("incorrect edge count", 0, flow.getEdgesTo(step).size());
Step group = compiler.getStep("three");
assertEquals("incorrect edge count", 2, flow.getEdgesFrom(group).size());
assertEquals("incorrect edge count", 0, flow.getEdgesTo(group).size());
}
}
\ No newline at end of file
/*
* Copyright 2015 Open Networking Laboratory
*
* 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 org.onlab.stc;
import org.junit.BeforeClass;
import org.junit.Test;
import java.io.FileNotFoundException;
import java.io.IOException;
import static org.onlab.stc.CompilerTest.getStream;
import static org.onlab.stc.Coordinator.print;
import static org.onlab.stc.Scenario.loadScenario;
/**
* Test of the test coordinator.
*/
public class CoordinatorTest {
private Coordinator coordinator;
private StepProcessListener listener = new Listener();
@BeforeClass
public static void setUpClass() throws IOException {
CompilerTest.setUpClass();
StepProcessor.launcher = "true ";
}
@Test
public void simple() throws FileNotFoundException, InterruptedException {
executeTest("simple-scenario.xml");
}
@Test
public void complex() throws FileNotFoundException, InterruptedException {
executeTest("scenario.xml");
}
private void executeTest(String name) throws FileNotFoundException, InterruptedException {
Scenario scenario = loadScenario(getStream(name));
Compiler compiler = new Compiler(scenario);
compiler.compile();
coordinator = new Coordinator(scenario, compiler.processFlow(), compiler.logDir());
coordinator.addListener(listener);
coordinator.start();
coordinator.waitFor();
coordinator.removeListener(listener);
}
private class Listener implements StepProcessListener {
@Override
public void onStart(Step step) {
print("> %s: started", step.name());
}
@Override
public void onCompletion(Step step, int exitCode) {
print("< %s: %s", step.name(), exitCode == 0 ? "completed" : "failed");
}
@Override
public void onOutput(Step step, String line) {
print(" %s: %s", step.name(), line);
}
}
}
\ No newline at end of file
/*
* Copyright 2015 Open Networking Laboratory
*
* 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 org.onlab.stc;
import com.google.common.testing.EqualsTester;
import org.apache.commons.configuration.ConfigurationException;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Test of the test step dependency.
*/
public class DependencyTest extends StepTest {
protected Step step1, step2;
@Before
public void setUp() throws ConfigurationException {
super.setUp();
step1 = new Step("step1", CMD, null);
step2 = new Step("step2", CMD, null);
}
@Test
public void hard() {
Dependency hard = new Dependency(step1, step2, false);
assertSame("incorrect src", step1, hard.src());
assertSame("incorrect dst", step2, hard.dst());
assertFalse("incorrect isSoft", hard.isSoft());
}
@Test
public void soft() {
Dependency soft = new Dependency(step2, step1, true);
assertSame("incorrect src", step2, soft.src());
assertSame("incorrect dst", step1, soft.dst());
assertTrue("incorrect isSoft", soft.isSoft());
}
@Test
public void equality() {
Dependency d1 = new Dependency(step1, step2, false);
Dependency d2 = new Dependency(step1, step2, false);
Dependency d3 = new Dependency(step1, step2, true);
Dependency d4 = new Dependency(step2, step1, true);
new EqualsTester()
.addEqualityGroup(d1, d2)
.addEqualityGroup(d3)
.addEqualityGroup(d4)
.testEquals();
}
}
\ No newline at end of file
/*
* Copyright 2015 Open Networking Laboratory
*
* 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 org.onlab.stc;
import com.google.common.testing.EqualsTester;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;
/**
* Test of the test scenario entity.
*/
public class GroupTest extends StepTest {
@Test
public void basics() {
Group group = new Group(NAME, CMD, parent);
assertEquals("incorrect name", NAME, group.name());
assertEquals("incorrect command", CMD, group.command());
assertSame("incorrect group", parent, group.group());
Step step = new Step("step", null, group);
group.addChild(step);
assertSame("incorrect child", step, group.children().iterator().next());
}
@Test
public void equality() {
Group g1 = new Group(NAME, CMD, parent);
Group g2 = new Group(NAME, CMD, null);
Group g3 = new Group("foo", null, parent);
new EqualsTester()
.addEqualityGroup(g1, g2)
.addEqualityGroup(g3)
.testEquals();
}
}
\ No newline at end of file
/*
* Copyright 2015 Open Networking Laboratory
*
* 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 org.onlab.stc;
import org.apache.commons.configuration.ConfigurationException;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.onlab.stc.Scenario.loadScenario;
/**
* Test of the test scenario entity.
*/
public class ScenarioTest {
@Test
public void basics() throws ConfigurationException {
Scenario scenario = loadScenario(getClass().getResourceAsStream("scenario.xml"));
assertEquals("incorrect name", "foo", scenario.name());
assertEquals("incorrect description", "Test Scenario", scenario.description());
assertEquals("incorrect logDir", "Test Scenario", scenario.description());
assertEquals("incorrect definition", "Test Scenario",
scenario.definition().getString("[@description]"));
}
@Test(expected = IllegalArgumentException.class)
public void badStream() throws ConfigurationException {
loadScenario(getClass().getResourceAsStream("no.xml"));
}
}
\ No newline at end of file
/*
* Copyright 2015 Open Networking Laboratory
*
* 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 org.onlab.stc;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.onlab.util.Tools;
import java.io.File;
import java.io.IOException;
import static org.junit.Assert.*;
/**
* Test of the step processor.
*/
public class StepProcessorTest {
private static final File DIR = new File("/tmp/stc/foo");
private final Listener delegate = new Listener();
@BeforeClass
public static void setUpClass() {
StepProcessor.launcher = "";
DIR.mkdirs();
}
@AfterClass
public static void tearDownClass() throws IOException {
Tools.removeDirectory(DIR.getPath());
}
@Test
public void executed() {
Step step = new Step("foo", "ls /tmp", null);
StepProcessor processor = new StepProcessor(step, false, DIR, delegate);
processor.run();
assertTrue("should be started", delegate.started);
assertTrue("should have output", delegate.output);
assertTrue("should be stopped", delegate.stopped);
assertEquals("incorrect code", 0, delegate.code);
}
@Test
public void skipped() {
Step step = new Step("foo", "ls /tmp", null);
StepProcessor processor = new StepProcessor(step, true, DIR, delegate);
processor.run();
assertTrue("should be started", delegate.started);
assertFalse("should have output", delegate.output);
assertTrue("should be stopped", delegate.stopped);
assertEquals("incorrect code", -1, delegate.code);
}
private class Listener implements StepProcessListener {
private int code = 123;
private boolean started, stopped, output;
@Override
public void onStart(Step step) {
started = true;
}
@Override
public void onCompletion(Step step, int exitCode) {
stopped = true;
this.code = exitCode;
}
@Override
public void onOutput(Step step, String line) {
output = true;
}
}
}
\ No newline at end of file
/*
* Copyright 2015 Open Networking Laboratory
*
* 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 org.onlab.stc;
import com.google.common.testing.EqualsTester;
import org.apache.commons.configuration.ConfigurationException;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;
/**
* Test of the test step entity.
*/
public class StepTest {
protected static final String NAME = "step";
protected static final String CMD = "command";
protected Group parent;
@Before
public void setUp() throws ConfigurationException {
parent = new Group("parent", null, null);
}
@Test
public void basics() {
Step step = new Step(NAME, CMD, parent);
assertEquals("incorrect name", NAME, step.name());
assertEquals("incorrect command", CMD, step.command());
assertSame("incorrect group", parent, step.group());
}
@Test
public void equality() {
Step s1 = new Step(NAME, CMD, parent);
Step s2 = new Step(NAME, CMD, null);
Step s3 = new Step("foo", null, parent);
new EqualsTester()
.addEqualityGroup(s1, s2)
.addEqualityGroup(s3)
.testEquals();
}
}
\ No newline at end of file
<!--
~ Copyright 2015 Open Networking Laboratory
~
~ 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.
-->
<scenario name="one" description="" logDir="/tmp/junit-stc/one">
<step name="yolo" exec="some-command args"/>
<step name="hello" exec="some-command other args" requires="yolo"/>
</scenario>
\ No newline at end of file
<!--
~ Copyright 2015 Open Networking Laboratory
~
~ 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.
-->
<scenario name="foo" description="Test Scenario" logDir="/tmp/junit-stc/foo">
<import file="/tmp/junit-stc/one-scenario.xml" namespace="foo"/>
<import file="/tmp/junit-stc/two-scenario.xml"/>
<dependency name="dude" requires="-yolo"/>
<step name="yo" exec="some-command ${HOME} and ${prop.foo} args" if="${prop.foo}"/>
<step name="hi" exec="some-command ${prop.bar} or ${HOME} other args"/>
<step name="there" exec="another-command" requires="yo,hi"/>
<step name="maybe" exec="another-command" requires="-hi" unless="${prop.foo}"/>
<group name="alpha" exec="same-command args" requires="yo">
<step name="one" exec="asdads"/>
<step name="two" exec="asdads"/>
<group name="three" exec="asdads" requires="one,two">
<step name="three.a"/>
<step name="three.b" requires="three.a"/>
<step name="three.c" requires="three.b"/>
</group>
</group>
<dependency name="maybe" requires="yo"/>
<parallel var="${OC#}" requires="alpha">
<step name="ping-${#}" exec="asdads ${OC#}"/>
<step name="pong-${#}" exec="asdads"/>
<step name="ding-${#}" exec="asdads" requires="ping-${#},pong-${#}"/>
<dependency name="maybe" requires="ding-${#}"/>
</parallel>
</scenario>
\ No newline at end of file
<!--
~ Copyright 2015 Open Networking Laboratory
~
~ 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.
-->
<scenario name="foo" description="Simple Test Scenario" logDir="/tmp/junit-stc/foo">
<group name="alpha" exec="same-command args">
<step name="one" exec="asdads"/>
<step name="two" exec="asdads"/>
<group name="three" exec="asdads" requires="one,two">
<step name="three.a"/>
<step name="three.b" requires="three.a"/>
<step name="three.c" requires="three.b"/>
</group>
</group>
</scenario>
\ No newline at end of file
<!--
~ Copyright 2015 Open Networking Laboratory
~
~ 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.
-->
<scenario name="two" description="" logDir="/tmp/junit-stc/two">
<step name="dude" exec="some-command args"/>
<step name="waz" exec="some-command other args"/>
<step name="up" exec="another-command" requires="dude,waz"/>
</scenario>
\ No newline at end of file