Thomas Vachuska

Removed STC as it has been moved to a separate repository.

Change-Id: I419079b4ce539e5c082dd3f9dcf2bf8a91e2e45a
Showing 43 changed files with 0 additions and 5458 deletions
......@@ -37,7 +37,6 @@
<module>yangutils</module>
<module>osgi</module>
<module>rest</module>
<module>stc</module>
<module>jdvue</module>
<module>osgiwrap</module>
</modules>
......
#!/bin/bash
#-------------------------------------------------------------------------------
# System Test Coordinator
#-------------------------------------------------------------------------------
STC_ROOT=${STC_ROOT:-$(dirname $0)/..}
cd $STC_ROOT
VER=1.6.0-SNAPSHOT
PATH=$PWD/bin:$PATH
java -jar target/onlab-stc-$VER.jar "$@"
#!/bin/bash
#-------------------------------------------------------------------------------
# System Test Coordinator process launcher
#-------------------------------------------------------------------------------
env=$1 && shift
cwd=$1 && shift
if [ $env != "-" ]; then
[ ! -f $env ] && echo "$env file not found" && exit 1
source $env
fi
if [ $cwd != "-" ]; then
[ ! -d $cwd ] && echo "$cwd directory not found" && exit 1
cd $cwd
fi
"$@" 2>&1
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2015-present 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.6.0-SNAPSHOT</version>
</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>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.7.3</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.7.3</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
<version>8.1.19.v20160209</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-servlet</artifactId>
<version>8.1.19.v20160209</version>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-websocket</artifactId>
<version>8.1.19.v20160209</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>2.4.2</version>
<configuration>
<transformers>
<transformer
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>org.onlab.stc.Main
</mainClass>
</transformer>
</transformers>
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
<!--
~ Copyright 2015-present 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-present 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 org.onlab.graph.DepthFirstSearch;
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.*;
import static com.google.common.base.Strings.isNullOrEmpty;
import static java.lang.Integer.parseInt;
import static org.onlab.graph.DepthFirstSearch.EdgeType.BACK_EDGE;
import static org.onlab.graph.GraphPathSearch.ALL_PATHS;
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 = "${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 SEQUENTIAL = "sequential";
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 ENV = "[@env]";
private static final String CWD = "[@cwd]";
private static final String DELAY = "[@delay]";
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 STARTS = "[@starts]";
private static final String ENDS = "[@ends]";
private static final String FILE = "[@file]";
private static final String NAMESPACE = "[@namespace]";
static final String PROP_START = "${";
static final String PROP_END = "}";
private static final String HASH = "#";
private static final String HASH_PREV = "#-1";
private final Scenario scenario;
private final Map<String, Step> steps = Maps.newHashMap();
private final Map<String, Step> inactiveSteps = Maps.newHashMap();
private final Map<String, String> requirements = Maps.newHashMap();
private final Set<Dependency> dependencies = Sets.newHashSet();
private final List<Integer> clonables = Lists.newArrayList();
private ProcessFlow processFlow;
private File logDir;
private String previous = null;
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);
compileRequirements();
// Produce the process flow
processFlow = new ProcessFlow(ImmutableSet.copyOf(steps.values()),
ImmutableSet.copyOf(dependencies));
scanForCycles();
// Extract the log directory if there was one specified
String defaultPath = DEFAULT_LOG_DIR + scenario.name();
String path = scenario.definition().getString(LOG_DIR, defaultPath);
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 + ">";
print("pfx=%s namespace=%s", pfx, namespace);
// 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 sequential groups
cfg.configurationsAt(SEQUENTIAL)
.forEach(c -> processSequentialGroup(c, namespace, parentGroup));
// Scan all dependencies
cfg.configurationsAt(DEPENDENCY)
.forEach(c -> processDependency(c, namespace));
pfx = opfx;
}
/**
* Compiles requirements for all steps and groups accrued during the
* overall compilation process.
*/
private void compileRequirements() {
requirements.forEach((name, requires) ->
compileRequirements(getStep(name), requires));
}
private void compileRequirements(Step src, String requires) {
split(requires).forEach(n -> {
boolean isSoft = n.startsWith("~");
String name = n.replaceFirst("^~", "");
Step dst = getStep(name);
if (dst != null) {
dependencies.add(new Dependency(src, dst, isSoft));
}
});
}
/**
* 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(), newNamespace, 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 command = expand(cfg.getString(COMMAND, parentGroup != null ? parentGroup.command() : null), true);
String env = expand(cfg.getString(ENV, parentGroup != null ? parentGroup.env() : null));
String cwd = expand(cfg.getString(CWD, parentGroup != null ? parentGroup.cwd() : null));
int delay = parseInt(expand(cfg.getString(DELAY, parentGroup != null ? "" + parentGroup.delay() : "0")));
print("step name=%s command=%s env=%s cwd=%s delay=%d", name, command, env, cwd, delay);
Step step = new Step(name, command, env, cwd, parentGroup, delay);
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 command = expand(cfg.getString(COMMAND, parentGroup != null ? parentGroup.command() : null), true);
String env = expand(cfg.getString(ENV, parentGroup != null ? parentGroup.env() : null));
String cwd = expand(cfg.getString(CWD, parentGroup != null ? parentGroup.cwd() : null));
int delay = parseInt(expand(cfg.getString(DELAY, parentGroup != null ? "" + parentGroup.delay() : "0")));
print("group name=%s command=%s env=%s cwd=%s delay=%d", name, command, env, cwd, delay);
Group group = new Group(name, command, env, cwd, parentGroup, delay);
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) {
checkState(!steps.containsKey(step.name()), "Step %s already exists", step.name());
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);
previous = step.name();
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) {
clonables.add(0, i);
compile(cfg, namespace, parentGroup);
clonables.remove(0);
i++;
}
}
/**
* Processes a sequential clone group directive.
*
* @param cfg hierarchical definition
* @param namespace optional namespace
* @param parentGroup optional parent group
*/
private void processSequentialGroup(HierarchicalConfiguration cfg,
String namespace, Group parentGroup) {
String var = cfg.getString(VAR);
String starts = cfg.getString(STARTS);
String ends = cfg.getString(ENDS);
print("sequential var=%s", var);
int i = 1;
while (condition(var, i).length() > 0) {
clonables.add(0, i);
compile(cfg, namespace, parentGroup);
if (i > 1) {
processSequentialRequirements(starts, ends, namespace);
}
clonables.remove(0);
i++;
}
}
/**
* Hooks starts of this sequence tier to the previous tier.
*
* @param starts comma-separated list of start steps
* @param ends comma-separated list of end steps
* @param namespace optional namespace
*/
private void processSequentialRequirements(String starts, String ends,
String namespace) {
for (String s : split(starts)) {
String start = expand(prefix(s, namespace));
String reqs = requirements.get(s);
for (String n : split(ends)) {
boolean isSoft = n.startsWith("~");
String name = n.replaceFirst("^~", "");
name = (isSoft ? "~" : "") + expand(prefix(name, namespace));
reqs = reqs == null ? name : (reqs + "," + name);
}
requirements.put(start, reqs);
}
}
/**
* 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);
if (!inactiveSteps.containsValue(step)) {
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) {
String reqs = requirements.get(src.name());
for (String n : split(requires)) {
boolean isSoft = n.startsWith("~");
String name = n.replaceFirst("^~", "");
name = previous != null && name.equals("^") ? previous : name;
name = (isSoft ? "~" : "") + expand(prefix(name, namespace));
reqs = reqs == null ? name : (reqs + "," + name);
}
requirements.put(src.name(), reqs);
}
/**
* 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 isNullOrEmpty(namespace) ? name : namespace + "." + name;
}
/**
* Expands any environment variables in the specified string. These are
* specified as ${property} tokens.
*
* @param string string to be processed
* @param keepTokens true if the original unresolved tokens should be kept
* @return original string with expanded substitutions
*/
private String expand(String string, boolean... keepTokens) {
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 = Integer.toString(clonables.get(0));
} else if (prop.equals(HASH_PREV)) {
value = Integer.toString(clonables.get(0) - 1);
} else if (prop.endsWith(HASH)) {
pString = pString.replaceFirst("#}", clonables.get(0) + "}");
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);
}
}
if (value == null && keepTokens.length == 1 && keepTokens[0]) {
sb.append("${").append(prop).append("}");
} else {
sb.append(value != null ? value : "");
}
last = end + 1;
}
sb.append(pString.substring(last));
return sb.toString().replace('\n', ' ').replace('\r', ' ');
}
/**
* 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();
}
/**
* Scans the process flow graph for cyclic dependencies.
*/
private void scanForCycles() {
DepthFirstSearch<Step, Dependency> dfs = new DepthFirstSearch<>();
// Use a brute-force method of searching paths from all vertices.
processFlow().getVertexes().forEach(s -> {
DepthFirstSearch<Step, Dependency>.SpanningTreeResult r =
dfs.search(processFlow, s, null, null, ALL_PATHS);
r.edges().forEach((e, et) -> checkArgument(et != BACK_EDGE,
"Process flow has a cycle involving dependency from %s to %s",
e.src().name, e.dst().name));
});
}
/**
* 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-present 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.Maps;
import com.google.common.collect.Sets;
import java.io.File;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static java.util.concurrent.Executors.newFixedThreadPool;
import static org.onlab.stc.Compiler.PROP_END;
import static org.onlab.stc.Compiler.PROP_START;
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 = 64;
private final ExecutorService executor = newFixedThreadPool(MAX_THREADS);
private final ProcessFlow processFlow;
private final StepProcessListener delegate;
private final CountDownLatch latch;
private final ScenarioStore store;
private static final Pattern PROP_ERE = Pattern.compile("^@stc ([a-zA-Z0-9_.]+)=(.*$)");
private final Map<String, String> properties = Maps.newConcurrentMap();
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, SKIPPED
}
/**
* 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.processFlow = processFlow;
this.logDir = logDir;
this.store = new ScenarioStore(processFlow, logDir, scenario.name());
this.delegate = new Delegate();
this.latch = new CountDownLatch(1);
}
/**
* Resets any previously accrued status and events.
*/
public void reset() {
store.reset();
}
/**
* Resets all previously accrued status and events for steps that lie
* in the range between the steps or groups whose names match the specified
* patterns.
*
* @param runFromPatterns list of starting step patterns
* @param runToPatterns list of ending step patterns
*/
public void reset(List<String> runFromPatterns, List<String> runToPatterns) {
List<Step> fromSteps = matchSteps(runFromPatterns);
List<Step> toSteps = matchSteps(runToPatterns);
// FIXME: implement this
}
/**
* Returns number of milliseconds it took to execute.
*
* @return number of millis elapsed during the run
*/
public long duration() {
return store.endTime() - store.startTime();
}
/**
* Returns a list of steps that match the specified list of patterns.
*
* @param runToPatterns list of patterns
* @return list of steps with matching names
*/
private List<Step> matchSteps(List<String> runToPatterns) {
ImmutableList.Builder<Step> builder = ImmutableList.builder();
store.getSteps().forEach(step -> {
runToPatterns.forEach(p -> {
if (step.name().matches(p)) {
builder.add(step);
}
});
});
return builder.build();
}
/**
* Starts execution of the process flow graph.
*/
public void start() {
executeRoots(null);
}
/**
* Waits 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 {
while (!store.isComplete()) {
latch.await(1, TimeUnit.SECONDS);
}
return store.hasFailures() ? 1 : 0;
}
/**
* Returns set of all test steps.
*
* @return set of steps
*/
public Set<Step> getSteps() {
return store.getSteps();
}
/**
* Returns a chronological list of step or group records.
*
* @return list of events
*/
List<StepEvent> getRecords() {
return store.getEvents();
}
/**
* Returns the status record of the specified test step.
*
* @param step test step or group
* @return step status record
*/
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) {
// FIXME: add ability to skip past completed steps
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) {
store.markStarted(step);
if (step instanceof Group) {
Group group = (Group) step;
delegate.onStart(group, null);
executeRoots(group);
} else {
executor.execute(new StepProcessor(step, logDir, delegate,
substitute(step.command())));
}
} else if (directive == SKIP) {
skipStep(step);
}
}
/**
* Recursively skips the specified step or group and any steps/groups within.
*
* @param step step or group
*/
private void skipStep(Step step) {
if (step instanceof Group) {
Group group = (Group) step;
store.markComplete(step, SKIPPED);
group.children().forEach(this::skipStep);
}
delegate.onCompletion(step, SKIPPED);
}
/**
* 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 || depStatus == SKIPPED) && !dependency.isSoft()) ||
(step.group() != null && store.getStatus(step.group()) == SKIPPED)) {
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 || status == SKIPPED);
failed = failed || status == FAILED;
}
if (done) {
delegate.onCompletion(group, failed ? FAILED : SUCCEEDED);
}
}
}
/**
* Expands the var references with values from the properties map.
*
* @param string string to perform substitutions on
*/
private String substitute(String string) {
StringBuilder sb = new StringBuilder();
int start, end, last = 0;
while ((start = string.indexOf(PROP_START, last)) >= 0) {
end = string.indexOf(PROP_END, start + PROP_START.length());
checkArgument(end > start, "Malformed property in %s", string);
sb.append(string.substring(last, start));
String prop = string.substring(start + PROP_START.length(), end);
String value = properties.get(prop);
sb.append(value != null ? value : "");
last = end + 1;
}
sb.append(string.substring(last));
return sb.toString().replace('\n', ' ').replace('\r', ' ');
}
/**
* Scrapes the line of output for any variables to be captured and posted
* in the properties for later use.
*
* @param line line of output to scrape for property exports
*/
private void scrapeForVariables(String line) {
Matcher matcher = PROP_ERE.matcher(line);
if (matcher.matches()) {
String prop = matcher.group(1);
String value = matcher.group(2);
properties.put(prop, value);
}
}
/**
* 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, String command) {
listeners.forEach(listener -> listener.onStart(step, command));
}
@Override
public void onCompletion(Step step, Status status) {
store.markComplete(step, status);
listeners.forEach(listener -> listener.onCompletion(step, status));
executeSucessors(step);
if (store.isComplete()) {
latch.countDown();
}
}
@Override
public void onOutput(Step step, String line) {
scrapeForVariables(line);
listeners.forEach(listener -> listener.onOutput(step, line));
}
}
}
/*
* Copyright 2015-present 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-present 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 default command
* @param env default path to file to be sourced into the environment
* @param cwd default path to current working directory for the step
* @param group optional group to which this step belongs
* @param delay seconds to delay before executing
*/
public Group(String name, String command, String env, String cwd, Group group, int delay) {
super(name, command, env, cwd, group, delay);
}
/**
* 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-present 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.io.Files;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.util.log.Logger;
import org.onlab.stc.Coordinator.Status;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import static java.lang.System.currentTimeMillis;
import static org.onlab.stc.Coordinator.Status.*;
import static org.onlab.stc.Coordinator.print;
/**
* Main program for executing system test coordinator.
*/
public final class Main {
private static final String NONE = "\u001B[0m";
private static final String GRAY = "\u001B[30;1m";
private static final String RED = "\u001B[31;1m";
private static final String GREEN = "\u001B[32;1m";
private static final String BLUE = "\u001B[36m";
private static final String SUCCESS_SUMMARY =
"%s %sPassed! %d steps succeeded%s";
private static final String MIXED_SUMMARY =
"%s%d steps succeeded; %s%d steps failed; %s%d steps skipped%s";
private static final String FAILURE_SUMMARY = "%s %sFailed! " + MIXED_SUMMARY;
private static final String ABORTED_SUMMARY = "%s %sAborted! " + MIXED_SUMMARY;
private boolean isReported = false;
private enum Command {
LIST, RUN, RUN_RANGE, HELP
}
private final String scenarioFile;
private Command command = Command.HELP;
private String runFromPatterns = "";
private String runToPatterns = "";
private Coordinator coordinator;
private Compiler compiler;
private Monitor monitor;
private Listener delegate = new Listener();
private static boolean useColor = Objects.equals("true", System.getenv("stcColor"));
private static boolean dumpLogs = Objects.equals("true", System.getenv("stcDumpLogs"));
// usage: stc [<scenario-file>] [run]
// usage: stc [<scenario-file>] run [from <from-patterns>] [to <to-patterns>]]
// usage: stc [<scenario-file>] list
// Public construction forbidden
private Main(String[] args) {
this.scenarioFile = args[0];
if (args.length <= 1 || args.length == 2 && args[1].equals("run")) {
command = Command.RUN;
} else if (args.length == 2 && args[1].equals("list")) {
command = Command.LIST;
} else if (args.length >= 4 && args[1].equals("run")) {
int i = 2;
if (args[i].equals("from")) {
command = Command.RUN_RANGE;
runFromPatterns = args[i + 1];
i += 2;
}
if (args.length >= i + 2 && args[i].equals("to")) {
command = Command.RUN_RANGE;
runToPatterns = args[i + 1];
}
}
}
/**
* 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();
}
// Runs the scenario processing
private void run() {
try {
// Load scenario
Scenario scenario = Scenario.loadScenario(new FileInputStream(scenarioFile));
// Elaborate scenario
compiler = new Compiler(scenario);
compiler.compile();
// Setup the process flow coordinator
coordinator = new Coordinator(scenario, compiler.processFlow(),
compiler.logDir());
coordinator.addListener(delegate);
// Prepare the GUI monitor
monitor = new Monitor(coordinator, compiler);
startMonitorServer(monitor);
// Execute process flow
processCommand();
} catch (FileNotFoundException e) {
print("Unable to find scenario file %s", scenarioFile);
}
}
// Initiates a web-server for the monitor GUI.
private static void startMonitorServer(Monitor monitor) {
org.eclipse.jetty.util.log.Log.setLog(new NullLogger());
Server server = new Server(9999);
ServletHandler handler = new ServletHandler();
server.setHandler(handler);
MonitorWebSocketServlet.setMonitor(monitor);
handler.addServletWithMapping(MonitorWebSocketServlet.class, "/*");
try {
server.start();
} catch (Exception e) {
print("GUI already active; running without...");
}
}
// Processes the appropriate command
private void processCommand() {
switch (command) {
case RUN:
processRun();
break;
case LIST:
processList();
break;
case RUN_RANGE:
processRunRange();
break;
default:
print("Unsupported command %s", command);
}
}
// Processes the scenario 'run' command.
private void processRun() {
coordinator.reset();
runCoordinator();
}
// Processes the scenario 'run' command for range of steps.
private void processRunRange() {
coordinator.reset(list(runFromPatterns), list(runToPatterns));
runCoordinator();
}
// Processes the scenario 'list' command.
private void processList() {
coordinator.getRecords()
.forEach(event -> logStatus(event.time(), event.name(), event.status(), event.command()));
printSummary(0, false);
System.exit(0);
}
// Runs the coordinator and waits for it to finish.
private void runCoordinator() {
try {
Runtime.getRuntime().addShutdownHook(new ShutdownHook());
coordinator.start();
int exitCode = coordinator.waitFor();
pause(100); // allow stdout to flush
printSummary(exitCode, false);
System.exit(exitCode);
} catch (InterruptedException e) {
print("Unable to execute scenario %s", scenarioFile);
}
}
private synchronized void printSummary(int exitCode, boolean isAborted) {
if (!isReported) {
isReported = true;
Set<Step> steps = coordinator.getSteps();
String duration = formatDuration((int) (coordinator.duration() / 1_000));
int count = steps.size();
if (exitCode == 0) {
print(SUCCESS_SUMMARY, duration, color(SUCCEEDED), count, color(null));
} else {
long success = steps.stream().filter(s -> coordinator.getStatus(s) == SUCCEEDED).count();
long failed = steps.stream().filter(s -> coordinator.getStatus(s) == FAILED).count();
long skipped = steps.stream().filter(s -> coordinator.getStatus(s) == SKIPPED).count();
print(isAborted ? ABORTED_SUMMARY : FAILURE_SUMMARY, duration,
color(FAILED), color(SUCCEEDED), success,
color(FAILED), failed, color(SKIPPED), skipped, color(null));
}
}
}
/**
* Internal delegate to monitor the process execution.
*/
private class Listener implements StepProcessListener {
@Override
public void onStart(Step step, String command) {
logStatus(currentTimeMillis(), step.name(), IN_PROGRESS, command);
}
@Override
public void onCompletion(Step step, Status status) {
logStatus(currentTimeMillis(), step.name(), status, null);
if (dumpLogs && !(step instanceof Group) && status == FAILED) {
dumpLogs(step);
}
}
@Override
public void onOutput(Step step, String line) {
}
}
// Logs the step status.
private static void logStatus(long time, String name, Status status, String cmd) {
if (cmd != null) {
print("%s %s%s %s%s -- %s", time(time), color(status), name, action(status), color(null), cmd);
} else {
print("%s %s%s %s%s", time(time), color(status), name, action(status), color(null));
}
}
// Dumps the step logs to standard output.
private void dumpLogs(Step step) {
File logFile = new File(compiler.logDir(), step.name() + ".log");
try {
print(">>>>>");
Files.copy(logFile, System.out);
print("<<<<<");
} catch (IOException e) {
print("Unable to dump log file %s", logFile.getName());
}
}
// Produces a description of event using the specified step status.
private static String action(Status status) {
return status == IN_PROGRESS ? "started" :
(status == SUCCEEDED ? "completed" :
(status == FAILED ? "failed" :
(status == SKIPPED ? "skipped" : "waiting")));
}
// Produces an ANSI escape code for color using the specified step status.
private static String color(Status status) {
if (!useColor) {
return "";
}
return status == null ? NONE :
(status == IN_PROGRESS ? BLUE :
(status == SUCCEEDED ? GREEN :
(status == FAILED ? RED : GRAY)));
}
// Produces a list from the specified comma-separated string.
private static List<String> list(String patterns) {
return ImmutableList.copyOf(patterns.split(","));
}
// Produces a formatted time stamp.
private static String time(long time) {
return new SimpleDateFormat("YYYY-MM-dd HH:mm:ss").format(new Date(time));
}
// Pauses for the specified number of millis.
private static void pause(int ms) {
try {
Thread.sleep(ms);
} catch (InterruptedException e) {
print("Interrupted!");
}
}
// Formats time duration
private static String formatDuration(int totalSeconds) {
int seconds = totalSeconds % 60;
int totalMinutes = totalSeconds / 60;
int minutes = totalMinutes % 60;
int hours = totalMinutes / 60;
return hours > 0 ?
String.format("%d:%02d:%02d", hours, minutes, seconds) :
String.format("%d:%02d", minutes, seconds);
}
// Shutdown hook to report status even when aborted.
private class ShutdownHook extends Thread {
@Override
public void run() {
printSummary(1, true);
}
}
// Logger to quiet Jetty down
private static class NullLogger implements Logger {
@Override
public String getName() {
return "quiet";
}
@Override
public void warn(String msg, Object... args) {
}
@Override
public void warn(Throwable thrown) {
}
@Override
public void warn(String msg, Throwable thrown) {
}
@Override
public void info(String msg, Object... args) {
}
@Override
public void info(Throwable thrown) {
}
@Override
public void info(String msg, Throwable thrown) {
}
@Override
public boolean isDebugEnabled() {
return false;
}
@Override
public void setDebugEnabled(boolean enabled) {
}
@Override
public void debug(String msg, Object... args) {
}
@Override
public void debug(Throwable thrown) {
}
@Override
public void debug(String msg, Throwable thrown) {
}
@Override
public Logger getLogger(String name) {
return this;
}
@Override
public void ignore(Throwable ignored) {
}
}
}
/*
* Copyright 2015-present 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.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.collect.Maps;
import org.onlab.stc.MonitorLayout.Box;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Map;
import static org.onlab.stc.Coordinator.Status.IN_PROGRESS;
/**
* Scenario test monitor.
*/
public class Monitor implements StepProcessListener {
private final ObjectMapper mapper = new ObjectMapper();
private final Coordinator coordinator;
private final Compiler compiler;
private final MonitorLayout layout;
private MonitorDelegate delegate;
private Map<Step, Box> boxes = Maps.newHashMap();
/**
* Creates a new shared process flow monitor.
*
* @param coordinator process flow coordinator
* @param compiler scenario compiler
*/
Monitor(Coordinator coordinator, Compiler compiler) {
this.coordinator = coordinator;
this.compiler = compiler;
this.layout = new MonitorLayout(compiler);
coordinator.addListener(this);
}
/**
* Sets the process monitor delegate.
*
* @param delegate process monitor delegate
*/
void setDelegate(MonitorDelegate delegate) {
this.delegate = delegate;
}
/**
* Notifies the process monitor delegate with the specified event.
*
* @param event JSON event data
*/
public void notify(ObjectNode event) {
if (delegate != null) {
delegate.notify(event);
}
}
/**
* Returns the scenario process flow as JSON data.
*
* @return scenario process flow data
*/
ObjectNode scenarioData() {
ObjectNode root = mapper.createObjectNode();
ArrayNode steps = mapper.createArrayNode();
ArrayNode requirements = mapper.createArrayNode();
ProcessFlow pf = compiler.processFlow();
pf.getVertexes().forEach(step -> add(step, steps));
pf.getEdges().forEach(requirement -> add(requirement, requirements));
root.set("steps", steps);
root.set("requirements", requirements);
try (FileWriter fw = new FileWriter("/tmp/data.json");
PrintWriter pw = new PrintWriter(fw)) {
pw.println(root.toString());
} catch (IOException e) {
e.printStackTrace();
}
return root;
}
private void add(Step step, ArrayNode steps) {
Box box = layout.get(step);
ObjectNode sn = mapper.createObjectNode()
.put("name", step.name())
.put("isGroup", step instanceof Group)
.put("status", status(coordinator.getStatus(step)))
.put("tier", box.tier())
.put("depth", box.depth());
if (step.group() != null) {
sn.put("group", step.group().name());
}
steps.add(sn);
}
private String status(Coordinator.Status status) {
return status.toString().toLowerCase();
}
private void add(Dependency requirement, ArrayNode requirements) {
ObjectNode rn = mapper.createObjectNode();
rn.put("src", requirement.src().name())
.put("dst", requirement.dst().name())
.put("isSoft", requirement.isSoft());
requirements.add(rn);
}
@Override
public void onStart(Step step, String command) {
notify(event(step, status(IN_PROGRESS)));
}
@Override
public void onCompletion(Step step, Coordinator.Status status) {
notify(event(step, status(status)));
}
@Override
public void onOutput(Step step, String line) {
}
private ObjectNode event(Step step, String status) {
ObjectNode event = mapper.createObjectNode()
.put("name", step.name())
.put("status", status);
return event;
}
}
/*
* Copyright 2015-present 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.fasterxml.jackson.databind.node.ObjectNode;
/**
* Delegate to which monitor can send notifications.
*/
public interface MonitorDelegate {
/**
* Issues JSON event to be sent to any connected monitor clients.
*
* @param event JSON event data
*/
void notify(ObjectNode event);
}
/*
* Copyright 2015-present 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.Lists;
import com.google.common.collect.Maps;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
/**
* Computes scenario process flow layout for the Monitor GUI.
*/
public class MonitorLayout {
public static final int WIDTH = 210;
public static final int HEIGHT = 30;
public static final int W_GAP = 40;
public static final int H_GAP = 50;
public static final int SLOT_WIDTH = WIDTH + H_GAP;
private final Compiler compiler;
private final ProcessFlow flow;
private Map<Step, Box> boxes = Maps.newHashMap();
/**
* Creates a new shared process flow monitor.
*
* @param compiler scenario compiler
*/
MonitorLayout(Compiler compiler) {
this.compiler = compiler;
this.flow = compiler.processFlow();
// Extract the flow and create initial bounding boxes.
boxes.put(null, new Box(null, 0));
flow.getVertexes().forEach(this::createBox);
computeLayout(null, 0, 1);
}
// Computes the graph layout giving preference to group associations.
private void computeLayout(Group group, int absoluteTier, int tier) {
Box box = boxes.get(group);
// Find all children of the group, or items with no group if at top.
Set<Step> children = group != null ? group.children() :
flow.getVertexes().stream().filter(s -> s.group() == null)
.collect(Collectors.toSet());
children.forEach(s -> visit(s, absoluteTier, 1, group));
// Figure out what the group root vertexes are.
Set<Step> roots = findRoots(group);
// Compute the boxes for each of the roots.
roots.forEach(s -> updateBox(s, absoluteTier + 1, 1, group));
// Update the tier and depth of the group bounding box.
computeTiersAndDepth(group, box, absoluteTier, tier, children);
// Compute the minimum breadth of this group's bounding box.
computeBreadth(group, box, children);
// Compute child placements
computeChildPlacements(group, box, children);
}
// Updates the box for the specified step, given the tier number, which
// is relative to the parent.
private Box updateBox(Step step, int absoluteTier, int tier, Group group) {
Box box = boxes.get(step);
if (step instanceof Group) {
computeLayout((Group) step, absoluteTier, tier);
} else {
box.setTierAndDepth(absoluteTier, tier, 1, group);
}
// Follow the steps downstream of this one.
follow(step, absoluteTier + box.depth(), box.tier() + box.depth());
return box;
}
// Backwards follows edges leading towards the specified step to visit
// the source vertex and compute layout of those vertices that had
// sufficient number of visits to compute their tier.
private void follow(Step step, int absoluteTier, int tier) {
Group from = step.group();
flow.getEdgesTo(step).stream()
.filter(d -> visit(d.src(), absoluteTier, tier, from))
.forEach(d -> updateBox(d.src(), absoluteTier, tier, from));
}
// Visits each step, records maximum tier and returns true if this
// was the last expected visit.
private boolean visit(Step step, int absoluteTier, int tier, Group from) {
Box box = boxes.get(step);
return box.visitAndLatchMaxTier(absoluteTier, tier, from);
}
// Computes the absolute and relative tiers and the depth of the group
// bounding box.
private void computeTiersAndDepth(Group group, Box box,
int absoluteTier, int tier, Set<Step> children) {
int depth = children.stream().mapToInt(this::bottomMostTier).max().getAsInt();
box.setTierAndDepth(absoluteTier, tier, depth, group);
}
// Returns the bottom-most tier this step occupies relative to its parent.
private int bottomMostTier(Step step) {
Box box = boxes.get(step);
return box.tier() + box.depth();
}
// Computes breadth of the specified group.
private void computeBreadth(Group group, Box box, Set<Step> children) {
if (box.breadth() == 0) {
// Scan through all tiers and determine the maximum breadth of each.
IntStream.range(1, box.depth)
.forEach(t -> computeTierBreadth(t, box, children));
box.latchBreadth(children.stream()
.mapToInt(s -> boxes.get(s).breadth())
.max().getAsInt());
}
}
// Computes tier width.
private void computeTierBreadth(int t, Box box, Set<Step> children) {
box.latchBreadth(children.stream().map(boxes::get)
.filter(b -> isSpanningTier(b, t))
.mapToInt(Box::breadth).sum());
}
// Computes the actual child box placements relative to the parent using
// the previously established tier, depth and breadth attributes.
private void computeChildPlacements(Group group, Box box,
Set<Step> children) {
// Order the root-nodes in alphanumeric order first.
List<Box> tierBoxes = Lists.newArrayList(boxesOnTier(1, children));
tierBoxes.sort((a, b) -> a.step().name().compareTo(b.step().name()));
// Place the boxes centered on the parent box; left to right.
int tierBreadth = tierBoxes.stream().mapToInt(Box::breadth).sum();
int slot = 1;
for (Box b : tierBoxes) {
b.updateCenter(1, slot(slot, tierBreadth));
slot += b.breadth();
}
}
// Returns the horizontal offset off the parent center.
private int slot(int slot, int tierBreadth) {
boolean even = tierBreadth % 2 == 0;
int multiplier = -tierBreadth / 2 + slot - 1;
return even ? multiplier * SLOT_WIDTH + SLOT_WIDTH / 2 : multiplier * SLOT_WIDTH;
}
// Returns a list of all child step boxes that start on the specified tier.
private List<Box> boxesOnTier(int tier, Set<Step> children) {
return boxes.values().stream()
.filter(b -> b.tier() == tier && children.contains(b.step()))
.collect(Collectors.toList());
}
// Determines whether the specified box spans, or occupies a tier.
private boolean isSpanningTier(Box b, int tier) {
return (b.depth() == 1 && b.tier() == tier) ||
(b.tier() <= tier && tier < b.tier() + b.depth());
}
// Determines roots of the specified group or of the entire graph.
private Set<Step> findRoots(Group group) {
Set<Step> steps = group != null ? group.children() : flow.getVertexes();
return steps.stream().filter(s -> isRoot(s, group)).collect(Collectors.toSet());
}
private boolean isRoot(Step step, Group group) {
if (step.group() != group) {
return false;
}
Set<Dependency> requirements = flow.getEdgesFrom(step);
return requirements.stream().filter(r -> r.dst().group() == group)
.collect(Collectors.toSet()).isEmpty();
}
/**
* Returns the bounding box for the specified step. If null is given, it
* returns the overall bounding box.
*
* @param step step or group; null for the overall bounding box
* @return bounding box
*/
public Box get(Step step) {
return boxes.get(step);
}
/**
* Returns the bounding box for the specified step name. If null is given,
* it returns the overall bounding box.
*
* @param name name of step or group; null for the overall bounding box
* @return bounding box
*/
public Box get(String name) {
return get(name == null ? null : compiler.getStep(name));
}
// Creates a bounding box for the specified step or group.
private void createBox(Step step) {
boxes.put(step, new Box(step, flow.getEdgesFrom(step).size()));
}
/**
* Bounding box data for a step or group.
*/
final class Box {
private Step step;
private int remainingRequirements;
private int absoluteTier = 0;
private int tier;
private int depth = 1;
private int breadth;
private int center, top;
private Box(Step step, int remainingRequirements) {
this.step = step;
this.remainingRequirements = remainingRequirements + 1;
breadth = step == null || step instanceof Group ? 0 : 1;
}
private void latchTiers(int absoluteTier, int tier, Group from) {
this.absoluteTier = Math.max(this.absoluteTier, absoluteTier);
if (step == null || step.group() == from) {
this.tier = Math.max(this.tier, tier);
}
}
public void latchBreadth(int breadth) {
this.breadth = Math.max(this.breadth, breadth);
}
void setTierAndDepth(int absoluteTier, int tier, int depth, Group from) {
latchTiers(absoluteTier, tier, from);
this.depth = depth;
}
boolean visitAndLatchMaxTier(int absoluteTier, int tier, Group from) {
latchTiers(absoluteTier, tier, from);
--remainingRequirements;
return remainingRequirements == 0;
}
Step step() {
return step;
}
public int absoluteTier() {
return absoluteTier;
}
int tier() {
return tier;
}
int depth() {
return depth;
}
int breadth() {
return breadth;
}
int top() {
return top;
}
int center() {
return center;
}
public void updateCenter(int top, int center) {
this.top = top;
this.center = center;
}
}
}
/*
* Copyright 2015-present 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.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.eclipse.jetty.websocket.WebSocket;
import java.io.IOException;
import static org.onlab.stc.Coordinator.print;
/**
* Web socket capable of interacting with the STC monitor GUI.
*/
public class MonitorWebSocket implements WebSocket.OnTextMessage, WebSocket.OnControl {
private static final long MAX_AGE_MS = 30_000;
private static final byte PING = 0x9;
private static final byte PONG = 0xA;
private static final byte[] PING_DATA = new byte[]{(byte) 0xde, (byte) 0xad};
private final Monitor monitor;
private Connection connection;
private FrameConnection control;
private final ObjectMapper mapper = new ObjectMapper();
private long lastActive = System.currentTimeMillis();
/**
* Creates a new monitor client GUI web-socket.
*
* @param monitor shared process flow monitor
*/
MonitorWebSocket(Monitor monitor) {
this.monitor = monitor;
}
/**
* Issues a close on the connection.
*/
synchronized void close() {
destroyHandlers();
if (connection.isOpen()) {
connection.close();
}
}
/**
* Indicates if this connection is idle.
*
* @return true if idle or closed
*/
synchronized boolean isIdle() {
long quietFor = System.currentTimeMillis() - lastActive;
boolean idle = quietFor > MAX_AGE_MS;
if (idle || (connection != null && !connection.isOpen())) {
return true;
} else if (connection != null) {
try {
control.sendControl(PING, PING_DATA, 0, PING_DATA.length);
} catch (IOException e) {
print("Unable to send ping message due to: %s", e);
}
}
return false;
}
@Override
public void onOpen(Connection connection) {
this.connection = connection;
this.control = (FrameConnection) connection;
try {
createHandlers();
sendMessage(message("flow", monitor.scenarioData()));
} catch (Exception e) {
print("Unable to open monitor connection: %s", e);
this.connection.close();
this.connection = null;
this.control = null;
}
}
@Override
public synchronized void onClose(int closeCode, String message) {
destroyHandlers();
}
@Override
public boolean onControl(byte controlCode, byte[] data, int offset, int length) {
lastActive = System.currentTimeMillis();
return true;
}
@Override
public void onMessage(String data) {
lastActive = System.currentTimeMillis();
try {
ObjectNode message = (ObjectNode) mapper.reader().readTree(data);
// TODO:
print("Got message: %s", message);
} catch (Exception e) {
print("Unable to parse GUI message %s due to %s", data, e);
}
}
public synchronized void sendMessage(ObjectNode message) {
try {
if (connection.isOpen()) {
connection.sendMessage(message.toString());
}
} catch (IOException e) {
print("Unable to send message %s to GUI due to %s", message, e);
}
}
public ObjectNode message(String type, ObjectNode payload) {
ObjectNode message = mapper.createObjectNode().put("event", type);
message.set("payload", payload);
return message;
}
// Creates new message handlers.
private synchronized void createHandlers() {
}
// Destroys message handlers.
private synchronized void destroyHandlers() {
}
}
/*
* Copyright 2015-present 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.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.io.ByteStreams;
import com.google.common.net.MediaType;
import org.eclipse.jetty.websocket.WebSocket;
import org.eclipse.jetty.websocket.WebSocketServlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
/**
* Web socket servlet capable of creating web sockets for the STC monitor.
*/
public class MonitorWebSocketServlet extends WebSocketServlet
implements MonitorDelegate {
private static final long PING_DELAY_MS = 5000;
private static final String DOT = ".";
private static Monitor monitor;
private static MonitorWebSocketServlet instance;
private final Set<MonitorWebSocket> sockets = new HashSet<>();
private final Timer timer = new Timer();
private final TimerTask pruner = new Pruner();
/**
* Binds the shared process flow monitor.
*
* @param m process monitor reference
*/
public static void setMonitor(Monitor m) {
monitor = m;
}
/**
* Closes all currently open monitor web-sockets.
*/
public static void closeAll() {
if (instance != null) {
instance.sockets.forEach(MonitorWebSocket::close);
instance.sockets.clear();
}
}
@Override
public void init() throws ServletException {
super.init();
instance = this;
monitor.setDelegate(this);
timer.schedule(pruner, PING_DELAY_MS, PING_DELAY_MS);
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String uri = req.getRequestURI();
uri = uri.length() <= 1 ? "/index.html" : uri;
InputStream resource = getClass().getResourceAsStream(uri);
if (resource == null) {
resp.setStatus(HttpServletResponse.SC_NOT_FOUND);
} else {
byte[] entity = ByteStreams.toByteArray(resource);
resp.setStatus(HttpServletResponse.SC_OK);
resp.setContentType(contentType(uri).toString());
resp.setContentLength(entity.length);
resp.getOutputStream().write(entity);
}
}
private MediaType contentType(String uri) {
int sep = uri.lastIndexOf(DOT);
String ext = sep > 0 ? uri.substring(sep + 1) : null;
return ext == null ? MediaType.APPLICATION_BINARY :
ext.equals("html") ? MediaType.HTML_UTF_8 :
ext.equals("js") ? MediaType.JAVASCRIPT_UTF_8 :
ext.equals("css") ? MediaType.CSS_UTF_8 :
MediaType.APPLICATION_BINARY;
}
@Override
public WebSocket doWebSocketConnect(HttpServletRequest request, String protocol) {
MonitorWebSocket socket = new MonitorWebSocket(monitor);
synchronized (sockets) {
sockets.add(socket);
}
return socket;
}
@Override
public void notify(ObjectNode event) {
if (instance != null) {
instance.sockets.forEach(ws -> ws.sendMessage(event));
}
}
// Task for pruning web-sockets that are idle.
private class Pruner extends TimerTask {
@Override
public void run() {
synchronized (sockets) {
Iterator<MonitorWebSocket> it = sockets.iterator();
while (it.hasNext()) {
MonitorWebSocket socket = it.next();
if (socket.isIdle()) {
it.remove();
socket.close();
}
}
}
}
}
}
/*
* Copyright 2015-present 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-present 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-present 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.Lists;
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.List;
import java.util.Map;
import java.util.Set;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.onlab.stc.Coordinator.Status.*;
import static org.onlab.stc.Coordinator.print;
/**
* Maintains state of scenario execution.
*/
class ScenarioStore {
private final ProcessFlow processFlow;
private final File storeFile;
private final File logDir;
private final List<StepEvent> events = Lists.newArrayList();
private final Map<String, Status> statusMap = Maps.newConcurrentMap();
private long startTime = Long.MAX_VALUE;
private long endTime = Long.MIN_VALUE;
/**
* 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.logDir = logDir;
this.storeFile = new File(logDir, name + ".stc");
load();
}
/**
* Resets status of all steps to waiting and clears all events.
*/
void reset() {
events.clear();
statusMap.clear();
processFlow.getVertexes().forEach(step -> statusMap.put(step.name(), WAITING));
try {
removeLogs();
PropertiesConfiguration cfg = new PropertiesConfiguration(storeFile);
cfg.clear();
cfg.save();
startTime = Long.MAX_VALUE;
endTime = Long.MIN_VALUE;
} catch (ConfigurationException e) {
print("Unable to store file %s", storeFile);
}
}
/**
* Returns set of all test steps.
*
* @return set of steps
*/
Set<Step> getSteps() {
return processFlow.getVertexes();
}
/**
* Returns a chronological list of step or group records.
*
* @return list of events
*/
synchronized List<StepEvent> getEvents() {
return ImmutableList.copyOf(events);
}
/**
* Returns the status record of the specified test step.
*
* @param step test step or group
* @return step status record
*/
Status getStatus(Step step) {
return checkNotNull(statusMap.get(step.name()), "Step %s not found", step.name());
}
/**
* Marks the specified test step as being in progress.
*
* @param step test step or group
*/
synchronized void markStarted(Step step) {
add(new StepEvent(step.name(), IN_PROGRESS, step.command()));
save();
}
/**
* Marks the specified test step as being complete.
*
* @param step test step or group
* @param status new step status
*/
synchronized void markComplete(Step step, Status status) {
add(new StepEvent(step.name(), status, null));
save();
}
/**
* Returns true if all steps in the store have been marked as completed
* regardless of the completion status.
*
* @return true if all steps completed one way or another
*/
synchronized boolean isComplete() {
return !statusMap.values().stream().anyMatch(s -> s == WAITING || s == IN_PROGRESS);
}
/**
* Indicates whether there are any failures.
*
* @return true if there are failed steps
*/
boolean hasFailures() {
for (Status status : statusMap.values()) {
if (status == FAILED) {
return true;
}
}
return false;
}
/**
* Registers a new step record.
*
* @param event step event
*/
private synchronized void add(StepEvent event) {
events.add(event);
statusMap.put(event.name(), event.status());
startTime = Math.min(startTime, event.time());
endTime = Math.max(endTime, event.time());
}
/**
* Loads the states from disk.
*/
private void load() {
try {
PropertiesConfiguration cfg = new PropertiesConfiguration(storeFile);
cfg.getKeys().forEachRemaining(prop -> add(StepEvent.fromString(cfg.getString(prop))));
cfg.save();
} catch (ConfigurationException e) {
print("Unable to load file %s", storeFile);
}
}
/**
* Saves the states to disk.
*/
private void save() {
try {
PropertiesConfiguration cfg = new PropertiesConfiguration(storeFile);
events.forEach(event -> cfg.setProperty("T" + event.time(), event.toString()));
cfg.save();
} catch (ConfigurationException e) {
print("Unable to store file %s", storeFile);
}
}
/**
* Removes all scenario log files.
*/
private void removeLogs() {
File[] logFiles = logDir.listFiles();
if (logFiles != null && logFiles.length > 0) {
for (File file : logFiles) {
if (!file.delete()) {
print("Unable to delete log file %s", file);
}
}
}
}
/**
* Returns the scenario run start time.
*
* @return start time in mills since start of epoch
*/
public long startTime() {
return startTime;
}
/**
* Returns the scenario run end time or current time if the scenario
* is still running.
*
* @return end time (or current time) in mills since start of epoch
*/
public long endTime() {
return endTime > 0 ? endTime : System.currentTimeMillis();
}
}
/*
* Copyright 2015-present 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 String env;
protected final String cwd;
protected final Group group;
protected final int delay;
/**
* Creates a new test step.
*
* @param name step name
* @param command step command to execute
* @param env path to file to be sourced into the environment
* @param cwd path to current working directory for the step
* @param group optional group to which this step belongs
* @param delay seconds to delay before executing
*/
public Step(String name, String command, String env, String cwd, Group group, int delay) {
this.name = checkNotNull(name, "Name cannot be null");
this.group = group;
this.delay = delay;
// Set the command, environment and cwd
// If one is not given use the value from the enclosing group
this.command = command != null ? command : group != null && group.command != null ? group.command : null;
this.env = env != null ? env : group != null && group.env != null ? group.env : null;
this.cwd = cwd != null ? cwd : group != null && group.cwd != null ? group.cwd : 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 step environment script path.
*
* @return env script path
*/
public String env() {
return env;
}
/**
* Returns the step current working directory path.
*
* @return current working dir path
*/
public String cwd() {
return cwd;
}
/**
* Returns the enclosing group; null if none.
*
* @return enclosing group or null
*/
public Group group() {
return group;
}
/**
* Returns the start delay in seconds.
*
* @return number of seconds
*/
public int delay() {
return delay;
}
@Override
public int hashCode() {
return name.hashCode();
}
@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("env", env)
.add("cwd", cwd)
.add("group", group)
.add("delay", delay)
.toString();
}
}
/*
* Copyright 2015-present 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.stc.Coordinator.Status;
import static java.lang.Long.parseLong;
import static org.onlab.stc.Coordinator.Status.valueOf;
/**
* Represents an event of execution of a scenario step or group.
*/
public class StepEvent {
private static final String SEP = "~";
private final String name;
private final long time;
private final Status status;
private final String command;
/**
* Creates a new step record.
*
* @param name test step or group name
* @param time time in millis since start of epoch
* @param status step completion status
* @param command step command
*/
public StepEvent(String name, long time, Status status, String command) {
this.name = name;
this.time = time;
this.status = status;
this.command = command;
}
/**
* Creates a new step record for non-running status.
*
* @param name test step or group name
* @param status status
* @param command step command
*/
public StepEvent(String name, Status status, String command) {
this(name, System.currentTimeMillis(), status, command);
}
/**
* Returns the test step or test group name.
*
* @return step or group name
*/
public String name() {
return name;
}
/**
* Returns the step event time.
*
* @return time in millis since start of epoch
*/
public long time() {
return time;
}
/**
* Returns the step completion status.
*
* @return completion status
*/
public Status status() {
return status;
}
/**
* Returns the step command.
*
* @return step command
*/
public String command() {
return command;
}
@Override
public String toString() {
return name + SEP + time + SEP + status + SEP + command;
}
/**
* Returns a record parsed from the specified string.
*
* @param string string encoding
* @return step record
*/
public static StepEvent fromString(String string) {
String[] fields = string.split("~");
return fields.length == 4 ?
new StepEvent(fields[0], parseLong(fields[1]), valueOf(fields[2]),
fields[3].equals("null") ? null : fields[3]) :
new StepEvent(fields[0], 0, Status.WAITING, null);
}
}
/*
* Copyright 2015-present 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
* @param command actual command executed; includes run-time substitutions
*/
default void onStart(Step step, String command) {
}
/**
* Indicates that process step has completed.
*
* @param step subject step
* @param status step completion status
*/
default void onCompletion(Step step, Coordinator.Status status) {
}
/**
* 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-present 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.stc.Coordinator.Status;
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 java.lang.String.format;
import static org.onlab.stc.Coordinator.Status.FAILED;
import static org.onlab.stc.Coordinator.Status.SUCCEEDED;
import static org.onlab.stc.Coordinator.print;
/**
* Manages execution of the specified step or a group.
*/
class StepProcessor implements Runnable {
private static final String IGNORE_CODE = "~";
private static final String NEGATE_CODE = "!";
private static final int FAIL = -1;
private static final int SECONDS = 1_000;
static String launcher = "stc-launcher ";
private final Step step;
private final File logDir;
private String command;
private Process process;
private StepProcessListener delegate;
/**
* Creates a process monitor.
*
* @param step step or group to be executed
* @param logDir directory where step process log should be stored
* @param delegate process lifecycle listener
* @param command actual command to execute
*/
StepProcessor(Step step, File logDir, StepProcessListener delegate,
String command) {
this.step = step;
this.logDir = logDir;
this.delegate = delegate;
this.command = command;
}
@Override
public void run() {
delegate.onStart(step, command);
delayIfNeeded();
int code = execute();
boolean ignoreCode = step.env() != null && step.env.equals(IGNORE_CODE);
boolean negateCode = step.env() != null && step.env.equals(NEGATE_CODE);
Status status = ignoreCode || code == 0 && !negateCode || code != 0 && negateCode ?
SUCCEEDED : FAILED;
delegate.onCompletion(step, status);
}
/**
* Pauses if the step requires it.
*/
private void delayIfNeeded() {
if (step.delay() > 0) {
try {
Thread.sleep(step.delay() * SECONDS);
} catch (InterruptedException e) {
throw new RuntimeException("Interrupted", e);
}
}
}
/**
* Executes the step process.
*
* @return exit code
*/
private int execute() {
try (PrintWriter pw = new PrintWriter(logFile())) {
process = Runtime.getRuntime().exec(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;
}
/**
* Returns ready-to-run command for the step.
*
* @return command to execute
*/
private String command() {
return format("%s %s %s %s", launcher,
step.env() != null ? step.env() : "-",
step.cwd() != null ? step.cwd() : "-",
command);
}
/**
* 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 step output.
*
* @return log file
*/
private File logFile() {
return new File(logDir, step.name() + ".log");
}
}
/*
* Copyright 2015-present 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
{
"requirements": [
{
"dst": "Reactive-Forwarding.Ping-2",
"isSoft": false,
"src": "Reactive-Forwarding.Link-2-Down"
},
{
"dst": "Final-Check-Logs-2",
"isSoft": true,
"src": "Fetch-Logs-2"
},
{
"dst": "Host-Intent.Ping-4",
"isSoft": false,
"src": "Host-Intent.Link-2-Up"
},
{
"dst": "Install-1",
"isSoft": false,
"src": "Wait-for-Start-1"
},
{
"dst": "Host-Intent.Link-1-Down",
"isSoft": false,
"src": "Host-Intent.Ping-2"
},
{
"dst": "Host-Intent.Link-2-Up",
"isSoft": false,
"src": "Host-Intent.Ping-5"
},
{
"dst": "Host-Intent.Ping-2",
"isSoft": false,
"src": "Host-Intent.Link-2-Down"
},
{
"dst": "Reinstall-App-With-CLI",
"isSoft": false,
"src": "Verify-CLI"
},
{
"dst": "Create-App-UI-Overlay",
"isSoft": false,
"src": "Build-App-With-UI"
},
{
"dst": "Secure-SSH",
"isSoft": true,
"src": "Wait-for-Start-1"
},
{
"dst": "Pause-For-Masters",
"isSoft": true,
"src": "Check-Flows"
},
{
"dst": "Secure-SSH",
"isSoft": true,
"src": "Wait-for-Start-3"
},
{
"dst": "Uninstall-3",
"isSoft": false,
"src": "Kill-3"
},
{
"dst": "Balance-Masters",
"isSoft": false,
"src": "Pause-For-Masters"
},
{
"dst": "Reactive-Forwarding.Net-Pingall",
"isSoft": true,
"src": "Reactive-Forwarding.Net-Link-Down-Up"
},
{
"dst": "Wait-for-Start-3",
"isSoft": true,
"src": "Check-Logs-3"
},
{
"dst": "Wait-for-Start-2",
"isSoft": true,
"src": "Check-Components-2"
},
{
"dst": "Uninstall-Reactive-Forwarding",
"isSoft": false,
"src": "Find-Host-1"
},
{
"dst": "Wipe-Out-Data-Before",
"isSoft": true,
"src": "Initial-Summary-Check"
},
{
"dst": "Reactive-Forwarding.Ping-3",
"isSoft": false,
"src": "Reactive-Forwarding.Link-1-Up"
},
{
"dst": "Archetypes",
"isSoft": true,
"src": "Wrapup"
},
{
"dst": "Reactive-Forwarding.Ping-4",
"isSoft": false,
"src": "Reactive-Forwarding.Link-2-Up"
},
{
"dst": "Host-Intent-Connectivity",
"isSoft": true,
"src": "Net-Teardown"
},
{
"dst": "Host-Intent.Ping-3",
"isSoft": false,
"src": "Host-Intent.Link-1-Up"
},
{
"dst": "Host-Intent.Ping-1",
"isSoft": false,
"src": "Host-Intent.Link-1-Down"
},
{
"dst": "Install-App",
"isSoft": false,
"src": "Create-App-CLI-Overlay"
},
{
"dst": "Final-Check-Logs-3",
"isSoft": true,
"src": "Fetch-Logs-3"
},
{
"dst": "Install-App",
"isSoft": false,
"src": "Verify-App"
},
{
"dst": "Host-Intent.Link-2-Down",
"isSoft": false,
"src": "Host-Intent.Ping-3"
},
{
"dst": "Prerequisites",
"isSoft": false,
"src": "Setup"
},
{
"dst": "Verify-App",
"isSoft": true,
"src": "Reinstall-App-With-CLI"
},
{
"dst": "Net-Smoke",
"isSoft": true,
"src": "Archetypes"
},
{
"dst": "Setup",
"isSoft": true,
"src": "Wrapup"
},
{
"dst": "Start-Mininet",
"isSoft": false,
"src": "Wait-For-Mininet"
},
{
"dst": "Verify-UI",
"isSoft": false,
"src": "Uninstall-App"
},
{
"dst": "Kill-3",
"isSoft": false,
"src": "Install-3"
},
{
"dst": "Wait-for-Start-1",
"isSoft": true,
"src": "Check-Components-1"
},
{
"dst": "Wait-for-Start-1",
"isSoft": true,
"src": "Check-Nodes-1"
},
{
"dst": "Push-Topos",
"isSoft": false,
"src": "Start-Mininet"
},
{
"dst": "Reactive-Forwarding.Check-Summary-For-Hosts",
"isSoft": true,
"src": "Reactive-Forwarding.Config-Topo"
},
{
"dst": "Reactive-Forwarding.Install-Apps",
"isSoft": false,
"src": "Reactive-Forwarding.Check-Apps"
},
{
"dst": "Push-Bits",
"isSoft": false,
"src": "Install-2"
},
{
"dst": "Install-1",
"isSoft": false,
"src": "Secure-SSH"
},
{
"dst": "Create-Intent",
"isSoft": false,
"src": "Host-Intent.Net-Link-Down-Up"
},
{
"dst": "Verify-CLI",
"isSoft": true,
"src": "Reinstall-App-With-UI"
},
{
"dst": "Wait-for-Start-3",
"isSoft": true,
"src": "Check-Apps-3"
},
{
"dst": "Net-Smoke",
"isSoft": true,
"src": "Wrapup"
},
{
"dst": "Initial-Summary-Check",
"isSoft": false,
"src": "Start-Mininet"
},
{
"dst": "Install-3",
"isSoft": false,
"src": "Wait-for-Start-3"
},
{
"dst": "Reactive-Forwarding.Link-1-Up",
"isSoft": false,
"src": "Reactive-Forwarding.Ping-4"
},
{
"dst": "Check-Summary",
"isSoft": true,
"src": "Balance-Masters"
},
{
"dst": "Reactive-Forwarding.Net-Link-Down-Up",
"isSoft": true,
"src": "Host-Intent-Connectivity"
},
{
"dst": "Secure-SSH",
"isSoft": true,
"src": "Wait-for-Start-2"
},
{
"dst": "Build-App-With-CLI",
"isSoft": false,
"src": "Reinstall-App-With-CLI"
},
{
"dst": "Uninstall-1",
"isSoft": false,
"src": "Kill-1"
},
{
"dst": "Find-Host-1",
"isSoft": false,
"src": "Find-Host-2"
},
{
"dst": "Create-App-CLI-Overlay",
"isSoft": false,
"src": "Build-App-With-CLI"
},
{
"dst": "Net-Setup",
"isSoft": false,
"src": "Reactive-Forwarding.Net-Link-Down-Up"
},
{
"dst": "Kill-2",
"isSoft": false,
"src": "Install-2"
},
{
"dst": "Wait-for-Start-1",
"isSoft": true,
"src": "Check-Logs-1"
},
{
"dst": "Wait-for-Start-2",
"isSoft": true,
"src": "Check-Nodes-2"
},
{
"dst": "Reactive-Forwarding.Ping-All-And-Verify",
"isSoft": true,
"src": "Reactive-Forwarding.Check-Summary-For-Hosts"
},
{
"dst": "Clean-Up",
"isSoft": false,
"src": "Create-App"
},
{
"dst": "Host-Intent.Link-1-Up",
"isSoft": false,
"src": "Host-Intent.Ping-4"
},
{
"dst": "Build-App-With-UI",
"isSoft": false,
"src": "Reinstall-App-With-UI"
},
{
"dst": "Install-2",
"isSoft": false,
"src": "Secure-SSH"
},
{
"dst": "Wait-For-Mininet",
"isSoft": false,
"src": "Check-Summary"
},
{
"dst": "Host-Intent.Net-Link-Down-Up",
"isSoft": false,
"src": "Remove-Intent"
},
{
"dst": "Net-Setup",
"isSoft": false,
"src": "Host-Intent-Connectivity"
},
{
"dst": "Net-Setup",
"isSoft": false,
"src": "Reactive-Forwarding.Net-Pingall"
},
{
"dst": "Reactive-Forwarding.Link-2-Down",
"isSoft": false,
"src": "Reactive-Forwarding.Ping-3"
},
{
"dst": "Find-Host-2",
"isSoft": false,
"src": "Create-Intent"
},
{
"dst": "Wait-for-Start-2",
"isSoft": true,
"src": "Check-Apps-2"
},
{
"dst": "Final-Check-Logs-1",
"isSoft": true,
"src": "Fetch-Logs-1"
},
{
"dst": "Install-2",
"isSoft": false,
"src": "Wait-for-Start-2"
},
{
"dst": "Reactive-Forwarding.Ping-1",
"isSoft": false,
"src": "Reactive-Forwarding.Link-1-Down"
},
{
"dst": "Create-App",
"isSoft": false,
"src": "Build-App"
},
{
"dst": "Check-Summary",
"isSoft": true,
"src": "Check-Flows"
},
{
"dst": "Build-App",
"isSoft": false,
"src": "Install-App"
},
{
"dst": "Reinstall-App-With-UI",
"isSoft": false,
"src": "Verify-UI"
},
{
"dst": "Uninstall-2",
"isSoft": false,
"src": "Kill-2"
},
{
"dst": "Setup",
"isSoft": false,
"src": "Archetypes"
},
{
"dst": "Setup",
"isSoft": false,
"src": "Net-Smoke"
},
{
"dst": "Kill-1",
"isSoft": false,
"src": "Install-1"
},
{
"dst": "Reactive-Forwarding.Link-1-Down",
"isSoft": false,
"src": "Reactive-Forwarding.Ping-2"
},
{
"dst": "Wait-for-Start-2",
"isSoft": true,
"src": "Check-Logs-2"
},
{
"dst": "Wait-for-Start-3",
"isSoft": true,
"src": "Check-Components-3"
},
{
"dst": "Wait-for-Start-3",
"isSoft": true,
"src": "Check-Nodes-3"
},
{
"dst": "Stop-Mininet-If-Needed",
"isSoft": false,
"src": "Start-Mininet"
},
{
"dst": "Reactive-Forwarding.Link-2-Up",
"isSoft": false,
"src": "Reactive-Forwarding.Ping-5"
},
{
"dst": "Reactive-Forwarding.Check-Apps",
"isSoft": false,
"src": "Reactive-Forwarding.Ping-All-And-Verify"
},
{
"dst": "Install-3",
"isSoft": false,
"src": "Secure-SSH"
},
{
"dst": "Push-Bits",
"isSoft": false,
"src": "Install-3"
},
{
"dst": "Reinstall-App-With-CLI",
"isSoft": false,
"src": "Create-App-UI-Overlay"
},
{
"dst": "Push-Bits",
"isSoft": false,
"src": "Install-1"
},
{
"dst": "Wait-for-Start-1",
"isSoft": true,
"src": "Check-Apps-1"
}
],
"steps": [
{
"group": "Net-Setup",
"isGroup": false,
"name": "Check-Summary",
"status": "waiting"
},
{
"group": "Net-Setup",
"isGroup": false,
"name": "Check-Flows",
"status": "waiting"
},
{
"group": "Wrapup",
"isGroup": false,
"name": "Final-Check-Logs-1",
"status": "waiting"
},
{
"group": "Wrapup",
"isGroup": false,
"name": "Final-Check-Logs-2",
"status": "waiting"
},
{
"group": "Archetypes",
"isGroup": false,
"name": "Clean-Up",
"status": "waiting"
},
{
"group": "Archetypes",
"isGroup": false,
"name": "Build-App-With-UI",
"status": "waiting"
},
{
"group": "Archetypes",
"isGroup": false,
"name": "Uninstall-App",
"status": "waiting"
},
{
"group": "Wrapup",
"isGroup": false,
"name": "Final-Check-Logs-3",
"status": "waiting"
},
{
"group": "Host-Intent.Net-Link-Down-Up",
"isGroup": false,
"name": "Host-Intent.Link-2-Down",
"status": "waiting"
},
{
"group": "Wrapup",
"isGroup": false,
"name": "Fetch-Logs-3",
"status": "waiting"
},
{
"group": "Wrapup",
"isGroup": false,
"name": "Fetch-Logs-2",
"status": "waiting"
},
{
"group": "Setup",
"isGroup": false,
"name": "Check-Components-3",
"status": "waiting"
},
{
"group": "Wrapup",
"isGroup": false,
"name": "Fetch-Logs-1",
"status": "waiting"
},
{
"group": "Net-Setup",
"isGroup": false,
"name": "Push-Topos",
"status": "waiting"
},
{
"group": "Reactive-Forwarding.Net-Pingall",
"isGroup": false,
"name": "Reactive-Forwarding.Check-Apps",
"status": "waiting"
},
{
"group": "Setup",
"isGroup": false,
"name": "Wait-for-Start-3",
"status": "waiting"
},
{
"group": "Setup",
"isGroup": false,
"name": "Wait-for-Start-2",
"status": "waiting"
},
{
"group": "Setup",
"isGroup": false,
"name": "Wait-for-Start-1",
"status": "waiting"
},
{
"group": "Net-Smoke",
"isGroup": true,
"name": "Host-Intent-Connectivity",
"status": "waiting"
},
{
"group": "Host-Intent-Connectivity",
"isGroup": false,
"name": "Create-Intent",
"status": "waiting"
},
{
"isGroup": true,
"name": "Prerequisites",
"status": "in_progress"
},
{
"group": "Setup",
"isGroup": false,
"name": "Push-Bits",
"status": "waiting"
},
{
"group": "Setup",
"isGroup": false,
"name": "Check-Logs-2",
"status": "waiting"
},
{
"group": "Setup",
"isGroup": false,
"name": "Check-Logs-3",
"status": "waiting"
},
{
"group": "Setup",
"isGroup": false,
"name": "Kill-1",
"status": "waiting"
},
{
"group": "Setup",
"isGroup": false,
"name": "Kill-3",
"status": "waiting"
},
{
"group": "Setup",
"isGroup": false,
"name": "Kill-2",
"status": "waiting"
},
{
"group": "Host-Intent-Connectivity",
"isGroup": true,
"name": "Host-Intent.Net-Link-Down-Up",
"status": "waiting"
},
{
"group": "Host-Intent.Net-Link-Down-Up",
"isGroup": false,
"name": "Host-Intent.Ping-1",
"status": "waiting"
},
{
"group": "Archetypes",
"isGroup": false,
"name": "Verify-UI",
"status": "waiting"
},
{
"group": "Host-Intent.Net-Link-Down-Up",
"isGroup": false,
"name": "Host-Intent.Ping-2",
"status": "waiting"
},
{
"group": "Host-Intent.Net-Link-Down-Up",
"isGroup": false,
"name": "Host-Intent.Ping-3",
"status": "waiting"
},
{
"group": "Setup",
"isGroup": false,
"name": "Uninstall-1",
"status": "waiting"
},
{
"group": "Setup",
"isGroup": false,
"name": "Check-Logs-1",
"status": "waiting"
},
{
"group": "Host-Intent.Net-Link-Down-Up",
"isGroup": false,
"name": "Host-Intent.Ping-4",
"status": "waiting"
},
{
"group": "Setup",
"isGroup": false,
"name": "Uninstall-3",
"status": "waiting"
},
{
"group": "Host-Intent.Net-Link-Down-Up",
"isGroup": false,
"name": "Host-Intent.Ping-5",
"status": "waiting"
},
{
"group": "Setup",
"isGroup": false,
"name": "Uninstall-2",
"status": "waiting"
},
{
"group": "Reactive-Forwarding.Net-Pingall",
"isGroup": false,
"name": "Reactive-Forwarding.Install-Apps",
"status": "waiting"
},
{
"group": "Net-Smoke",
"isGroup": true,
"name": "Reactive-Forwarding.Net-Link-Down-Up",
"status": "waiting"
},
{
"group": "Prerequisites",
"isGroup": false,
"name": "Check-ONOS-Bits",
"status": "in_progress"
},
{
"isGroup": true,
"name": "Wrapup",
"status": "waiting"
},
{
"group": "Setup",
"isGroup": false,
"name": "Install-2",
"status": "waiting"
},
{
"group": "Host-Intent-Connectivity",
"isGroup": false,
"name": "Find-Host-1",
"status": "waiting"
},
{
"group": "Setup",
"isGroup": false,
"name": "Install-1",
"status": "waiting"
},
{
"group": "Net-Setup",
"isGroup": false,
"name": "Wipe-Out-Data-Before",
"status": "waiting"
},
{
"group": "Net-Setup",
"isGroup": false,
"name": "Pause-For-Masters",
"status": "waiting"
},
{
"group": "Reactive-Forwarding.Net-Link-Down-Up",
"isGroup": false,
"name": "Reactive-Forwarding.Link-2-Up",
"status": "waiting"
},
{
"group": "Net-Smoke",
"isGroup": true,
"name": "Reactive-Forwarding.Net-Pingall",
"status": "waiting"
},
{
"group": "Setup",
"isGroup": false,
"name": "Check-Components-2",
"status": "waiting"
},
{
"group": "Setup",
"isGroup": false,
"name": "Check-Components-1",
"status": "waiting"
},
{
"group": "Archetypes",
"isGroup": false,
"name": "Reinstall-App-With-UI",
"status": "waiting"
},
{
"group": "Archetypes",
"isGroup": false,
"name": "Reinstall-App-With-CLI",
"status": "waiting"
},
{
"group": "Archetypes",
"isGroup": false,
"name": "Build-App-With-CLI",
"status": "waiting"
},
{
"group": "Host-Intent-Connectivity",
"isGroup": false,
"name": "Uninstall-Reactive-Forwarding",
"status": "waiting"
},
{
"group": "Host-Intent.Net-Link-Down-Up",
"isGroup": false,
"name": "Host-Intent.Link-2-Up",
"status": "waiting"
},
{
"group": "Net-Teardown",
"isGroup": false,
"name": "Stop-Mininet",
"status": "waiting"
},
{
"group": "Reactive-Forwarding.Net-Pingall",
"isGroup": false,
"name": "Reactive-Forwarding.Config-Topo",
"status": "waiting"
},
{
"group": "Archetypes",
"isGroup": false,
"name": "Create-App-CLI-Overlay",
"status": "waiting"
},
{
"group": "Reactive-Forwarding.Net-Link-Down-Up",
"isGroup": false,
"name": "Reactive-Forwarding.Link-1-Down",
"status": "waiting"
},
{
"isGroup": true,
"name": "Net-Smoke",
"status": "waiting"
},
{
"group": "Prerequisites",
"isGroup": false,
"name": "Check-Passwordless-Login-2",
"status": "in_progress"
},
{
"group": "Prerequisites",
"isGroup": false,
"name": "Check-Passwordless-Login-1",
"status": "in_progress"
},
{
"group": "Prerequisites",
"isGroup": false,
"name": "Check-Passwordless-Login-3",
"status": "in_progress"
},
{
"group": "Setup",
"isGroup": false,
"name": "Secure-SSH",
"status": "waiting"
},
{
"group": "Net-Smoke",
"isGroup": true,
"name": "Net-Setup",
"status": "waiting"
},
{
"group": "Setup",
"isGroup": false,
"name": "Check-Nodes-1",
"status": "waiting"
},
{
"group": "Setup",
"isGroup": false,
"name": "Install-3",
"status": "waiting"
},
{
"group": "Host-Intent-Connectivity",
"isGroup": false,
"name": "Find-Host-2",
"status": "waiting"
},
{
"group": "Net-Setup",
"isGroup": false,
"name": "Initial-Summary-Check",
"status": "waiting"
},
{
"group": "Archetypes",
"isGroup": false,
"name": "Create-App",
"status": "waiting"
},
{
"group": "Setup",
"isGroup": false,
"name": "Check-Nodes-3",
"status": "waiting"
},
{
"group": "Setup",
"isGroup": false,
"name": "Check-Nodes-2",
"status": "waiting"
},
{
"group": "Reactive-Forwarding.Net-Link-Down-Up",
"isGroup": false,
"name": "Reactive-Forwarding.Link-2-Down",
"status": "waiting"
},
{
"isGroup": true,
"name": "Setup",
"status": "waiting"
},
{
"group": "Archetypes",
"isGroup": false,
"name": "Verify-App",
"status": "waiting"
},
{
"group": "Reactive-Forwarding.Net-Link-Down-Up",
"isGroup": false,
"name": "Reactive-Forwarding.Ping-1",
"status": "waiting"
},
{
"group": "Reactive-Forwarding.Net-Link-Down-Up",
"isGroup": false,
"name": "Reactive-Forwarding.Ping-2",
"status": "waiting"
},
{
"group": "Net-Setup",
"isGroup": false,
"name": "Start-Mininet",
"status": "waiting"
},
{
"group": "Reactive-Forwarding.Net-Link-Down-Up",
"isGroup": false,
"name": "Reactive-Forwarding.Ping-3",
"status": "waiting"
},
{
"group": "Reactive-Forwarding.Net-Link-Down-Up",
"isGroup": false,
"name": "Reactive-Forwarding.Ping-4",
"status": "waiting"
},
{
"group": "Reactive-Forwarding.Net-Link-Down-Up",
"isGroup": false,
"name": "Reactive-Forwarding.Ping-5",
"status": "waiting"
},
{
"group": "Archetypes",
"isGroup": false,
"name": "Verify-CLI",
"status": "waiting"
},
{
"group": "Reactive-Forwarding.Net-Pingall",
"isGroup": false,
"name": "Reactive-Forwarding.Check-Summary-For-Hosts",
"status": "waiting"
},
{
"group": "Net-Smoke",
"isGroup": true,
"name": "Net-Teardown",
"status": "waiting"
},
{
"group": "Host-Intent.Net-Link-Down-Up",
"isGroup": false,
"name": "Host-Intent.Link-1-Up",
"status": "waiting"
},
{
"group": "Host-Intent-Connectivity",
"isGroup": false,
"name": "Remove-Intent",
"status": "waiting"
},
{
"group": "Archetypes",
"isGroup": false,
"name": "Install-App",
"status": "waiting"
},
{
"group": "Archetypes",
"isGroup": false,
"name": "Create-App-UI-Overlay",
"status": "waiting"
},
{
"group": "Reactive-Forwarding.Net-Link-Down-Up",
"isGroup": false,
"name": "Reactive-Forwarding.Link-1-Up",
"status": "waiting"
},
{
"group": "Net-Setup",
"isGroup": false,
"name": "Wait-For-Mininet",
"status": "waiting"
},
{
"group": "Setup",
"isGroup": false,
"name": "Check-Apps-3",
"status": "waiting"
},
{
"group": "Setup",
"isGroup": false,
"name": "Check-Apps-2",
"status": "waiting"
},
{
"group": "Setup",
"isGroup": false,
"name": "Check-Apps-1",
"status": "waiting"
},
{
"group": "Net-Setup",
"isGroup": false,
"name": "Stop-Mininet-If-Needed",
"status": "waiting"
},
{
"group": "Prerequisites",
"isGroup": false,
"name": "Check-Environment",
"status": "in_progress"
},
{
"isGroup": true,
"name": "Archetypes",
"status": "waiting"
},
{
"group": "Host-Intent.Net-Link-Down-Up",
"isGroup": false,
"name": "Host-Intent.Link-1-Down",
"status": "waiting"
},
{
"group": "Net-Setup",
"isGroup": false,
"name": "Balance-Masters",
"status": "waiting"
},
{
"group": "Reactive-Forwarding.Net-Pingall",
"isGroup": false,
"name": "Reactive-Forwarding.Ping-All-And-Verify",
"status": "waiting"
},
{
"group": "Archetypes",
"isGroup": false,
"name": "Build-App",
"status": "waiting"
}
]
}
<!DOCTYPE html>
<!--
~ Copyright 2015-present 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.
-->
<html>
<head lang="en">
<meta charset="utf-8">
<title>Scenario Test Coordinator</title>
<link rel="stylesheet" href="stc.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
<script src="stc.js"></script>
</head>
<body>
</body>
</html>
\ No newline at end of file
/*
* Copyright 2015-present 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.
*/
.body {
font-family: Helvetica, Arial, sans-serif;
}
.node {
stroke: #fff;
stroke-width: 1.5px;
}
.link {
stroke: #999;
stroke-opacity: .6;
}
text {
font-family: 'DejaVu Sans', Arial, Helvetica, sans-serif;
stroke: #000;
stroke-width: 0.2;
font-weight: normal;
font-size: 0.6em;
}
\ No newline at end of file
/*
* Copyright 2015-present 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.
*/
(function () {
var ws, flow,
nodes = [],
links = [],
nodeIndexes = {};
var width = 2400,
height = 2400;
var color = d3.scale.category20();
var force = d3.layout.force()
.charge(-820)
.linkDistance(50)
.size([width, height]);
// Process flow graph layout
function createNode(n) {
nodeIndexes[n.name] = nodes.push(n) - 1;
}
function createLink(e) {
e.source = nodeIndexes[e.src];
e.target = nodeIndexes[e.dst];
links.push(e);
}
// Returns the newly computed bounding box of the rectangle
function adjustRectToFitText(n) {
var text = n.select('text'),
box = text.node().getBBox();
text.attr('text-anchor', 'left')
.attr('y', 2)
.attr('x', 4);
// add padding
box.x -= 4;
box.width += 8;
box.y -= 2;
box.height += 4;
n.select("rect").attr(box);
}
function processFlow() {
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
flow.steps.forEach(createNode);
flow.requirements.forEach(createLink);
force
.nodes(nodes)
.links(links)
.start();
var link = svg.selectAll(".link")
.data(links)
.enter().append("line")
.attr("class", "link")
.style("stroke-width", function(d) { return d.isSoft ? 1 : 2; });
var node = svg.selectAll(".node")
.data(nodes)
.enter().append("g")
.attr("class", "node")
.call(force.drag);
node.append("rect")
.attr({ rx: 5, ry:5, width:180, height:18 })
.style("fill", function(d) { return color(d.group); });
node.append("text").text( function(d) { return d.name; })
.attr({ dy:"1.1em", width:100, height:16, x:4, y:2 });
node.append("title")
.text(function(d) { return d.name; });
force.on("tick", function() {
link.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
node.attr("transform", function(d) { return "translate(" + (d.x - 180/2) + "," + (d.y - 18/2) + ")"; });
});
}
// Web socket callbacks
function handleOpen() {
console.log('WebSocket open');
}
// Handles the specified (incoming) message using handler bindings.
function handleMessage(msg) {
console.log('rx: ', msg);
evt = JSON.parse(msg.data);
if (evt.event === 'progress') {
} else if (evt.event === 'log') {
} else if (evt.event === 'flow') {
flow = evt.payload;
processFlow();
}
}
function handleClose() {
console.log('WebSocket closed');
}
if (false) {
d3.json("data.json", function (error, data) {
flow = data;
processFlow();
});
return;
}
// Open the web-socket
ws = new WebSocket(document.location.href.replace('http:', 'ws:'));
if (ws) {
ws.onopen = handleOpen;
ws.onmessage = handleMessage;
ws.onclose = handleClose;
}
})();
\ No newline at end of file
/*
* Copyright 2015-present 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 com.google.common.io.Files;
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 {
private static File testDir;
@BeforeClass
public static void setUpClass() throws IOException {
testDir = Files.createTempDir();
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("TOC1", "1.2.3.1");
System.setProperty("TOC2", "1.2.3.2");
System.setProperty("TOC3", "1.2.3.3");
System.setProperty("test.dir", testDir.getAbsolutePath());
}
@AfterClass
public static void tearDownClass() throws IOException {
Tools.removeDirectory(testDir.getPath());
}
static FileInputStream getStream(String name) throws FileNotFoundException {
return new FileInputStream(new File(testDir, name));
}
static void stageTestResource(String name) throws IOException {
byte[] bytes = toByteArray(CompilerTest.class.getResourceAsStream(name));
write(bytes, new File(testDir, 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", 33, flow.getVertexes().size());
assertEquals("incorrect dependency count", 26, flow.getEdges().size());
assertEquals("incorrect logDir",
new File(testDir.getAbsolutePath(), "foo"), compiler.logDir());
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-present 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.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 ";
}
@AfterClass
public static void tearDownClass() throws IOException {
CompilerTest.tearDownClass();
}
@Test
public void simple() throws IOException, InterruptedException {
executeTest("simple-scenario.xml");
}
@Test
public void complex() throws IOException, InterruptedException {
executeTest("scenario.xml");
}
private void executeTest(String name) throws IOException, InterruptedException {
Scenario scenario = loadScenario(getStream(name));
Compiler compiler = new Compiler(scenario);
compiler.compile();
Tools.removeDirectory(compiler.logDir());
coordinator = new Coordinator(scenario, compiler.processFlow(), compiler.logDir());
coordinator.addListener(listener);
coordinator.reset();
coordinator.start();
coordinator.waitFor();
coordinator.removeListener(listener);
}
private class Listener implements StepProcessListener {
@Override
public void onStart(Step step, String command) {
print("> %s: started; %s", step.name(), command);
}
@Override
public void onCompletion(Step step, Coordinator.Status status) {
print("< %s: %s", step.name(), status == Coordinator.Status.SUCCEEDED ? "completed" : "failed");
}
@Override
public void onOutput(Step step, String line) {
print(" %s: %s", step.name(), line);
}
}
}
\ No newline at end of file
/*
* Copyright 2015-present 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;
@Override
@Before
public void setUp() throws ConfigurationException {
super.setUp();
step1 = new Step("step1", CMD, null, null, null, 0);
step2 = new Step("step2", CMD, null, null, null, 0);
}
@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());
}
@Override
@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-present 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, ENV, CWD, parent, 1);
assertEquals("incorrect name", NAME, group.name());
assertEquals("incorrect command", CMD, group.command());
assertEquals("incorrect env", ENV, group.env());
assertEquals("incorrect cwd", CWD, group.cwd());
assertSame("incorrect group", parent, group.group());
assertEquals("incorrect delay", 1, group.delay());
Step step = new Step("step", null, null, null, group, 0);
group.addChild(step);
assertSame("incorrect child", step, group.children().iterator().next());
}
@Test
public void equality() {
Group g1 = new Group(NAME, CMD, null, null, parent, 0);
Group g2 = new Group(NAME, CMD, ENV, CWD, null, 0);
Group g3 = new Group("foo", null, null, null, parent, 0);
new EqualsTester()
.addEqualityGroup(g1, g2)
.addEqualityGroup(g3)
.testEquals();
}
}
\ No newline at end of file
/*
* Copyright 2015-present 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.stc.MonitorLayout.Box;
import java.io.IOException;
import static org.junit.Assert.assertEquals;
import static org.onlab.stc.CompilerTest.getStream;
import static org.onlab.stc.CompilerTest.stageTestResource;
import static org.onlab.stc.MonitorLayout.SLOT_WIDTH;
import static org.onlab.stc.Scenario.loadScenario;
/**
* Tests of the monitor layout functionality.
*/
public class MonitorLayoutTest {
private MonitorLayout layout;
@BeforeClass
public static void setUpClass() throws IOException {
CompilerTest.setUpClass();
}
@AfterClass
public static void tearDownClass() throws IOException {
CompilerTest.tearDownClass();
}
private Compiler getCompiler(String name) throws IOException {
stageTestResource(name);
Scenario scenario = loadScenario(getStream(name));
Compiler compiler = new Compiler(scenario);
compiler.compile();
return compiler;
}
@Test
public void basic() throws IOException {
layout = new MonitorLayout(getCompiler("layout-basic.xml"));
validate(layout, null, 0, 1, 5, 2);
validate(layout, "a", 1, 1, 1, 1, 1, -SLOT_WIDTH / 2);
validate(layout, "b", 2, 2, 1, 1, 0, 0);
validate(layout, "f", 3, 3, 1);
validate(layout, "g", 1, 1, 4, 1, 1, SLOT_WIDTH / 2);
validate(layout, "c", 2, 1, 1);
validate(layout, "d", 3, 2, 1);
validate(layout, "e", 4, 3, 1);
}
@Test
public void basicNest() throws IOException {
layout = new MonitorLayout(getCompiler("layout-basic-nest.xml"));
validate(layout, null, 0, 1, 6, 2);
validate(layout, "a", 1, 1, 1, 1, 1, -SLOT_WIDTH / 2);
validate(layout, "b", 2, 2, 1);
validate(layout, "f", 3, 3, 1);
validate(layout, "g", 1, 1, 5, 1);
validate(layout, "c", 2, 1, 1);
validate(layout, "gg", 3, 2, 3, 1);
validate(layout, "d", 4, 1, 1);
validate(layout, "e", 5, 2, 1);
}
@Test
public void staggeredDependencies() throws IOException {
layout = new MonitorLayout(getCompiler("layout-staggered-dependencies.xml"));
validate(layout, null, 0, 1, 7, 4);
validate(layout, "a", 1, 1, 1, 1, 1, -SLOT_WIDTH - SLOT_WIDTH / 2);
validate(layout, "aa", 1, 1, 1, 1, 1, -SLOT_WIDTH / 2);
validate(layout, "b", 2, 2, 1);
validate(layout, "f", 3, 3, 1);
validate(layout, "g", 1, 1, 5, 2, 1, +SLOT_WIDTH / 2);
validate(layout, "c", 2, 1, 1);
validate(layout, "gg", 3, 2, 3, 2);
validate(layout, "d", 4, 1, 1);
validate(layout, "dd", 4, 1, 1);
validate(layout, "e", 5, 2, 1);
validate(layout, "i", 6, 6, 1);
}
@Test
public void deepNext() throws IOException {
layout = new MonitorLayout(getCompiler("layout-deep-nest.xml"));
validate(layout, null, 0, 1, 7, 6);
validate(layout, "a", 1, 1, 1);
validate(layout, "aa", 1, 1, 1);
validate(layout, "b", 2, 2, 1);
validate(layout, "f", 3, 3, 1);
validate(layout, "g", 1, 1, 5, 2);
validate(layout, "c", 2, 1, 1);
validate(layout, "gg", 3, 2, 3, 2);
validate(layout, "d", 4, 1, 1);
validate(layout, "dd", 4, 1, 1);
validate(layout, "e", 5, 2, 1);
validate(layout, "i", 6, 6, 1);
validate(layout, "g1", 1, 1, 6, 2);
validate(layout, "g2", 2, 1, 5, 2);
validate(layout, "g3", 3, 1, 4, 2);
validate(layout, "u", 4, 1, 1);
validate(layout, "v", 4, 1, 1);
validate(layout, "w", 5, 2, 1);
validate(layout, "z", 6, 3, 1);
}
private void validate(MonitorLayout layout, String name,
int absoluteTier, int tier, int depth, int breadth) {
Box b = layout.get(name);
assertEquals("incorrect absolute tier", absoluteTier, b.absoluteTier());
assertEquals("incorrect tier", tier, b.tier());
assertEquals("incorrect depth", depth, b.depth());
assertEquals("incorrect breadth", breadth, b.breadth());
}
private void validate(MonitorLayout layout, String name,
int absoluteTier, int tier, int depth, int breadth,
int top, int center) {
validate(layout, name, absoluteTier, tier, depth, breadth);
Box b = layout.get(name);
assertEquals("incorrect top", top, b.top());
assertEquals("incorrect center", center, b.center());
}
private void validate(MonitorLayout layout, String name,
int absoluteTier, int tier, int depth) {
validate(layout, name, absoluteTier, tier, depth, 1);
}
}
\ No newline at end of file
/*
* Copyright 2015-present 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-present 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.ClassRule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import java.io.File;
import static com.google.common.base.Preconditions.checkState;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.onlab.stc.Coordinator.Status.SUCCEEDED;
/**
* Test of the step processor.
*/
public class StepProcessorTest {
@ClassRule
public static TemporaryFolder testFolder = new TemporaryFolder();
private static File dir;
private final Listener delegate = new Listener();
@BeforeClass
public static void setUpClass() {
dir = testFolder.getRoot();
StepProcessor.launcher = "echo";
checkState(dir.exists() || dir.mkdirs(), "Unable to create directory");
}
@Test
public void basics() {
Step step = new Step("foo", "ls " + dir.getAbsolutePath(), null, null, null, 0);
StepProcessor processor = new StepProcessor(step, dir, delegate, step.command());
processor.run();
assertTrue("should be started", delegate.started);
assertTrue("should be stopped", delegate.stopped);
assertEquals("incorrect status", SUCCEEDED, delegate.status);
assertTrue("should have output", delegate.output);
}
private class Listener implements StepProcessListener {
private Coordinator.Status status;
private boolean started, stopped, output;
@Override
public void onStart(Step step, String command) {
started = true;
}
@Override
public void onCompletion(Step step, Coordinator.Status status) {
stopped = true;
this.status = status;
}
@Override
public void onOutput(Step step, String line) {
output = true;
}
}
}
\ No newline at end of file
/*
* Copyright 2015-present 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 static final String ENV = "environment";
protected static final String CWD = "directory";
protected Group parent;
@Before
public void setUp() throws ConfigurationException {
parent = new Group("parent", null, null, null, null, 0);
}
@Test
public void basics() {
Step step = new Step(NAME, CMD, ENV, CWD, parent, 1);
assertEquals("incorrect name", NAME, step.name());
assertEquals("incorrect command", CMD, step.command());
assertEquals("incorrect env", ENV, step.env());
assertEquals("incorrect cwd", CWD, step.cwd());
assertSame("incorrect group", parent, step.group());
assertEquals("incorrect delay", 1, step.delay());
}
@Test
public void equality() {
Step s1 = new Step(NAME, CMD, null, null, parent, 0);
Step s2 = new Step(NAME, CMD, ENV, CWD, null, 0);
Step s3 = new Step("foo", null, null, null, parent, 0);
new EqualsTester()
.addEqualityGroup(s1, s2)
.addEqualityGroup(s3)
.testEquals();
}
}
\ No newline at end of file
<!--
~ Copyright 2015-present 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="basic-nest">
<step name="a"/>
<step name="b" requires="a"/>
<step name="f" requires="b"/>
<group name="g">
<step name="c"/>
<group name="gg" requires="c">
<step name="d"/>
<step name="e" requires="d"/>
</group>
</group>
</scenario>
\ No newline at end of file
<!--
~ Copyright 2015-present 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="basic">
<step name="a"/>
<step name="b" requires="a"/>
<step name="f" requires="b"/>
<group name="g">
<step name="c"/>
<step name="d" requires="c"/>
<step name="e" requires="d"/>
</group>
</scenario>
\ No newline at end of file
<!--
~ Copyright 2015-present 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="basic-nest">
<step name="a"/>
<step name="aa"/>
<step name="b" requires="a"/>
<step name="f" requires="b,aa"/>
<group name="g">
<step name="c"/>
<group name="gg" requires="c">
<step name="d"/>
<step name="dd" requires="c"/>
<step name="e" requires="d"/>
</group>
</group>
<step name="i" requires="f,g"/>
<group name="g1">
<group name="g2">
<group name="g3">
<step name="u"/>
<step name="v"/>
<step name="w" requires="u,v"/>
<step name="z" requires="u,w"/>
</group>
</group>
</group>
</scenario>
\ No newline at end of file
<!--
~ Copyright 2015-present 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="basic-nest">
<step name="a"/>
<step name="aa"/>
<step name="b" requires="a"/>
<step name="f" requires="b,aa"/>
<group name="g">
<step name="c"/>
<group name="gg" requires="c">
<step name="d"/>
<step name="dd" requires="c"/>
<step name="e" requires="d"/>
</group>
</group>
<step name="i" requires="f,g"/>
</scenario>
\ No newline at end of file
<!--
~ Copyright 2015-present 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="${test.dir}/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-present 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="${test.dir}/foo">
<import file="${test.dir}/one-scenario.xml" namespace="foo"/>
<import file="${test.dir}/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="${TOC#}" requires="alpha">
<step name="ping-${#}" exec="asdads ${TOC#}"/>
<step name="pong-${#}" exec="asdads"/>
<step name="ding-${#}" exec="asdads" requires="ping-${#},pong-${#}"/>
<dependency name="maybe" requires="ding-${#}"/>
</parallel>
<sequential var="${TOC#}" requires="alpha" starts="fifi-${#},gigi-${#}" ends="gaga-${#-1}">
<step name="fifi-${#}" exec="asdads ${TOC#}"/>
<step name="gigi-${#}" exec="asdads"/>
<step name="gaga-${#}" exec="asdads" requires="fifi-${#},gigi-${#}"/>
<dependency name="maybe" requires="gaga-${#}"/>
</sequential>
</scenario>
\ No newline at end of file
<!--
~ Copyright 2015-present 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="${test.dir}/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-present 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="${test.dir}/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