diff options
Diffstat (limited to 'framework/src/onos/utils/stc')
42 files changed, 5252 insertions, 0 deletions
diff --git a/framework/src/onos/utils/stc/bin/stc b/framework/src/onos/utils/stc/bin/stc new file mode 100755 index 00000000..23affc4a --- /dev/null +++ b/framework/src/onos/utils/stc/bin/stc @@ -0,0 +1,12 @@ +#!/bin/bash +#------------------------------------------------------------------------------- +# System Test Coordinator +#------------------------------------------------------------------------------- + +STC_ROOT=${STC_ROOT:-$(dirname $0)/..} +cd $STC_ROOT +VER=1.3.0-SNAPSHOT + +PATH=$PWD/bin:$PATH + +java -jar target/onlab-stc-$VER.jar "$@" diff --git a/framework/src/onos/utils/stc/bin/stc-launcher b/framework/src/onos/utils/stc/bin/stc-launcher new file mode 100755 index 00000000..0d56017e --- /dev/null +++ b/framework/src/onos/utils/stc/bin/stc-launcher @@ -0,0 +1,19 @@ +#!/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 diff --git a/framework/src/onos/utils/stc/pom.xml b/framework/src/onos/utils/stc/pom.xml new file mode 100644 index 00000000..a3f96430 --- /dev/null +++ b/framework/src/onos/utils/stc/pom.xml @@ -0,0 +1,122 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright 2015 Open Networking Laboratory + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.onosproject</groupId> + <artifactId>onlab-utils</artifactId> + <version>1.3.0-SNAPSHOT</version> + <relativePath>../pom.xml</relativePath> + </parent> + + <artifactId>onlab-stc</artifactId> + <packaging>jar</packaging> + + <description>System Test Coordinator</description> + + <dependencies> + <dependency> + <groupId>org.onosproject</groupId> + <artifactId>onlab-misc</artifactId> + </dependency> + <dependency> + <groupId>org.onosproject</groupId> + <artifactId>onlab-junit</artifactId> + <scope>test</scope> + </dependency> + + <dependency> + <groupId>commons-configuration</groupId> + <artifactId>commons-configuration</artifactId> + </dependency> + + <dependency> + <groupId>commons-collections</groupId> + <artifactId>commons-collections</artifactId> + </dependency> + + <dependency> + <groupId>com.fasterxml.jackson.core</groupId> + <artifactId>jackson-databind</artifactId> + <version>2.4.2</version> + <scope>compile</scope> + </dependency> + <dependency> + <groupId>com.fasterxml.jackson.core</groupId> + <artifactId>jackson-annotations</artifactId> + <version>2.4.2</version> + <scope>compile</scope> + </dependency> + + <dependency> + <groupId>org.eclipse.jetty</groupId> + <artifactId>jetty-server</artifactId> + <version>8.1.17.v20150415</version> + </dependency> + <dependency> + <groupId>org.eclipse.jetty</groupId> + <artifactId>jetty-servlet</artifactId> + <version>8.1.17.v20150415</version> + </dependency> + <dependency> + <groupId>org.eclipse.jetty</groupId> + <artifactId>jetty-websocket</artifactId> + <version>8.1.17.v20150415</version> + </dependency> + </dependencies> + + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-shade-plugin</artifactId> + <version>2.3</version> + <configuration> + <transformers> + <transformer + implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> + <mainClass>org.onlab.stc.Main + </mainClass> + </transformer> + </transformers> + <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> diff --git a/framework/src/onos/utils/stc/sample/scenario.xml b/framework/src/onos/utils/stc/sample/scenario.xml new file mode 100644 index 00000000..8cee6319 --- /dev/null +++ b/framework/src/onos/utils/stc/sample/scenario.xml @@ -0,0 +1,20 @@ +<!-- + ~ Copyright 2015 Open Networking Laboratory + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<scenario name="sample" description="Sample Test Scenario"> + <step name="alpha" exec="/bin/ls -l"/> + <step name="beta" exec="/bin/ls -lF"/> + <step name="gamma" exec="/bin/ls" requires="alpha,beta"/> +</scenario>
\ No newline at end of file diff --git a/framework/src/onos/utils/stc/src/main/java/org/onlab/stc/Compiler.java b/framework/src/onos/utils/stc/src/main/java/org/onlab/stc/Compiler.java new file mode 100644 index 00000000..c2a0c812 --- /dev/null +++ b/framework/src/onos/utils/stc/src/main/java/org/onlab/stc/Compiler.java @@ -0,0 +1,482 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onlab.stc; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; +import org.apache.commons.configuration.HierarchicalConfiguration; +import 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 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 = "${env.WORKSPACE}/tmp/stc/"; + + private static final String IMPORT = "import"; + private static final String GROUP = "group"; + private static final String STEP = "step"; + private static final String PARALLEL = "parallel"; + private static final String DEPENDENCY = "dependency"; + + private static final String LOG_DIR = "[@logDir]"; + private static final String NAME = "[@name]"; + private static final String COMMAND = "[@exec]"; + private static final String ENV = "[@env]"; + private static final String CWD = "[@cwd]"; + private static final String REQUIRES = "[@requires]"; + private static final String IF = "[@if]"; + private static final String UNLESS = "[@unless]"; + private static final String VAR = "[@var]"; + private static final String FILE = "[@file]"; + private static final String NAMESPACE = "[@namespace]"; + + static final String PROP_START = "${"; + static final String PROP_END = "}"; + private static final String HASH = "#"; + + private final Scenario scenario; + + private final Map<String, Step> steps = Maps.newHashMap(); + private final Map<String, Step> inactiveSteps = Maps.newHashMap(); + private final Map<String, String> requirements = Maps.newHashMap(); + private final Set<Dependency> dependencies = Sets.newHashSet(); + private final List<Integer> parallels = 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 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)); + + print("step name=%s command=%s env=%s cwd=%s", name, command, env, cwd); + Step step = new Step(name, command, env, cwd, parentGroup); + registerStep(step, cfg, namespace, parentGroup); + } + + /** + * Processes a group directive. + * + * @param cfg hierarchical definition + * @param namespace optional namespace + * @param parentGroup optional parent group + */ + private void processGroup(HierarchicalConfiguration cfg, + String namespace, Group parentGroup) { + String name = expand(prefix(cfg.getString(NAME), namespace)); + String 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)); + + print("group name=%s command=%s env=%s cwd=%s", name, command, env, cwd); + Group group = new Group(name, command, env, cwd, parentGroup); + if (registerStep(group, cfg, namespace, parentGroup)) { + compile(cfg, namespace, group); + } + } + + /** + * Registers the specified step or group. + * + * @param step step or group + * @param cfg hierarchical definition + * @param namespace optional namespace + * @param parentGroup optional parent group + * @return true of the step or group was registered as active + */ + private boolean registerStep(Step step, HierarchicalConfiguration cfg, + String namespace, Group parentGroup) { + 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) { + parallels.add(0, i); + compile(cfg, namespace, parentGroup); + parallels.remove(0); + i++; + } + } + + /** + * Returns the elaborated repetition construct conditional. + * + * @param var repetition var property + * @param i index to elaborate + * @return elaborated string + */ + private String condition(String var, Integer i) { + return expand(var.replaceFirst("#", i.toString())).trim(); + } + + /** + * Processes a dependency directive. + * + * @param cfg hierarchical definition + * @param namespace optional namespace + */ + private void processDependency(HierarchicalConfiguration cfg, String namespace) { + String name = expand(prefix(cfg.getString(NAME), namespace)); + String requires = expand(cfg.getString(REQUIRES)); + + print("dependency name=%s requires=%s", name, requires); + Step step = getStep(name, namespace); + 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 = parallels.get(0).toString(); + } else if (prop.endsWith(HASH)) { + pString = pString.replaceFirst("#}", parallels.get(0).toString() + "}"); + last = start; + continue; + } else { + // Try system property first, then fall back to env. variable. + value = System.getProperty(prop); + if (value == null) { + value = System.getenv(prop); + } + } + 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)); + } + } + +} diff --git a/framework/src/onos/utils/stc/src/main/java/org/onlab/stc/Coordinator.java b/framework/src/onos/utils/stc/src/main/java/org/onlab/stc/Coordinator.java new file mode 100644 index 00000000..8e3aad5b --- /dev/null +++ b/framework/src/onos/utils/stc/src/main/java/org/onlab/stc/Coordinator.java @@ -0,0 +1,364 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onlab.stc; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.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.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 = 16; + + 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 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); + } + + /** + * Wants for completion of the entire process flow. + * + * @return exit code to use + * @throws InterruptedException if interrupted while waiting for completion + */ + public int waitFor() throws InterruptedException { + latch.await(); + return store.hasFailures() ? 1 : 0; + } + + /** + * Returns set of all test steps. + * + * @return set of steps + */ + public Set<Step> getSteps() { + return store.getSteps(); + } + + /** + * Returns 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) { + if (step instanceof Group) { + Group group = (Group) step; + group.children().forEach(child -> delegate.onCompletion(child, SKIPPED)); + } + 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()) { + 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)); + } + } + +} diff --git a/framework/src/onos/utils/stc/src/main/java/org/onlab/stc/Dependency.java b/framework/src/onos/utils/stc/src/main/java/org/onlab/stc/Dependency.java new file mode 100644 index 00000000..9025d2e5 --- /dev/null +++ b/framework/src/onos/utils/stc/src/main/java/org/onlab/stc/Dependency.java @@ -0,0 +1,77 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onlab.stc; + +import com.google.common.base.MoreObjects; +import org.onlab.graph.AbstractEdge; + +import java.util.Objects; + +/** + * Representation of a dependency from one step on completion of another. + */ +public class Dependency extends AbstractEdge<Step> { + + private boolean isSoft; + + /** + * Creates a new edge between the specified source and destination vertexes. + * + * @param src source vertex + * @param dst destination vertex + * @param isSoft indicates whether this is a hard or soft dependency + */ + public Dependency(Step src, Step dst, boolean isSoft) { + super(src, dst); + this.isSoft = isSoft; + } + + /** + * Indicates whether this is a soft or hard dependency, i.e. one that + * requires successful completion of the dependency or just any completion. + * + * @return true if dependency is a soft one + */ + public boolean isSoft() { + return isSoft; + } + + @Override + public int hashCode() { + return 31 * super.hashCode() + Objects.hash(isSoft); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj instanceof Dependency) { + final Dependency other = (Dependency) obj; + return super.equals(other) && Objects.equals(this.isSoft, other.isSoft); + } + return false; + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("name", src().name()) + .add("requires", dst().name()) + .add("isSoft", isSoft) + .toString(); + } +} diff --git a/framework/src/onos/utils/stc/src/main/java/org/onlab/stc/Group.java b/framework/src/onos/utils/stc/src/main/java/org/onlab/stc/Group.java new file mode 100644 index 00000000..0281c364 --- /dev/null +++ b/framework/src/onos/utils/stc/src/main/java/org/onlab/stc/Group.java @@ -0,0 +1,60 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onlab.stc; + +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Sets; + +import java.util.Set; + +/** + * Represenation of a related group of steps. + */ +public class Group extends Step { + + private final Set<Step> children = Sets.newHashSet(); + + /** + * Creates a new test step. + * + * @param name group name + * @param command 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 + */ + public Group(String name, String command, String env, String cwd, Group group) { + super(name, command, env, cwd, group); + } + + /** + * Returns the set of child steps and groups contained within this group. + * + * @return set of children + */ + public Set<Step> children() { + return ImmutableSet.copyOf(children); + } + + /** + * Adds the specified step or group as a child of this group. + * + * @param child child step or group to add + */ + public void addChild(Step child) { + children.add(child); + } +} diff --git a/framework/src/onos/utils/stc/src/main/java/org/onlab/stc/Main.java b/framework/src/onos/utils/stc/src/main/java/org/onlab/stc/Main.java new file mode 100644 index 00000000..09b89456 --- /dev/null +++ b/framework/src/onos/utils/stc/src/main/java/org/onlab/stc/Main.java @@ -0,0 +1,353 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onlab.stc; + +import com.google.common.collect.ImmutableList; +import 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.FileInputStream; +import java.io.FileNotFoundException; +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 = "%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 = "%sFailed! " + MIXED_SUMMARY; + private static final String ABORTED_SUMMARY = "%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 Monitor monitor; + private Listener delegate = new Listener(); + + private static boolean useColor = Objects.equals("true", System.getenv("stcColor")); + + // 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 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) { + e.printStackTrace(); + } + } + + // 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())); + 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(); + int count = steps.size(); + if (exitCode == 0) { + print(SUCCESS_SUMMARY, 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, + color(FAILED), color(SUCCEEDED), success, + color(FAILED), failed, color(SKIPPED), skipped, color(null)); + } + } + } + + /** + * Internal delegate to monitor the process execution. + */ + private static 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); + } + + @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)); + } + } + + // 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!"); + } + } + + // 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) { + } + } +} diff --git a/framework/src/onos/utils/stc/src/main/java/org/onlab/stc/Monitor.java b/framework/src/onos/utils/stc/src/main/java/org/onlab/stc/Monitor.java new file mode 100644 index 00000000..4e6f63fa --- /dev/null +++ b/framework/src/onos/utils/stc/src/main/java/org/onlab/stc/Monitor.java @@ -0,0 +1,154 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onlab.stc; + +import com.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; + } + +} diff --git a/framework/src/onos/utils/stc/src/main/java/org/onlab/stc/MonitorDelegate.java b/framework/src/onos/utils/stc/src/main/java/org/onlab/stc/MonitorDelegate.java new file mode 100644 index 00000000..d11542a7 --- /dev/null +++ b/framework/src/onos/utils/stc/src/main/java/org/onlab/stc/MonitorDelegate.java @@ -0,0 +1,31 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onlab.stc; + +import com.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); +} diff --git a/framework/src/onos/utils/stc/src/main/java/org/onlab/stc/MonitorLayout.java b/framework/src/onos/utils/stc/src/main/java/org/onlab/stc/MonitorLayout.java new file mode 100644 index 00000000..1c0e7313 --- /dev/null +++ b/framework/src/onos/utils/stc/src/main/java/org/onlab/stc/MonitorLayout.java @@ -0,0 +1,307 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onlab.stc; + +import com.google.common.collect.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; + } + } +} diff --git a/framework/src/onos/utils/stc/src/main/java/org/onlab/stc/MonitorWebSocket.java b/framework/src/onos/utils/stc/src/main/java/org/onlab/stc/MonitorWebSocket.java new file mode 100644 index 00000000..cd146070 --- /dev/null +++ b/framework/src/onos/utils/stc/src/main/java/org/onlab/stc/MonitorWebSocket.java @@ -0,0 +1,149 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onlab.stc; + +import com.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() { + } + +} + diff --git a/framework/src/onos/utils/stc/src/main/java/org/onlab/stc/MonitorWebSocketServlet.java b/framework/src/onos/utils/stc/src/main/java/org/onlab/stc/MonitorWebSocketServlet.java new file mode 100644 index 00000000..a8705003 --- /dev/null +++ b/framework/src/onos/utils/stc/src/main/java/org/onlab/stc/MonitorWebSocketServlet.java @@ -0,0 +1,137 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onlab.stc; + +import com.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(); + } + } + } + } + } +} diff --git a/framework/src/onos/utils/stc/src/main/java/org/onlab/stc/ProcessFlow.java b/framework/src/onos/utils/stc/src/main/java/org/onlab/stc/ProcessFlow.java new file mode 100644 index 00000000..4d99b339 --- /dev/null +++ b/framework/src/onos/utils/stc/src/main/java/org/onlab/stc/ProcessFlow.java @@ -0,0 +1,37 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onlab.stc; + +import org.onlab.graph.MutableAdjacencyListsGraph; + +import java.util.Set; + +/** + * Graph representation of a test process flow. + */ +public class ProcessFlow extends MutableAdjacencyListsGraph<Step, Dependency> { + + /** + * Creates a graph comprising of the specified vertexes and edges. + * + * @param vertexes set of graph vertexes + * @param edges set of graph edges + */ + public ProcessFlow(Set<Step> vertexes, Set<Dependency> edges) { + super(vertexes, edges); + } + +} diff --git a/framework/src/onos/utils/stc/src/main/java/org/onlab/stc/Scenario.java b/framework/src/onos/utils/stc/src/main/java/org/onlab/stc/Scenario.java new file mode 100644 index 00000000..fd2cd62d --- /dev/null +++ b/framework/src/onos/utils/stc/src/main/java/org/onlab/stc/Scenario.java @@ -0,0 +1,106 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onlab.stc; + +import org.apache.commons.configuration.ConfigurationException; +import org.apache.commons.configuration.HierarchicalConfiguration; +import org.apache.commons.configuration.XMLConfiguration; + +import java.io.InputStream; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; + +/** + * Representation of a re-usable test scenario. + */ +public final class Scenario { + + private static final String SCENARIO = "scenario"; + private static final String NAME = "[@name]"; + private static final String DESCRIPTION = "[@description]"; + + private final String name; + private final String description; + private final HierarchicalConfiguration definition; + + // Creates a new scenario from the specified definition. + private Scenario(String name, String description, HierarchicalConfiguration definition) { + this.name = checkNotNull(name, "Name cannot be null"); + this.description = checkNotNull(description, "Description cannot be null"); + this.definition = checkNotNull(definition, "Definition cannot be null"); + } + + /** + * Loads a new scenario from the specified hierarchical configuration. + * + * @param definition scenario definition + * @return loaded scenario + */ + public static Scenario loadScenario(HierarchicalConfiguration definition) { + String name = definition.getString(NAME); + String description = definition.getString(DESCRIPTION, ""); + checkState(name != null, "Scenario name must be specified"); + return new Scenario(name, description, definition); + } + + /** + * Loads a new scenario from the specified input stream. + * + * @param stream scenario definition stream + * @return loaded scenario + */ + public static Scenario loadScenario(InputStream stream) { + XMLConfiguration cfg = new XMLConfiguration(); + cfg.setAttributeSplittingDisabled(true); + cfg.setDelimiterParsingDisabled(true); + cfg.setRootElementName(SCENARIO); + try { + cfg.load(stream); + return loadScenario(cfg); + } catch (ConfigurationException e) { + throw new IllegalArgumentException("Unable to load scenario from the stream", e); + } + } + + /** + * Returns the scenario name. + * + * @return scenario name + */ + public String name() { + return name; + } + + /** + * Returns the scenario description. + * + * @return scenario description + */ + public String description() { + return description; + } + + /** + * Returns the scenario definition. + * + * @return scenario definition + */ + public HierarchicalConfiguration definition() { + return definition; + } + +} diff --git a/framework/src/onos/utils/stc/src/main/java/org/onlab/stc/ScenarioStore.java b/framework/src/onos/utils/stc/src/main/java/org/onlab/stc/ScenarioStore.java new file mode 100644 index 00000000..d37222b1 --- /dev/null +++ b/framework/src/onos/utils/stc/src/main/java/org/onlab/stc/ScenarioStore.java @@ -0,0 +1,201 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onlab.stc; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.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(); + + /** + * 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(); + } 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()); + } + + /** + * 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); + } + } + } + } + +} diff --git a/framework/src/onos/utils/stc/src/main/java/org/onlab/stc/Step.java b/framework/src/onos/utils/stc/src/main/java/org/onlab/stc/Step.java new file mode 100644 index 00000000..3d8ea983 --- /dev/null +++ b/framework/src/onos/utils/stc/src/main/java/org/onlab/stc/Step.java @@ -0,0 +1,129 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onlab.stc; + +import com.google.common.base.MoreObjects; +import org.onlab.graph.Vertex; + +import java.util.Objects; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Representation of a test step. + */ +public class Step implements Vertex { + + protected final String name; + protected final String command; + protected final String env; + protected final String cwd; + protected final Group group; + + /** + * 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 + */ + public Step(String name, String command, String env, String cwd, Group group) { + this.name = checkNotNull(name, "Name cannot be null"); + this.group = group; + + // 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; + } + + + @Override + public int hashCode() { + return Objects.hash(name); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj instanceof Step) { + final Step other = (Step) obj; + return Objects.equals(this.name, other.name); + } + return false; + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("name", name) + .add("command", command) + .add("env", env) + .add("cwd", cwd) + .add("group", group) + .toString(); + } +} diff --git a/framework/src/onos/utils/stc/src/main/java/org/onlab/stc/StepEvent.java b/framework/src/onos/utils/stc/src/main/java/org/onlab/stc/StepEvent.java new file mode 100644 index 00000000..c9b81a24 --- /dev/null +++ b/framework/src/onos/utils/stc/src/main/java/org/onlab/stc/StepEvent.java @@ -0,0 +1,116 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onlab.stc; + +import org.onlab.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); + } +} diff --git a/framework/src/onos/utils/stc/src/main/java/org/onlab/stc/StepProcessListener.java b/framework/src/onos/utils/stc/src/main/java/org/onlab/stc/StepProcessListener.java new file mode 100644 index 00000000..a8222d0b --- /dev/null +++ b/framework/src/onos/utils/stc/src/main/java/org/onlab/stc/StepProcessListener.java @@ -0,0 +1,50 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onlab.stc; + +/** + * Entity capable of receiving notifications of process step execution events. + */ +public interface StepProcessListener { + + /** + * Indicates that process step has started. + * + * @param step subject step + * @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) { + } + +} diff --git a/framework/src/onos/utils/stc/src/main/java/org/onlab/stc/StepProcessor.java b/framework/src/onos/utils/stc/src/main/java/org/onlab/stc/StepProcessor.java new file mode 100644 index 00000000..49943691 --- /dev/null +++ b/framework/src/onos/utils/stc/src/main/java/org/onlab/stc/StepProcessor.java @@ -0,0 +1,141 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onlab.stc; + +import org.onlab.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; + + 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); + 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); + } + + /** + * 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"); + } + +} diff --git a/framework/src/onos/utils/stc/src/main/java/org/onlab/stc/package-info.java b/framework/src/onos/utils/stc/src/main/java/org/onlab/stc/package-info.java new file mode 100644 index 00000000..56145899 --- /dev/null +++ b/framework/src/onos/utils/stc/src/main/java/org/onlab/stc/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * System Test Coordinator tool for modular scenario-based testing. + */ +package org.onlab.stc;
\ No newline at end of file diff --git a/framework/src/onos/utils/stc/src/main/resources/data.json b/framework/src/onos/utils/stc/src/main/resources/data.json new file mode 100644 index 00000000..f5823744 --- /dev/null +++ b/framework/src/onos/utils/stc/src/main/resources/data.json @@ -0,0 +1,1087 @@ +{ + "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" + } + ] +} diff --git a/framework/src/onos/utils/stc/src/main/resources/index.html b/framework/src/onos/utils/stc/src/main/resources/index.html new file mode 100644 index 00000000..c75bb8f2 --- /dev/null +++ b/framework/src/onos/utils/stc/src/main/resources/index.html @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<!-- + ~ Copyright 2015 Open Networking Laboratory + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<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 diff --git a/framework/src/onos/utils/stc/src/main/resources/stc.css b/framework/src/onos/utils/stc/src/main/resources/stc.css new file mode 100644 index 00000000..8d94253e --- /dev/null +++ b/framework/src/onos/utils/stc/src/main/resources/stc.css @@ -0,0 +1,37 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +.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 diff --git a/framework/src/onos/utils/stc/src/main/resources/stc.js b/framework/src/onos/utils/stc/src/main/resources/stc.js new file mode 100644 index 00000000..215fd6e2 --- /dev/null +++ b/framework/src/onos/utils/stc/src/main/resources/stc.js @@ -0,0 +1,148 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +(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 diff --git a/framework/src/onos/utils/stc/src/test/java/org/onlab/stc/CompilerTest.java b/framework/src/onos/utils/stc/src/test/java/org/onlab/stc/CompilerTest.java new file mode 100644 index 00000000..d70eff08 --- /dev/null +++ b/framework/src/onos/utils/stc/src/test/java/org/onlab/stc/CompilerTest.java @@ -0,0 +1,86 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onlab.stc; + +import com.google.common.io.Files; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; + +import static com.google.common.io.ByteStreams.toByteArray; +import static com.google.common.io.Files.write; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; +import static org.onlab.stc.Scenario.loadScenario; + +/** + * Test of the test scenario compiler. + */ +public class CompilerTest { + + static final File TEST_DIR = Files.createTempDir(); + + @BeforeClass + public static void setUpClass() throws IOException { + 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", TEST_DIR.getAbsolutePath()); + } + + static FileInputStream getStream(String name) throws FileNotFoundException { + return new FileInputStream(new File(TEST_DIR, name)); + } + + static void stageTestResource(String name) throws IOException { + byte[] bytes = toByteArray(CompilerTest.class.getResourceAsStream(name)); + write(bytes, new File(TEST_DIR, name)); + } + + @Test + public void basics() throws Exception { + Scenario scenario = loadScenario(getStream("scenario.xml")); + Compiler compiler = new Compiler(scenario); + compiler.compile(); + ProcessFlow flow = compiler.processFlow(); + + assertSame("incorrect scenario", scenario, compiler.scenario()); + assertEquals("incorrect step count", 24, flow.getVertexes().size()); + assertEquals("incorrect dependency count", 16, flow.getEdges().size()); + assertEquals("incorrect logDir", + new File(TEST_DIR.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 diff --git a/framework/src/onos/utils/stc/src/test/java/org/onlab/stc/CoordinatorTest.java b/framework/src/onos/utils/stc/src/test/java/org/onlab/stc/CoordinatorTest.java new file mode 100644 index 00000000..c6f057ec --- /dev/null +++ b/framework/src/onos/utils/stc/src/test/java/org/onlab/stc/CoordinatorTest.java @@ -0,0 +1,83 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onlab.stc; + +import org.junit.BeforeClass; +import org.junit.Test; +import org.onlab.util.Tools; + +import java.io.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(); + Tools.removeDirectory(StepProcessorTest.DIR); + + StepProcessor.launcher = "true "; + } + + @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 diff --git a/framework/src/onos/utils/stc/src/test/java/org/onlab/stc/DependencyTest.java b/framework/src/onos/utils/stc/src/test/java/org/onlab/stc/DependencyTest.java new file mode 100644 index 00000000..4438303c --- /dev/null +++ b/framework/src/onos/utils/stc/src/test/java/org/onlab/stc/DependencyTest.java @@ -0,0 +1,68 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onlab.stc; + +import com.google.common.testing.EqualsTester; +import org.apache.commons.configuration.ConfigurationException; +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * Test of the test step dependency. + */ +public class DependencyTest extends StepTest { + + protected Step step1, step2; + + @Before + public void setUp() throws ConfigurationException { + super.setUp(); + step1 = new Step("step1", CMD, null, null, null); + step2 = new Step("step2", CMD, null, null, null); + } + + @Test + public void hard() { + Dependency hard = new Dependency(step1, step2, false); + assertSame("incorrect src", step1, hard.src()); + assertSame("incorrect dst", step2, hard.dst()); + assertFalse("incorrect isSoft", hard.isSoft()); + } + + @Test + public void soft() { + Dependency soft = new Dependency(step2, step1, true); + assertSame("incorrect src", step2, soft.src()); + assertSame("incorrect dst", step1, soft.dst()); + assertTrue("incorrect isSoft", soft.isSoft()); + } + + @Test + public void equality() { + Dependency d1 = new Dependency(step1, step2, false); + Dependency d2 = new Dependency(step1, step2, false); + Dependency d3 = new Dependency(step1, step2, true); + Dependency d4 = new Dependency(step2, step1, true); + new EqualsTester() + .addEqualityGroup(d1, d2) + .addEqualityGroup(d3) + .addEqualityGroup(d4) + .testEquals(); + } + +}
\ No newline at end of file diff --git a/framework/src/onos/utils/stc/src/test/java/org/onlab/stc/GroupTest.java b/framework/src/onos/utils/stc/src/test/java/org/onlab/stc/GroupTest.java new file mode 100644 index 00000000..9b612c85 --- /dev/null +++ b/framework/src/onos/utils/stc/src/test/java/org/onlab/stc/GroupTest.java @@ -0,0 +1,54 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onlab.stc; + +import com.google.common.testing.EqualsTester; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; + +/** + * Test of the test scenario entity. + */ +public class GroupTest extends StepTest { + + @Test + public void basics() { + Group group = new Group(NAME, CMD, ENV, CWD, parent); + 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()); + + Step step = new Step("step", null, null, null, group); + group.addChild(step); + assertSame("incorrect child", step, group.children().iterator().next()); + } + + @Test + public void equality() { + Group g1 = new Group(NAME, CMD, null, null, parent); + Group g2 = new Group(NAME, CMD, ENV, CWD, null); + Group g3 = new Group("foo", null, null, null, parent); + new EqualsTester() + .addEqualityGroup(g1, g2) + .addEqualityGroup(g3) + .testEquals(); + } + +}
\ No newline at end of file diff --git a/framework/src/onos/utils/stc/src/test/java/org/onlab/stc/MonitorLayoutTest.java b/framework/src/onos/utils/stc/src/test/java/org/onlab/stc/MonitorLayoutTest.java new file mode 100644 index 00000000..4b7f5614 --- /dev/null +++ b/framework/src/onos/utils/stc/src/test/java/org/onlab/stc/MonitorLayoutTest.java @@ -0,0 +1,146 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onlab.stc; + +import org.junit.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; + + 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 diff --git a/framework/src/onos/utils/stc/src/test/java/org/onlab/stc/ScenarioTest.java b/framework/src/onos/utils/stc/src/test/java/org/onlab/stc/ScenarioTest.java new file mode 100644 index 00000000..2aa51747 --- /dev/null +++ b/framework/src/onos/utils/stc/src/test/java/org/onlab/stc/ScenarioTest.java @@ -0,0 +1,44 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onlab.stc; + +import org.apache.commons.configuration.ConfigurationException; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.onlab.stc.Scenario.loadScenario; + +/** + * Test of the test scenario entity. + */ +public class ScenarioTest { + + @Test + public void basics() throws ConfigurationException { + Scenario scenario = loadScenario(getClass().getResourceAsStream("scenario.xml")); + assertEquals("incorrect name", "foo", scenario.name()); + assertEquals("incorrect description", "Test Scenario", scenario.description()); + assertEquals("incorrect logDir", "Test Scenario", scenario.description()); + assertEquals("incorrect definition", "Test Scenario", + scenario.definition().getString("[@description]")); + } + + @Test(expected = IllegalArgumentException.class) + public void badStream() throws ConfigurationException { + loadScenario(getClass().getResourceAsStream("no.xml")); + } + +}
\ No newline at end of file diff --git a/framework/src/onos/utils/stc/src/test/java/org/onlab/stc/StepProcessorTest.java b/framework/src/onos/utils/stc/src/test/java/org/onlab/stc/StepProcessorTest.java new file mode 100644 index 00000000..74d50241 --- /dev/null +++ b/framework/src/onos/utils/stc/src/test/java/org/onlab/stc/StepProcessorTest.java @@ -0,0 +1,84 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onlab.stc; + +import com.google.common.io.Files; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.onlab.util.Tools; + +import java.io.File; +import java.io.IOException; + +import static 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 { + + static final File DIR = Files.createTempDir(); + private final Listener delegate = new Listener(); + + @BeforeClass + public static void setUpClass() { + StepProcessor.launcher = "echo"; + checkState(DIR.exists() || DIR.mkdirs(), "Unable to create directory"); + } + + @AfterClass + public static void tearDownClass() throws IOException { + Tools.removeDirectory(DIR.getPath()); + } + + @Test + public void basics() { + Step step = new Step("foo", "ls " + DIR.getAbsolutePath(), null, null, null); + 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 diff --git a/framework/src/onos/utils/stc/src/test/java/org/onlab/stc/StepTest.java b/framework/src/onos/utils/stc/src/test/java/org/onlab/stc/StepTest.java new file mode 100644 index 00000000..71083624 --- /dev/null +++ b/framework/src/onos/utils/stc/src/test/java/org/onlab/stc/StepTest.java @@ -0,0 +1,62 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onlab.stc; + +import com.google.common.testing.EqualsTester; +import org.apache.commons.configuration.ConfigurationException; +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; + +/** + * Test of the test step entity. + */ +public class StepTest { + + protected static final String NAME = "step"; + protected static final String CMD = "command"; + protected 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); + } + + @Test + public void basics() { + Step step = new Step(NAME, CMD, ENV, CWD, parent); + 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()); + } + + @Test + public void equality() { + Step s1 = new Step(NAME, CMD, null, null, parent); + Step s2 = new Step(NAME, CMD, ENV, CWD, null); + Step s3 = new Step("foo", null, null, null, parent); + new EqualsTester() + .addEqualityGroup(s1, s2) + .addEqualityGroup(s3) + .testEquals(); + } +}
\ No newline at end of file diff --git a/framework/src/onos/utils/stc/src/test/resources/org/onlab/stc/layout-basic-nest.xml b/framework/src/onos/utils/stc/src/test/resources/org/onlab/stc/layout-basic-nest.xml new file mode 100644 index 00000000..19c48db1 --- /dev/null +++ b/framework/src/onos/utils/stc/src/test/resources/org/onlab/stc/layout-basic-nest.xml @@ -0,0 +1,27 @@ +<!-- + ~ Copyright 2015 Open Networking Laboratory + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<scenario name="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 diff --git a/framework/src/onos/utils/stc/src/test/resources/org/onlab/stc/layout-basic.xml b/framework/src/onos/utils/stc/src/test/resources/org/onlab/stc/layout-basic.xml new file mode 100644 index 00000000..d7dc1383 --- /dev/null +++ b/framework/src/onos/utils/stc/src/test/resources/org/onlab/stc/layout-basic.xml @@ -0,0 +1,25 @@ +<!-- + ~ Copyright 2015 Open Networking Laboratory + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<scenario name="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 diff --git a/framework/src/onos/utils/stc/src/test/resources/org/onlab/stc/layout-deep-nest.xml b/framework/src/onos/utils/stc/src/test/resources/org/onlab/stc/layout-deep-nest.xml new file mode 100644 index 00000000..bbe1ac19 --- /dev/null +++ b/framework/src/onos/utils/stc/src/test/resources/org/onlab/stc/layout-deep-nest.xml @@ -0,0 +1,41 @@ +<!-- + ~ Copyright 2015 Open Networking Laboratory + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<scenario name="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 diff --git a/framework/src/onos/utils/stc/src/test/resources/org/onlab/stc/layout-staggered-dependencies.xml b/framework/src/onos/utils/stc/src/test/resources/org/onlab/stc/layout-staggered-dependencies.xml new file mode 100644 index 00000000..318b4ba1 --- /dev/null +++ b/framework/src/onos/utils/stc/src/test/resources/org/onlab/stc/layout-staggered-dependencies.xml @@ -0,0 +1,30 @@ +<!-- + ~ Copyright 2015 Open Networking Laboratory + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<scenario name="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 diff --git a/framework/src/onos/utils/stc/src/test/resources/org/onlab/stc/one-scenario.xml b/framework/src/onos/utils/stc/src/test/resources/org/onlab/stc/one-scenario.xml new file mode 100644 index 00000000..e5cb6f29 --- /dev/null +++ b/framework/src/onos/utils/stc/src/test/resources/org/onlab/stc/one-scenario.xml @@ -0,0 +1,20 @@ +<!-- + ~ Copyright 2015 Open Networking Laboratory + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<scenario name="one" description="" logDir="/tmp/junit-stc/one"> + <step name="yolo" exec="some-command args"/> + <step name="hello" exec="some-command other args" requires="yolo"/> +</scenario>
\ No newline at end of file diff --git a/framework/src/onos/utils/stc/src/test/resources/org/onlab/stc/scenario.xml b/framework/src/onos/utils/stc/src/test/resources/org/onlab/stc/scenario.xml new file mode 100644 index 00000000..34e67fd5 --- /dev/null +++ b/framework/src/onos/utils/stc/src/test/resources/org/onlab/stc/scenario.xml @@ -0,0 +1,47 @@ +<!-- + ~ Copyright 2015 Open Networking Laboratory + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<scenario name="foo" description="Test Scenario" logDir="${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> +</scenario>
\ No newline at end of file diff --git a/framework/src/onos/utils/stc/src/test/resources/org/onlab/stc/simple-scenario.xml b/framework/src/onos/utils/stc/src/test/resources/org/onlab/stc/simple-scenario.xml new file mode 100644 index 00000000..c70fe872 --- /dev/null +++ b/framework/src/onos/utils/stc/src/test/resources/org/onlab/stc/simple-scenario.xml @@ -0,0 +1,26 @@ +<!-- + ~ Copyright 2015 Open Networking Laboratory + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<scenario name="foo" description="Simple Test Scenario" logDir="/tmp/junit-stc/foo"> + <group name="alpha" exec="same-command args"> + <step name="one" exec="asdads"/> + <step name="two" exec="asdads"/> + <group name="three" exec="asdads" requires="one,two"> + <step name="three.a"/> + <step name="three.b" requires="three.a"/> + <step name="three.c" requires="three.b"/> + </group> + </group> +</scenario>
\ No newline at end of file diff --git a/framework/src/onos/utils/stc/src/test/resources/org/onlab/stc/two-scenario.xml b/framework/src/onos/utils/stc/src/test/resources/org/onlab/stc/two-scenario.xml new file mode 100644 index 00000000..0d6135d5 --- /dev/null +++ b/framework/src/onos/utils/stc/src/test/resources/org/onlab/stc/two-scenario.xml @@ -0,0 +1,21 @@ +<!-- + ~ Copyright 2015 Open Networking Laboratory + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<scenario name="two" description="" logDir="/tmp/junit-stc/two"> + <step name="dude" exec="some-command args"/> + <step name="waz" exec="some-command other args"/> + <step name="up" exec="another-command" requires="dude,waz"/> +</scenario>
\ No newline at end of file |