Thomas Vachuska
Committed by Gerrit Code Review

Added ability for commands to post properties to be used as params of other commands.

Starting to add monitor GUI.

Change-Id: I9fcf1568d0de27dfd1c19e875f8646fd731a1dfa
......@@ -3,6 +3,8 @@
# System Test Coordinator process launcher
#-------------------------------------------------------------------------------
#sleep 5 && exit 0;
env=$1 && shift
cwd=$1 && shift
......
<!--
~ 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="example" description="Example">
<step name="One" exec="echo @stc foo=bar"/>
<step name="Two" requires="One" exec="echo ${foo}"/>
</scenario>
......@@ -28,7 +28,7 @@
<step name="Check-Summary-For-Hosts" requires="~Ping-All-And-Verify"
exec="onos-check-summary ${OC1} [0-9]* 25 140 25"/>
<step name="Config-Topo" requires="Check-Summary-For-Hosts"
<step name="Config-Topo" requires="~Check-Summary-For-Hosts"
exec="onos-topo-cfg ${OC1} ${ONOS_ROOT}/tools/test/topos/attmpls.json"/>
</group>
</scenario>
\ No newline at end of file
......
......@@ -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>
......
......@@ -61,8 +61,8 @@ public class Compiler {
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 @@ public class Compiler {
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 @@ public class Compiler {
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 @@ public class Compiler {
}
/**
* 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 @@ public class Compiler {
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));
......
......@@ -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 @@ public class Coordinator {
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 @@ public class Coordinator {
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 @@ public class Coordinator {
}
/**
* 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 @@ public class Coordinator {
* 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 @@ public class Coordinator {
@Override
public void onOutput(Step step, String line) {
scrapeForVariables(line);
listeners.forEach(listener -> listener.onOutput(step, line));
}
}
}
......
......@@ -54,6 +54,7 @@ public final class Main {
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 @@ public final class Main {
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 @@ public final class Main {
}
// 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 @@ public final class Main {
*/
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
......
/*
* 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;
}
}
/*
* 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);
}
/*
* 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;
}
}
}
......@@ -18,24 +18,24 @@ package org.onlab.stc;
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 @@ public class MonitorWebSocket implements WebSocket.OnTextMessage, WebSocket.OnCo
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 @@ public class MonitorWebSocket implements WebSocket.OnTextMessage, WebSocket.OnCo
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 @@ public class MonitorWebSocket implements WebSocket.OnTextMessage, WebSocket.OnCo
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 @@ public class MonitorWebSocket implements WebSocket.OnTextMessage, WebSocket.OnCo
@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 @@ public class MonitorWebSocket implements WebSocket.OnTextMessage, WebSocket.OnCo
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 @@ public class MonitorWebSocket implements WebSocket.OnTextMessage, WebSocket.OnCo
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.
......
......@@ -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 @@ import java.util.TimerTask;
/**
* 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 @@ public class MonitorWebSocketServlet extends WebSocketServlet {
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 class MonitorWebSocketServlet extends WebSocketServlet {
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 @@ public class MonitorWebSocketServlet extends WebSocketServlet {
@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
......
......@@ -23,9 +23,10 @@ public interface StepProcessListener {
/**
* 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) {
}
/**
......
......@@ -23,6 +23,7 @@ import java.io.IOException;
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 @@ class StepProcessor implements Runnable {
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 @@ class StepProcessor implements Runnable {
*/
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.
......
{
"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"
}
]
}
......@@ -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
......
......@@ -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
......
......@@ -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
......
......@@ -52,11 +52,11 @@ public class CompilerTest {
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));
}
......
......@@ -66,8 +66,8 @@ public class CoordinatorTest {
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
......
/*
* 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
......@@ -51,7 +51,7 @@ public class StepProcessorTest {
@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 @@ public class StepProcessorTest {
private boolean started, stopped, output;
@Override
public void onStart(Step step) {
public void onStart(Step step, String command) {
started = true;
}
......
<!--
~ 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
<!--
~ 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
<!--
~ 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
<!--
~ 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