Added ability for commands to post properties to be used as params of other commands.
Starting to add monitor GUI.
Change-Id: I9fcf1568d0de27dfd1c19e875f8646fd731a1dfa
diff --git a/utils/stc/pom.xml b/utils/stc/pom.xml
index 6785ce9..a3f9643 100644
--- a/utils/stc/pom.xml
+++ b/utils/stc/pom.xml
@@ -53,6 +53,19 @@
</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>
diff --git a/utils/stc/src/main/java/org/onlab/stc/Compiler.java b/utils/stc/src/main/java/org/onlab/stc/Compiler.java
index 2d6fafa..162e8df 100644
--- a/utils/stc/src/main/java/org/onlab/stc/Compiler.java
+++ b/utils/stc/src/main/java/org/onlab/stc/Compiler.java
@@ -61,8 +61,8 @@
private static final String FILE = "[@file]";
private static final String NAMESPACE = "[@namespace]";
- private static final String PROP_START = "${";
- private static final String PROP_END = "}";
+ static final String PROP_START = "${";
+ static final String PROP_END = "}";
private static final String HASH = "#";
private final Scenario scenario;
@@ -230,7 +230,7 @@
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));
+ 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));
@@ -249,7 +249,7 @@
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));
+ 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));
@@ -388,13 +388,14 @@
}
/**
- * Expands any environment variables in the specified
- * string. These are specified as ${property} tokens.
+ * Expands any environment variables in the specified string. These are
+ * specified as ${property} tokens.
*
- * @param string string to be processed
+ * @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) {
+ private String expand(String string, boolean... keepTokens) {
if (string == null) {
return null;
}
@@ -421,7 +422,11 @@
value = System.getenv(prop);
}
}
- sb.append(value != null ? value : "");
+ 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));
diff --git a/utils/stc/src/main/java/org/onlab/stc/Coordinator.java b/utils/stc/src/main/java/org/onlab/stc/Coordinator.java
index a26e9fe..6f79764 100644
--- a/utils/stc/src/main/java/org/onlab/stc/Coordinator.java
+++ b/utils/stc/src/main/java/org/onlab/stc/Coordinator.java
@@ -16,16 +16,24 @@
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.function.Function;
+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.*;
@@ -44,6 +52,10 @@
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 Function<String, String> substitutor = this::substitute;
+
private final Set<StepProcessListener> listeners = Sets.newConcurrentHashSet();
private File logDir;
@@ -208,10 +220,11 @@
store.markStarted(step);
if (step instanceof Group) {
Group group = (Group) step;
- delegate.onStart(group);
+ delegate.onStart(group, null);
executeRoots(group);
} else {
- executor.execute(new StepProcessor(step, logDir, delegate));
+ executor.execute(new StepProcessor(step, logDir, delegate,
+ substitutor));
}
} else if (directive == SKIP) {
if (step instanceof Group) {
@@ -278,6 +291,43 @@
}
/**
+ * 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
@@ -291,10 +341,9 @@
* Internal delegate to monitor the process execution.
*/
private class Delegate implements StepProcessListener {
-
@Override
- public void onStart(Step step) {
- listeners.forEach(listener -> listener.onStart(step));
+ public void onStart(Step step, String command) {
+ listeners.forEach(listener -> listener.onStart(step, command));
}
@Override
@@ -307,9 +356,9 @@
@Override
public void onOutput(Step step, String line) {
+ scrapeForVariables(line);
listeners.forEach(listener -> listener.onOutput(step, line));
}
-
}
}
diff --git a/utils/stc/src/main/java/org/onlab/stc/Main.java b/utils/stc/src/main/java/org/onlab/stc/Main.java
index 01ebe36..310c96e 100644
--- a/utils/stc/src/main/java/org/onlab/stc/Main.java
+++ b/utils/stc/src/main/java/org/onlab/stc/Main.java
@@ -54,6 +54,7 @@
private String runToPatterns = "";
private Coordinator coordinator;
+ private Monitor monitor;
private Listener delegate = new Listener();
private static boolean useColor = Objects.equals("true", System.getenv("stcColor"));
@@ -105,13 +106,16 @@
Compiler compiler = new Compiler(scenario);
compiler.compile();
- // Execute process flow
+ // Setup the process flow coordinator
coordinator = new Coordinator(scenario, compiler.processFlow(),
compiler.logDir());
coordinator.addListener(delegate);
- startMonitorServer();
+ // Prepare the GUI monitor
+ monitor = new Monitor(coordinator, compiler);
+ startMonitorServer(monitor);
+ // Execute process flow
processCommand();
} catch (FileNotFoundException e) {
@@ -120,11 +124,12 @@
}
// Initiates a web-server for the monitor GUI.
- private static void startMonitorServer() {
+ 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();
@@ -187,8 +192,8 @@
*/
private static class Listener implements StepProcessListener {
@Override
- public void onStart(Step step) {
- logStatus(currentTimeMillis(), step.name(), IN_PROGRESS, step.command());
+ public void onStart(Step step, String command) {
+ logStatus(currentTimeMillis(), step.name(), IN_PROGRESS, command);
}
@Override
diff --git a/utils/stc/src/main/java/org/onlab/stc/Monitor.java b/utils/stc/src/main/java/org/onlab/stc/Monitor.java
new file mode 100644
index 0000000..4e6f63f
--- /dev/null
+++ b/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/utils/stc/src/main/java/org/onlab/stc/MonitorDelegate.java b/utils/stc/src/main/java/org/onlab/stc/MonitorDelegate.java
new file mode 100644
index 0000000..d11542a
--- /dev/null
+++ b/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/utils/stc/src/main/java/org/onlab/stc/MonitorLayout.java b/utils/stc/src/main/java/org/onlab/stc/MonitorLayout.java
new file mode 100644
index 0000000..1c0e731
--- /dev/null
+++ b/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/utils/stc/src/main/java/org/onlab/stc/MonitorWebSocket.java b/utils/stc/src/main/java/org/onlab/stc/MonitorWebSocket.java
index 3165dd3..cd14607 100644
--- a/utils/stc/src/main/java/org/onlab/stc/MonitorWebSocket.java
+++ b/utils/stc/src/main/java/org/onlab/stc/MonitorWebSocket.java
@@ -18,24 +18,24 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.eclipse.jetty.websocket.WebSocket;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
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 Logger log = LoggerFactory.getLogger(MonitorWebSocket.class);
-
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;
@@ -44,6 +44,15 @@
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() {
@@ -62,13 +71,12 @@
long quietFor = System.currentTimeMillis() - lastActive;
boolean idle = quietFor > MAX_AGE_MS;
if (idle || (connection != null && !connection.isOpen())) {
- log.debug("IDLE (or closed) websocket [{} ms]", quietFor);
return true;
} else if (connection != null) {
try {
control.sendControl(PING, PING_DATA, 0, PING_DATA.length);
} catch (IOException e) {
- log.warn("Unable to send ping message due to: ", e);
+ print("Unable to send ping message due to: %s", e);
}
}
return false;
@@ -80,10 +88,10 @@
this.control = (FrameConnection) connection;
try {
createHandlers();
- log.info("GUI client connected");
+ sendMessage(message("flow", monitor.scenarioData()));
} catch (Exception e) {
- log.warn("Unable to open monitor connection: {}", e);
+ print("Unable to open monitor connection: %s", e);
this.connection.close();
this.connection = null;
this.control = null;
@@ -93,8 +101,6 @@
@Override
public synchronized void onClose(int closeCode, String message) {
destroyHandlers();
- log.info("GUI client disconnected [close-code={}, message={}]",
- closeCode, message);
}
@Override
@@ -109,10 +115,9 @@
try {
ObjectNode message = (ObjectNode) mapper.reader().readTree(data);
// TODO:
- log.info("Got message: {}", message);
+ print("Got message: %s", message);
} catch (Exception e) {
- log.warn("Unable to parse GUI message {} due to {}", data, e);
- log.debug("Boom!!!", e);
+ print("Unable to parse GUI message %s due to %s", data, e);
}
}
@@ -122,20 +127,14 @@
connection.sendMessage(message.toString());
}
} catch (IOException e) {
- log.warn("Unable to send message {} to GUI due to {}", message, e);
- log.debug("Boom!!!", e);
+ print("Unable to send message %s to GUI due to %s", message, e);
}
}
- public synchronized void sendMessage(String type, long sid, ObjectNode payload) {
- ObjectNode message = mapper.createObjectNode();
- message.put("event", type);
- if (sid > 0) {
- message.put("sid", sid);
- }
+ public ObjectNode message(String type, ObjectNode payload) {
+ ObjectNode message = mapper.createObjectNode().put("event", type);
message.set("payload", payload);
- sendMessage(message);
-
+ return message;
}
// Creates new message handlers.
diff --git a/utils/stc/src/main/java/org/onlab/stc/MonitorWebSocketServlet.java b/utils/stc/src/main/java/org/onlab/stc/MonitorWebSocketServlet.java
index 6796c6b..a870500 100644
--- a/utils/stc/src/main/java/org/onlab/stc/MonitorWebSocketServlet.java
+++ b/utils/stc/src/main/java/org/onlab/stc/MonitorWebSocketServlet.java
@@ -15,6 +15,7 @@
*/
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;
@@ -34,11 +35,13 @@
/**
* Web socket servlet capable of creating web sockets for the STC monitor.
*/
-public class MonitorWebSocketServlet extends WebSocketServlet {
+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<>();
@@ -46,6 +49,15 @@
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() {
@@ -59,7 +71,7 @@
public void init() throws ServletException {
super.init();
instance = this;
- System.out.println("Yo!!!!");
+ monitor.setDelegate(this);
timer.schedule(pruner, PING_DELAY_MS, PING_DELAY_MS);
}
@@ -92,14 +104,20 @@
@Override
public WebSocket doWebSocketConnect(HttpServletRequest request, String protocol) {
- System.out.println("Wazup????");
- MonitorWebSocket socket = new MonitorWebSocket();
+ 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
diff --git a/utils/stc/src/main/java/org/onlab/stc/StepProcessListener.java b/utils/stc/src/main/java/org/onlab/stc/StepProcessListener.java
index 421a606..a8222d0 100644
--- a/utils/stc/src/main/java/org/onlab/stc/StepProcessListener.java
+++ b/utils/stc/src/main/java/org/onlab/stc/StepProcessListener.java
@@ -23,9 +23,10 @@
/**
* Indicates that process step has started.
*
- * @param step subject step
+ * @param step subject step
+ * @param command actual command executed; includes run-time substitutions
*/
- default void onStart(Step step) {
+ default void onStart(Step step, String command) {
}
/**
diff --git a/utils/stc/src/main/java/org/onlab/stc/StepProcessor.java b/utils/stc/src/main/java/org/onlab/stc/StepProcessor.java
index 86315e9..1da9545 100644
--- a/utils/stc/src/main/java/org/onlab/stc/StepProcessor.java
+++ b/utils/stc/src/main/java/org/onlab/stc/StepProcessor.java
@@ -23,6 +23,7 @@
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
+import java.util.function.Function;
import static java.lang.String.format;
import static org.onlab.stc.Coordinator.Status.FAILED;
@@ -41,26 +42,32 @@
private final Step step;
private final File logDir;
+ private String command;
private Process process;
private StepProcessListener delegate;
+ private Function<String, String> substitutor;
/**
* 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 step step or group to be executed
+ * @param logDir directory where step process log should be stored
+ * @param delegate process lifecycle listener
+ * @param substitutor function to substitute var reference in command
*/
- StepProcessor(Step step, File logDir, StepProcessListener delegate) {
+ StepProcessor(Step step, File logDir, StepProcessListener delegate,
+ Function<String, String> substitutor) {
this.step = step;
this.logDir = logDir;
this.delegate = delegate;
+ this.substitutor = substitutor;
}
@Override
public void run() {
- delegate.onStart(step);
+ command = substitutor != null ? substitutor.apply(command()) : command();
+ delegate.onStart(step, command);
int code = execute();
boolean ignoreCode = step.env() != null && step.env.equals(IGNORE_CODE);
Status status = ignoreCode || code == 0 ? SUCCEEDED : FAILED;
@@ -74,7 +81,7 @@
*/
private int execute() {
try (PrintWriter pw = new PrintWriter(logFile())) {
- process = Runtime.getRuntime().exec(command());
+ process = Runtime.getRuntime().exec(command);
processOutput(pw);
// Wait for the process to complete and get its exit code.
diff --git a/utils/stc/src/main/resources/data.json b/utils/stc/src/main/resources/data.json
new file mode 100644
index 0000000..f582374
--- /dev/null
+++ b/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/utils/stc/src/main/resources/index.html b/utils/stc/src/main/resources/index.html
index 5a7cb81..c75bb8f 100644
--- a/utils/stc/src/main/resources/index.html
+++ b/utils/stc/src/main/resources/index.html
@@ -16,14 +16,14 @@
-->
<html>
<head lang="en">
- <meta charset="UTF-8">
+ <meta charset="utf-8">
<title>Scenario Test Coordinator</title>
- <script src="stc.js"></script>
<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>
-<h1>Scenario Test Coordinator</h1>
-
</body>
</html>
\ No newline at end of file
diff --git a/utils/stc/src/main/resources/stc.css b/utils/stc/src/main/resources/stc.css
index a03dfca..8d94253 100644
--- a/utils/stc/src/main/resources/stc.css
+++ b/utils/stc/src/main/resources/stc.css
@@ -15,5 +15,23 @@
*/
.body {
- font-family: Helvetica, Arial;
+ 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/utils/stc/src/main/resources/stc.js b/utils/stc/src/main/resources/stc.js
index fed4272..215fd6e 100644
--- a/utils/stc/src/main/resources/stc.js
+++ b/utils/stc/src/main/resources/stc.js
@@ -15,4 +15,134 @@
*/
(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/utils/stc/src/test/java/org/onlab/stc/CompilerTest.java b/utils/stc/src/test/java/org/onlab/stc/CompilerTest.java
index 4604cf7..d70eff0 100644
--- a/utils/stc/src/test/java/org/onlab/stc/CompilerTest.java
+++ b/utils/stc/src/test/java/org/onlab/stc/CompilerTest.java
@@ -52,11 +52,11 @@
System.setProperty("test.dir", TEST_DIR.getAbsolutePath());
}
- public static FileInputStream getStream(String name) throws FileNotFoundException {
+ static FileInputStream getStream(String name) throws FileNotFoundException {
return new FileInputStream(new File(TEST_DIR, name));
}
- private static void stageTestResource(String name) throws IOException {
+ static void stageTestResource(String name) throws IOException {
byte[] bytes = toByteArray(CompilerTest.class.getResourceAsStream(name));
write(bytes, new File(TEST_DIR, name));
}
diff --git a/utils/stc/src/test/java/org/onlab/stc/CoordinatorTest.java b/utils/stc/src/test/java/org/onlab/stc/CoordinatorTest.java
index c6566ca..c6f057e 100644
--- a/utils/stc/src/test/java/org/onlab/stc/CoordinatorTest.java
+++ b/utils/stc/src/test/java/org/onlab/stc/CoordinatorTest.java
@@ -66,8 +66,8 @@
private class Listener implements StepProcessListener {
@Override
- public void onStart(Step step) {
- print("> %s: started", step.name());
+ public void onStart(Step step, String command) {
+ print("> %s: started; %s", step.name(), command);
}
@Override
diff --git a/utils/stc/src/test/java/org/onlab/stc/MonitorLayoutTest.java b/utils/stc/src/test/java/org/onlab/stc/MonitorLayoutTest.java
new file mode 100644
index 0000000..4b7f561
--- /dev/null
+++ b/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/utils/stc/src/test/java/org/onlab/stc/StepProcessorTest.java b/utils/stc/src/test/java/org/onlab/stc/StepProcessorTest.java
index 2f04a5d..570c96d 100644
--- a/utils/stc/src/test/java/org/onlab/stc/StepProcessorTest.java
+++ b/utils/stc/src/test/java/org/onlab/stc/StepProcessorTest.java
@@ -51,7 +51,7 @@
@Test
public void basics() {
Step step = new Step("foo", "ls " + DIR.getAbsolutePath(), null, null, null);
- StepProcessor processor = new StepProcessor(step, DIR, delegate);
+ StepProcessor processor = new StepProcessor(step, DIR, delegate, null);
processor.run();
assertTrue("should be started", delegate.started);
assertTrue("should be stopped", delegate.stopped);
@@ -65,7 +65,7 @@
private boolean started, stopped, output;
@Override
- public void onStart(Step step) {
+ public void onStart(Step step, String command) {
started = true;
}
diff --git a/utils/stc/src/test/resources/org/onlab/stc/layout-basic-nest.xml b/utils/stc/src/test/resources/org/onlab/stc/layout-basic-nest.xml
new file mode 100644
index 0000000..19c48db
--- /dev/null
+++ b/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/utils/stc/src/test/resources/org/onlab/stc/layout-basic.xml b/utils/stc/src/test/resources/org/onlab/stc/layout-basic.xml
new file mode 100644
index 0000000..d7dc138
--- /dev/null
+++ b/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/utils/stc/src/test/resources/org/onlab/stc/layout-deep-nest.xml b/utils/stc/src/test/resources/org/onlab/stc/layout-deep-nest.xml
new file mode 100644
index 0000000..bbe1ac1
--- /dev/null
+++ b/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/utils/stc/src/test/resources/org/onlab/stc/layout-staggered-dependencies.xml b/utils/stc/src/test/resources/org/onlab/stc/layout-staggered-dependencies.xml
new file mode 100644
index 0000000..318b4ba
--- /dev/null
+++ b/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