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 @@ ...@@ -3,6 +3,8 @@
3 # System Test Coordinator process launcher 3 # System Test Coordinator process launcher
4 #------------------------------------------------------------------------------- 4 #-------------------------------------------------------------------------------
5 5
6 +#sleep 5 && exit 0;
7 +
6 env=$1 && shift 8 env=$1 && shift
7 cwd=$1 && shift 9 cwd=$1 && shift
8 10
......
1 +<!--
2 + ~ Copyright 2015 Open Networking Laboratory
3 + ~
4 + ~ Licensed under the Apache License, Version 2.0 (the "License");
5 + ~ you may not use this file except in compliance with the License.
6 + ~ You may obtain a copy of the License at
7 + ~
8 + ~ http://www.apache.org/licenses/LICENSE-2.0
9 + ~
10 + ~ Unless required by applicable law or agreed to in writing, software
11 + ~ distributed under the License is distributed on an "AS IS" BASIS,
12 + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + ~ See the License for the specific language governing permissions and
14 + ~ limitations under the License.
15 + -->
16 +<scenario name="example" description="Example">
17 + <step name="One" exec="echo @stc foo=bar"/>
18 + <step name="Two" requires="One" exec="echo ${foo}"/>
19 +</scenario>
...@@ -28,7 +28,7 @@ ...@@ -28,7 +28,7 @@
28 <step name="Check-Summary-For-Hosts" requires="~Ping-All-And-Verify" 28 <step name="Check-Summary-For-Hosts" requires="~Ping-All-And-Verify"
29 exec="onos-check-summary ${OC1} [0-9]* 25 140 25"/> 29 exec="onos-check-summary ${OC1} [0-9]* 25 140 25"/>
30 30
31 - <step name="Config-Topo" requires="Check-Summary-For-Hosts" 31 + <step name="Config-Topo" requires="~Check-Summary-For-Hosts"
32 exec="onos-topo-cfg ${OC1} ${ONOS_ROOT}/tools/test/topos/attmpls.json"/> 32 exec="onos-topo-cfg ${OC1} ${ONOS_ROOT}/tools/test/topos/attmpls.json"/>
33 </group> 33 </group>
34 </scenario> 34 </scenario>
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -53,6 +53,19 @@ ...@@ -53,6 +53,19 @@
53 </dependency> 53 </dependency>
54 54
55 <dependency> 55 <dependency>
56 + <groupId>com.fasterxml.jackson.core</groupId>
57 + <artifactId>jackson-databind</artifactId>
58 + <version>2.4.2</version>
59 + <scope>compile</scope>
60 + </dependency>
61 + <dependency>
62 + <groupId>com.fasterxml.jackson.core</groupId>
63 + <artifactId>jackson-annotations</artifactId>
64 + <version>2.4.2</version>
65 + <scope>compile</scope>
66 + </dependency>
67 +
68 + <dependency>
56 <groupId>org.eclipse.jetty</groupId> 69 <groupId>org.eclipse.jetty</groupId>
57 <artifactId>jetty-server</artifactId> 70 <artifactId>jetty-server</artifactId>
58 <version>8.1.17.v20150415</version> 71 <version>8.1.17.v20150415</version>
......
...@@ -61,8 +61,8 @@ public class Compiler { ...@@ -61,8 +61,8 @@ public class Compiler {
61 private static final String FILE = "[@file]"; 61 private static final String FILE = "[@file]";
62 private static final String NAMESPACE = "[@namespace]"; 62 private static final String NAMESPACE = "[@namespace]";
63 63
64 - private static final String PROP_START = "${"; 64 + static final String PROP_START = "${";
65 - private static final String PROP_END = "}"; 65 + static final String PROP_END = "}";
66 private static final String HASH = "#"; 66 private static final String HASH = "#";
67 67
68 private final Scenario scenario; 68 private final Scenario scenario;
...@@ -230,7 +230,7 @@ public class Compiler { ...@@ -230,7 +230,7 @@ public class Compiler {
230 private void processStep(HierarchicalConfiguration cfg, 230 private void processStep(HierarchicalConfiguration cfg,
231 String namespace, Group parentGroup) { 231 String namespace, Group parentGroup) {
232 String name = expand(prefix(cfg.getString(NAME), namespace)); 232 String name = expand(prefix(cfg.getString(NAME), namespace));
233 - String command = expand(cfg.getString(COMMAND, parentGroup != null ? parentGroup.command() : null)); 233 + String command = expand(cfg.getString(COMMAND, parentGroup != null ? parentGroup.command() : null), true);
234 String env = expand(cfg.getString(ENV, parentGroup != null ? parentGroup.env() : null)); 234 String env = expand(cfg.getString(ENV, parentGroup != null ? parentGroup.env() : null));
235 String cwd = expand(cfg.getString(CWD, parentGroup != null ? parentGroup.cwd() : null)); 235 String cwd = expand(cfg.getString(CWD, parentGroup != null ? parentGroup.cwd() : null));
236 236
...@@ -249,7 +249,7 @@ public class Compiler { ...@@ -249,7 +249,7 @@ public class Compiler {
249 private void processGroup(HierarchicalConfiguration cfg, 249 private void processGroup(HierarchicalConfiguration cfg,
250 String namespace, Group parentGroup) { 250 String namespace, Group parentGroup) {
251 String name = expand(prefix(cfg.getString(NAME), namespace)); 251 String name = expand(prefix(cfg.getString(NAME), namespace));
252 - String command = expand(cfg.getString(COMMAND, parentGroup != null ? parentGroup.command() : null)); 252 + String command = expand(cfg.getString(COMMAND, parentGroup != null ? parentGroup.command() : null), true);
253 String env = expand(cfg.getString(ENV, parentGroup != null ? parentGroup.env() : null)); 253 String env = expand(cfg.getString(ENV, parentGroup != null ? parentGroup.env() : null));
254 String cwd = expand(cfg.getString(CWD, parentGroup != null ? parentGroup.cwd() : null)); 254 String cwd = expand(cfg.getString(CWD, parentGroup != null ? parentGroup.cwd() : null));
255 255
...@@ -388,13 +388,14 @@ public class Compiler { ...@@ -388,13 +388,14 @@ public class Compiler {
388 } 388 }
389 389
390 /** 390 /**
391 - * Expands any environment variables in the specified 391 + * Expands any environment variables in the specified string. These are
392 - * string. These are specified as ${property} tokens. 392 + * specified as ${property} tokens.
393 * 393 *
394 - * @param string string to be processed 394 + * @param string string to be processed
395 + * @param keepTokens true if the original unresolved tokens should be kept
395 * @return original string with expanded substitutions 396 * @return original string with expanded substitutions
396 */ 397 */
397 - private String expand(String string) { 398 + private String expand(String string, boolean... keepTokens) {
398 if (string == null) { 399 if (string == null) {
399 return null; 400 return null;
400 } 401 }
...@@ -421,7 +422,11 @@ public class Compiler { ...@@ -421,7 +422,11 @@ public class Compiler {
421 value = System.getenv(prop); 422 value = System.getenv(prop);
422 } 423 }
423 } 424 }
424 - sb.append(value != null ? value : ""); 425 + if (value == null && keepTokens.length == 1 && keepTokens[0]) {
426 + sb.append("${").append(prop).append("}");
427 + } else {
428 + sb.append(value != null ? value : "");
429 + }
425 last = end + 1; 430 last = end + 1;
426 } 431 }
427 sb.append(pString.substring(last)); 432 sb.append(pString.substring(last));
......
...@@ -16,16 +16,24 @@ ...@@ -16,16 +16,24 @@
16 package org.onlab.stc; 16 package org.onlab.stc;
17 17
18 import com.google.common.collect.ImmutableList; 18 import com.google.common.collect.ImmutableList;
19 +import com.google.common.collect.Maps;
19 import com.google.common.collect.Sets; 20 import com.google.common.collect.Sets;
20 21
21 import java.io.File; 22 import java.io.File;
22 import java.util.List; 23 import java.util.List;
24 +import java.util.Map;
23 import java.util.Set; 25 import java.util.Set;
24 import java.util.concurrent.CountDownLatch; 26 import java.util.concurrent.CountDownLatch;
25 import java.util.concurrent.ExecutorService; 27 import java.util.concurrent.ExecutorService;
28 +import java.util.function.Function;
29 +import java.util.regex.Matcher;
30 +import java.util.regex.Pattern;
26 31
32 +import static com.google.common.base.Preconditions.checkArgument;
27 import static com.google.common.base.Preconditions.checkNotNull; 33 import static com.google.common.base.Preconditions.checkNotNull;
28 import static java.util.concurrent.Executors.newFixedThreadPool; 34 import static java.util.concurrent.Executors.newFixedThreadPool;
35 +import static org.onlab.stc.Compiler.PROP_END;
36 +import static org.onlab.stc.Compiler.PROP_START;
29 import static org.onlab.stc.Coordinator.Directive.*; 37 import static org.onlab.stc.Coordinator.Directive.*;
30 import static org.onlab.stc.Coordinator.Status.*; 38 import static org.onlab.stc.Coordinator.Status.*;
31 39
...@@ -44,6 +52,10 @@ public class Coordinator { ...@@ -44,6 +52,10 @@ public class Coordinator {
44 private final CountDownLatch latch; 52 private final CountDownLatch latch;
45 private final ScenarioStore store; 53 private final ScenarioStore store;
46 54
55 + private static final Pattern PROP_ERE = Pattern.compile("^@stc ([a-zA-Z0-9_.]+)=(.*$)");
56 + private final Map<String, String> properties = Maps.newConcurrentMap();
57 + private final Function<String, String> substitutor = this::substitute;
58 +
47 private final Set<StepProcessListener> listeners = Sets.newConcurrentHashSet(); 59 private final Set<StepProcessListener> listeners = Sets.newConcurrentHashSet();
48 private File logDir; 60 private File logDir;
49 61
...@@ -208,10 +220,11 @@ public class Coordinator { ...@@ -208,10 +220,11 @@ public class Coordinator {
208 store.markStarted(step); 220 store.markStarted(step);
209 if (step instanceof Group) { 221 if (step instanceof Group) {
210 Group group = (Group) step; 222 Group group = (Group) step;
211 - delegate.onStart(group); 223 + delegate.onStart(group, null);
212 executeRoots(group); 224 executeRoots(group);
213 } else { 225 } else {
214 - executor.execute(new StepProcessor(step, logDir, delegate)); 226 + executor.execute(new StepProcessor(step, logDir, delegate,
227 + substitutor));
215 } 228 }
216 } else if (directive == SKIP) { 229 } else if (directive == SKIP) {
217 if (step instanceof Group) { 230 if (step instanceof Group) {
...@@ -278,6 +291,43 @@ public class Coordinator { ...@@ -278,6 +291,43 @@ public class Coordinator {
278 } 291 }
279 292
280 /** 293 /**
294 + * Expands the var references with values from the properties map.
295 + *
296 + * @param string string to perform substitutions on
297 + */
298 + private String substitute(String string) {
299 + StringBuilder sb = new StringBuilder();
300 + int start, end, last = 0;
301 + while ((start = string.indexOf(PROP_START, last)) >= 0) {
302 + end = string.indexOf(PROP_END, start + PROP_START.length());
303 + checkArgument(end > start, "Malformed property in %s", string);
304 + sb.append(string.substring(last, start));
305 + String prop = string.substring(start + PROP_START.length(), end);
306 + String value = properties.get(prop);
307 + sb.append(value != null ? value : "");
308 + last = end + 1;
309 + }
310 + sb.append(string.substring(last));
311 + return sb.toString().replace('\n', ' ').replace('\r', ' ');
312 + }
313 +
314 + /**
315 + * Scrapes the line of output for any variables to be captured and posted
316 + * in the properties for later use.
317 + *
318 + * @param line line of output to scrape for property exports
319 + */
320 + private void scrapeForVariables(String line) {
321 + Matcher matcher = PROP_ERE.matcher(line);
322 + if (matcher.matches()) {
323 + String prop = matcher.group(1);
324 + String value = matcher.group(2);
325 + properties.put(prop, value);
326 + }
327 + }
328 +
329 +
330 + /**
281 * Prints formatted output. 331 * Prints formatted output.
282 * 332 *
283 * @param format printf format string 333 * @param format printf format string
...@@ -291,10 +341,9 @@ public class Coordinator { ...@@ -291,10 +341,9 @@ public class Coordinator {
291 * Internal delegate to monitor the process execution. 341 * Internal delegate to monitor the process execution.
292 */ 342 */
293 private class Delegate implements StepProcessListener { 343 private class Delegate implements StepProcessListener {
294 -
295 @Override 344 @Override
296 - public void onStart(Step step) { 345 + public void onStart(Step step, String command) {
297 - listeners.forEach(listener -> listener.onStart(step)); 346 + listeners.forEach(listener -> listener.onStart(step, command));
298 } 347 }
299 348
300 @Override 349 @Override
...@@ -307,9 +356,9 @@ public class Coordinator { ...@@ -307,9 +356,9 @@ public class Coordinator {
307 356
308 @Override 357 @Override
309 public void onOutput(Step step, String line) { 358 public void onOutput(Step step, String line) {
359 + scrapeForVariables(line);
310 listeners.forEach(listener -> listener.onOutput(step, line)); 360 listeners.forEach(listener -> listener.onOutput(step, line));
311 } 361 }
312 -
313 } 362 }
314 363
315 } 364 }
......
...@@ -54,6 +54,7 @@ public final class Main { ...@@ -54,6 +54,7 @@ public final class Main {
54 private String runToPatterns = ""; 54 private String runToPatterns = "";
55 55
56 private Coordinator coordinator; 56 private Coordinator coordinator;
57 + private Monitor monitor;
57 private Listener delegate = new Listener(); 58 private Listener delegate = new Listener();
58 59
59 private static boolean useColor = Objects.equals("true", System.getenv("stcColor")); 60 private static boolean useColor = Objects.equals("true", System.getenv("stcColor"));
...@@ -105,13 +106,16 @@ public final class Main { ...@@ -105,13 +106,16 @@ public final class Main {
105 Compiler compiler = new Compiler(scenario); 106 Compiler compiler = new Compiler(scenario);
106 compiler.compile(); 107 compiler.compile();
107 108
108 - // Execute process flow 109 + // Setup the process flow coordinator
109 coordinator = new Coordinator(scenario, compiler.processFlow(), 110 coordinator = new Coordinator(scenario, compiler.processFlow(),
110 compiler.logDir()); 111 compiler.logDir());
111 coordinator.addListener(delegate); 112 coordinator.addListener(delegate);
112 113
113 - startMonitorServer(); 114 + // Prepare the GUI monitor
115 + monitor = new Monitor(coordinator, compiler);
116 + startMonitorServer(monitor);
114 117
118 + // Execute process flow
115 processCommand(); 119 processCommand();
116 120
117 } catch (FileNotFoundException e) { 121 } catch (FileNotFoundException e) {
...@@ -120,11 +124,12 @@ public final class Main { ...@@ -120,11 +124,12 @@ public final class Main {
120 } 124 }
121 125
122 // Initiates a web-server for the monitor GUI. 126 // Initiates a web-server for the monitor GUI.
123 - private static void startMonitorServer() { 127 + private static void startMonitorServer(Monitor monitor) {
124 org.eclipse.jetty.util.log.Log.setLog(new NullLogger()); 128 org.eclipse.jetty.util.log.Log.setLog(new NullLogger());
125 Server server = new Server(9999); 129 Server server = new Server(9999);
126 ServletHandler handler = new ServletHandler(); 130 ServletHandler handler = new ServletHandler();
127 server.setHandler(handler); 131 server.setHandler(handler);
132 + MonitorWebSocketServlet.setMonitor(monitor);
128 handler.addServletWithMapping(MonitorWebSocketServlet.class, "/*"); 133 handler.addServletWithMapping(MonitorWebSocketServlet.class, "/*");
129 try { 134 try {
130 server.start(); 135 server.start();
...@@ -187,8 +192,8 @@ public final class Main { ...@@ -187,8 +192,8 @@ public final class Main {
187 */ 192 */
188 private static class Listener implements StepProcessListener { 193 private static class Listener implements StepProcessListener {
189 @Override 194 @Override
190 - public void onStart(Step step) { 195 + public void onStart(Step step, String command) {
191 - logStatus(currentTimeMillis(), step.name(), IN_PROGRESS, step.command()); 196 + logStatus(currentTimeMillis(), step.name(), IN_PROGRESS, command);
192 } 197 }
193 198
194 @Override 199 @Override
......
1 +/*
2 + * Copyright 2015 Open Networking Laboratory
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + */
16 +package org.onlab.stc;
17 +
18 +import com.fasterxml.jackson.databind.ObjectMapper;
19 +import com.fasterxml.jackson.databind.node.ArrayNode;
20 +import com.fasterxml.jackson.databind.node.ObjectNode;
21 +import com.google.common.collect.Maps;
22 +import org.onlab.stc.MonitorLayout.Box;
23 +
24 +import java.io.FileWriter;
25 +import java.io.IOException;
26 +import java.io.PrintWriter;
27 +import java.util.Map;
28 +
29 +import static org.onlab.stc.Coordinator.Status.IN_PROGRESS;
30 +
31 +/**
32 + * Scenario test monitor.
33 + */
34 +public class Monitor implements StepProcessListener {
35 +
36 + private final ObjectMapper mapper = new ObjectMapper();
37 +
38 + private final Coordinator coordinator;
39 + private final Compiler compiler;
40 + private final MonitorLayout layout;
41 +
42 + private MonitorDelegate delegate;
43 +
44 + private Map<Step, Box> boxes = Maps.newHashMap();
45 +
46 + /**
47 + * Creates a new shared process flow monitor.
48 + *
49 + * @param coordinator process flow coordinator
50 + * @param compiler scenario compiler
51 + */
52 + Monitor(Coordinator coordinator, Compiler compiler) {
53 + this.coordinator = coordinator;
54 + this.compiler = compiler;
55 + this.layout = new MonitorLayout(compiler);
56 + coordinator.addListener(this);
57 + }
58 +
59 + /**
60 + * Sets the process monitor delegate.
61 + *
62 + * @param delegate process monitor delegate
63 + */
64 + void setDelegate(MonitorDelegate delegate) {
65 + this.delegate = delegate;
66 + }
67 +
68 + /**
69 + * Notifies the process monitor delegate with the specified event.
70 + *
71 + * @param event JSON event data
72 + */
73 + public void notify(ObjectNode event) {
74 + if (delegate != null) {
75 + delegate.notify(event);
76 + }
77 + }
78 +
79 + /**
80 + * Returns the scenario process flow as JSON data.
81 + *
82 + * @return scenario process flow data
83 + */
84 + ObjectNode scenarioData() {
85 + ObjectNode root = mapper.createObjectNode();
86 + ArrayNode steps = mapper.createArrayNode();
87 + ArrayNode requirements = mapper.createArrayNode();
88 +
89 + ProcessFlow pf = compiler.processFlow();
90 + pf.getVertexes().forEach(step -> add(step, steps));
91 + pf.getEdges().forEach(requirement -> add(requirement, requirements));
92 +
93 + root.set("steps", steps);
94 + root.set("requirements", requirements);
95 +
96 + try (FileWriter fw = new FileWriter("/tmp/data.json");
97 + PrintWriter pw = new PrintWriter(fw)) {
98 + pw.println(root.toString());
99 + } catch (IOException e) {
100 + e.printStackTrace();
101 + }
102 + return root;
103 + }
104 +
105 +
106 + private void add(Step step, ArrayNode steps) {
107 + Box box = layout.get(step);
108 + ObjectNode sn = mapper.createObjectNode()
109 + .put("name", step.name())
110 + .put("isGroup", step instanceof Group)
111 + .put("status", status(coordinator.getStatus(step)))
112 + .put("tier", box.tier())
113 + .put("depth", box.depth());
114 + if (step.group() != null) {
115 + sn.put("group", step.group().name());
116 + }
117 + steps.add(sn);
118 + }
119 +
120 + private String status(Coordinator.Status status) {
121 + return status.toString().toLowerCase();
122 + }
123 +
124 + private void add(Dependency requirement, ArrayNode requirements) {
125 + ObjectNode rn = mapper.createObjectNode();
126 + rn.put("src", requirement.src().name())
127 + .put("dst", requirement.dst().name())
128 + .put("isSoft", requirement.isSoft());
129 + requirements.add(rn);
130 + }
131 +
132 + @Override
133 + public void onStart(Step step, String command) {
134 + notify(event(step, status(IN_PROGRESS)));
135 + }
136 +
137 + @Override
138 + public void onCompletion(Step step, Coordinator.Status status) {
139 + notify(event(step, status(status)));
140 + }
141 +
142 + @Override
143 + public void onOutput(Step step, String line) {
144 +
145 + }
146 +
147 + private ObjectNode event(Step step, String status) {
148 + ObjectNode event = mapper.createObjectNode()
149 + .put("name", step.name())
150 + .put("status", status);
151 + return event;
152 + }
153 +
154 +}
1 +/*
2 + * Copyright 2015 Open Networking Laboratory
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + */
16 +package org.onlab.stc;
17 +
18 +import com.fasterxml.jackson.databind.node.ObjectNode;
19 +
20 +/**
21 + * Delegate to which monitor can send notifications.
22 + */
23 +public interface MonitorDelegate {
24 +
25 + /**
26 + * Issues JSON event to be sent to any connected monitor clients.
27 + *
28 + * @param event JSON event data
29 + */
30 + void notify(ObjectNode event);
31 +}
1 +/*
2 + * Copyright 2015 Open Networking Laboratory
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + */
16 +package org.onlab.stc;
17 +
18 +import com.google.common.collect.Lists;
19 +import com.google.common.collect.Maps;
20 +
21 +import java.util.List;
22 +import java.util.Map;
23 +import java.util.Set;
24 +import java.util.stream.Collectors;
25 +import java.util.stream.IntStream;
26 +
27 +/**
28 + * Computes scenario process flow layout for the Monitor GUI.
29 + */
30 +public class MonitorLayout {
31 +
32 + public static final int WIDTH = 210;
33 + public static final int HEIGHT = 30;
34 + public static final int W_GAP = 40;
35 + public static final int H_GAP = 50;
36 + public static final int SLOT_WIDTH = WIDTH + H_GAP;
37 +
38 + private final Compiler compiler;
39 + private final ProcessFlow flow;
40 +
41 + private Map<Step, Box> boxes = Maps.newHashMap();
42 +
43 + /**
44 + * Creates a new shared process flow monitor.
45 + *
46 + * @param compiler scenario compiler
47 + */
48 + MonitorLayout(Compiler compiler) {
49 + this.compiler = compiler;
50 + this.flow = compiler.processFlow();
51 +
52 + // Extract the flow and create initial bounding boxes.
53 + boxes.put(null, new Box(null, 0));
54 + flow.getVertexes().forEach(this::createBox);
55 +
56 + computeLayout(null, 0, 1);
57 + }
58 +
59 + // Computes the graph layout giving preference to group associations.
60 + private void computeLayout(Group group, int absoluteTier, int tier) {
61 + Box box = boxes.get(group);
62 +
63 + // Find all children of the group, or items with no group if at top.
64 + Set<Step> children = group != null ? group.children() :
65 + flow.getVertexes().stream().filter(s -> s.group() == null)
66 + .collect(Collectors.toSet());
67 +
68 + children.forEach(s -> visit(s, absoluteTier, 1, group));
69 +
70 + // Figure out what the group root vertexes are.
71 + Set<Step> roots = findRoots(group);
72 +
73 + // Compute the boxes for each of the roots.
74 + roots.forEach(s -> updateBox(s, absoluteTier + 1, 1, group));
75 +
76 + // Update the tier and depth of the group bounding box.
77 + computeTiersAndDepth(group, box, absoluteTier, tier, children);
78 +
79 + // Compute the minimum breadth of this group's bounding box.
80 + computeBreadth(group, box, children);
81 +
82 + // Compute child placements
83 + computeChildPlacements(group, box, children);
84 + }
85 +
86 + // Updates the box for the specified step, given the tier number, which
87 + // is relative to the parent.
88 + private Box updateBox(Step step, int absoluteTier, int tier, Group group) {
89 + Box box = boxes.get(step);
90 + if (step instanceof Group) {
91 + computeLayout((Group) step, absoluteTier, tier);
92 + } else {
93 + box.setTierAndDepth(absoluteTier, tier, 1, group);
94 + }
95 +
96 + // Follow the steps downstream of this one.
97 + follow(step, absoluteTier + box.depth(), box.tier() + box.depth());
98 + return box;
99 + }
100 +
101 + // Backwards follows edges leading towards the specified step to visit
102 + // the source vertex and compute layout of those vertices that had
103 + // sufficient number of visits to compute their tier.
104 + private void follow(Step step, int absoluteTier, int tier) {
105 + Group from = step.group();
106 + flow.getEdgesTo(step).stream()
107 + .filter(d -> visit(d.src(), absoluteTier, tier, from))
108 + .forEach(d -> updateBox(d.src(), absoluteTier, tier, from));
109 + }
110 +
111 + // Visits each step, records maximum tier and returns true if this
112 + // was the last expected visit.
113 + private boolean visit(Step step, int absoluteTier, int tier, Group from) {
114 + Box box = boxes.get(step);
115 + return box.visitAndLatchMaxTier(absoluteTier, tier, from);
116 + }
117 +
118 + // Computes the absolute and relative tiers and the depth of the group
119 + // bounding box.
120 + private void computeTiersAndDepth(Group group, Box box,
121 + int absoluteTier, int tier, Set<Step> children) {
122 + int depth = children.stream().mapToInt(this::bottomMostTier).max().getAsInt();
123 + box.setTierAndDepth(absoluteTier, tier, depth, group);
124 + }
125 +
126 + // Returns the bottom-most tier this step occupies relative to its parent.
127 + private int bottomMostTier(Step step) {
128 + Box box = boxes.get(step);
129 + return box.tier() + box.depth();
130 + }
131 +
132 + // Computes breadth of the specified group.
133 + private void computeBreadth(Group group, Box box, Set<Step> children) {
134 + if (box.breadth() == 0) {
135 + // Scan through all tiers and determine the maximum breadth of each.
136 + IntStream.range(1, box.depth)
137 + .forEach(t -> computeTierBreadth(t, box, children));
138 + box.latchBreadth(children.stream()
139 + .mapToInt(s -> boxes.get(s).breadth())
140 + .max().getAsInt());
141 + }
142 + }
143 +
144 + // Computes tier width.
145 + private void computeTierBreadth(int t, Box box, Set<Step> children) {
146 + box.latchBreadth(children.stream().map(boxes::get)
147 + .filter(b -> isSpanningTier(b, t))
148 + .mapToInt(Box::breadth).sum());
149 + }
150 +
151 + // Computes the actual child box placements relative to the parent using
152 + // the previously established tier, depth and breadth attributes.
153 + private void computeChildPlacements(Group group, Box box,
154 + Set<Step> children) {
155 + // Order the root-nodes in alphanumeric order first.
156 + List<Box> tierBoxes = Lists.newArrayList(boxesOnTier(1, children));
157 + tierBoxes.sort((a, b) -> a.step().name().compareTo(b.step().name()));
158 +
159 + // Place the boxes centered on the parent box; left to right.
160 + int tierBreadth = tierBoxes.stream().mapToInt(Box::breadth).sum();
161 + int slot = 1;
162 + for (Box b : tierBoxes) {
163 + b.updateCenter(1, slot(slot, tierBreadth));
164 + slot += b.breadth();
165 + }
166 + }
167 +
168 + // Returns the horizontal offset off the parent center.
169 + private int slot(int slot, int tierBreadth) {
170 + boolean even = tierBreadth % 2 == 0;
171 + int multiplier = -tierBreadth / 2 + slot - 1;
172 + return even ? multiplier * SLOT_WIDTH + SLOT_WIDTH / 2 : multiplier * SLOT_WIDTH;
173 + }
174 +
175 + // Returns a list of all child step boxes that start on the specified tier.
176 + private List<Box> boxesOnTier(int tier, Set<Step> children) {
177 + return boxes.values().stream()
178 + .filter(b -> b.tier() == tier && children.contains(b.step()))
179 + .collect(Collectors.toList());
180 + }
181 +
182 + // Determines whether the specified box spans, or occupies a tier.
183 + private boolean isSpanningTier(Box b, int tier) {
184 + return (b.depth() == 1 && b.tier() == tier) ||
185 + (b.tier() <= tier && tier < b.tier() + b.depth());
186 + }
187 +
188 +
189 + // Determines roots of the specified group or of the entire graph.
190 + private Set<Step> findRoots(Group group) {
191 + Set<Step> steps = group != null ? group.children() : flow.getVertexes();
192 + return steps.stream().filter(s -> isRoot(s, group)).collect(Collectors.toSet());
193 + }
194 +
195 + private boolean isRoot(Step step, Group group) {
196 + if (step.group() != group) {
197 + return false;
198 + }
199 +
200 + Set<Dependency> requirements = flow.getEdgesFrom(step);
201 + return requirements.stream().filter(r -> r.dst().group() == group)
202 + .collect(Collectors.toSet()).isEmpty();
203 + }
204 +
205 + /**
206 + * Returns the bounding box for the specified step. If null is given, it
207 + * returns the overall bounding box.
208 + *
209 + * @param step step or group; null for the overall bounding box
210 + * @return bounding box
211 + */
212 + public Box get(Step step) {
213 + return boxes.get(step);
214 + }
215 +
216 + /**
217 + * Returns the bounding box for the specified step name. If null is given,
218 + * it returns the overall bounding box.
219 + *
220 + * @param name name of step or group; null for the overall bounding box
221 + * @return bounding box
222 + */
223 + public Box get(String name) {
224 + return get(name == null ? null : compiler.getStep(name));
225 + }
226 +
227 + // Creates a bounding box for the specified step or group.
228 + private void createBox(Step step) {
229 + boxes.put(step, new Box(step, flow.getEdgesFrom(step).size()));
230 + }
231 +
232 + /**
233 + * Bounding box data for a step or group.
234 + */
235 + final class Box {
236 +
237 + private Step step;
238 + private int remainingRequirements;
239 +
240 + private int absoluteTier = 0;
241 + private int tier;
242 + private int depth = 1;
243 + private int breadth;
244 + private int center, top;
245 +
246 + private Box(Step step, int remainingRequirements) {
247 + this.step = step;
248 + this.remainingRequirements = remainingRequirements + 1;
249 + breadth = step == null || step instanceof Group ? 0 : 1;
250 + }
251 +
252 + private void latchTiers(int absoluteTier, int tier, Group from) {
253 + this.absoluteTier = Math.max(this.absoluteTier, absoluteTier);
254 + if (step == null || step.group() == from) {
255 + this.tier = Math.max(this.tier, tier);
256 + }
257 + }
258 +
259 + public void latchBreadth(int breadth) {
260 + this.breadth = Math.max(this.breadth, breadth);
261 + }
262 +
263 + void setTierAndDepth(int absoluteTier, int tier, int depth, Group from) {
264 + latchTiers(absoluteTier, tier, from);
265 + this.depth = depth;
266 + }
267 +
268 + boolean visitAndLatchMaxTier(int absoluteTier, int tier, Group from) {
269 + latchTiers(absoluteTier, tier, from);
270 + --remainingRequirements;
271 + return remainingRequirements == 0;
272 + }
273 +
274 + Step step() {
275 + return step;
276 + }
277 +
278 + public int absoluteTier() {
279 + return absoluteTier;
280 + }
281 +
282 + int tier() {
283 + return tier;
284 + }
285 +
286 + int depth() {
287 + return depth;
288 + }
289 +
290 + int breadth() {
291 + return breadth;
292 + }
293 +
294 + int top() {
295 + return top;
296 + }
297 +
298 + int center() {
299 + return center;
300 + }
301 +
302 + public void updateCenter(int top, int center) {
303 + this.top = top;
304 + this.center = center;
305 + }
306 + }
307 +}
...@@ -18,24 +18,24 @@ package org.onlab.stc; ...@@ -18,24 +18,24 @@ package org.onlab.stc;
18 import com.fasterxml.jackson.databind.ObjectMapper; 18 import com.fasterxml.jackson.databind.ObjectMapper;
19 import com.fasterxml.jackson.databind.node.ObjectNode; 19 import com.fasterxml.jackson.databind.node.ObjectNode;
20 import org.eclipse.jetty.websocket.WebSocket; 20 import org.eclipse.jetty.websocket.WebSocket;
21 -import org.slf4j.Logger;
22 -import org.slf4j.LoggerFactory;
23 21
24 import java.io.IOException; 22 import java.io.IOException;
25 23
24 +import static org.onlab.stc.Coordinator.print;
25 +
26 /** 26 /**
27 * Web socket capable of interacting with the STC monitor GUI. 27 * Web socket capable of interacting with the STC monitor GUI.
28 */ 28 */
29 public class MonitorWebSocket implements WebSocket.OnTextMessage, WebSocket.OnControl { 29 public class MonitorWebSocket implements WebSocket.OnTextMessage, WebSocket.OnControl {
30 30
31 - private static final Logger log = LoggerFactory.getLogger(MonitorWebSocket.class);
32 -
33 private static final long MAX_AGE_MS = 30_000; 31 private static final long MAX_AGE_MS = 30_000;
34 32
35 private static final byte PING = 0x9; 33 private static final byte PING = 0x9;
36 private static final byte PONG = 0xA; 34 private static final byte PONG = 0xA;
37 private static final byte[] PING_DATA = new byte[]{(byte) 0xde, (byte) 0xad}; 35 private static final byte[] PING_DATA = new byte[]{(byte) 0xde, (byte) 0xad};
38 36
37 + private final Monitor monitor;
38 +
39 private Connection connection; 39 private Connection connection;
40 private FrameConnection control; 40 private FrameConnection control;
41 41
...@@ -44,6 +44,15 @@ public class MonitorWebSocket implements WebSocket.OnTextMessage, WebSocket.OnCo ...@@ -44,6 +44,15 @@ public class MonitorWebSocket implements WebSocket.OnTextMessage, WebSocket.OnCo
44 private long lastActive = System.currentTimeMillis(); 44 private long lastActive = System.currentTimeMillis();
45 45
46 /** 46 /**
47 + * Creates a new monitor client GUI web-socket.
48 + *
49 + * @param monitor shared process flow monitor
50 + */
51 + MonitorWebSocket(Monitor monitor) {
52 + this.monitor = monitor;
53 + }
54 +
55 + /**
47 * Issues a close on the connection. 56 * Issues a close on the connection.
48 */ 57 */
49 synchronized void close() { 58 synchronized void close() {
...@@ -62,13 +71,12 @@ public class MonitorWebSocket implements WebSocket.OnTextMessage, WebSocket.OnCo ...@@ -62,13 +71,12 @@ public class MonitorWebSocket implements WebSocket.OnTextMessage, WebSocket.OnCo
62 long quietFor = System.currentTimeMillis() - lastActive; 71 long quietFor = System.currentTimeMillis() - lastActive;
63 boolean idle = quietFor > MAX_AGE_MS; 72 boolean idle = quietFor > MAX_AGE_MS;
64 if (idle || (connection != null && !connection.isOpen())) { 73 if (idle || (connection != null && !connection.isOpen())) {
65 - log.debug("IDLE (or closed) websocket [{} ms]", quietFor);
66 return true; 74 return true;
67 } else if (connection != null) { 75 } else if (connection != null) {
68 try { 76 try {
69 control.sendControl(PING, PING_DATA, 0, PING_DATA.length); 77 control.sendControl(PING, PING_DATA, 0, PING_DATA.length);
70 } catch (IOException e) { 78 } catch (IOException e) {
71 - log.warn("Unable to send ping message due to: ", e); 79 + print("Unable to send ping message due to: %s", e);
72 } 80 }
73 } 81 }
74 return false; 82 return false;
...@@ -80,10 +88,10 @@ public class MonitorWebSocket implements WebSocket.OnTextMessage, WebSocket.OnCo ...@@ -80,10 +88,10 @@ public class MonitorWebSocket implements WebSocket.OnTextMessage, WebSocket.OnCo
80 this.control = (FrameConnection) connection; 88 this.control = (FrameConnection) connection;
81 try { 89 try {
82 createHandlers(); 90 createHandlers();
83 - log.info("GUI client connected"); 91 + sendMessage(message("flow", monitor.scenarioData()));
84 92
85 } catch (Exception e) { 93 } catch (Exception e) {
86 - log.warn("Unable to open monitor connection: {}", e); 94 + print("Unable to open monitor connection: %s", e);
87 this.connection.close(); 95 this.connection.close();
88 this.connection = null; 96 this.connection = null;
89 this.control = null; 97 this.control = null;
...@@ -93,8 +101,6 @@ public class MonitorWebSocket implements WebSocket.OnTextMessage, WebSocket.OnCo ...@@ -93,8 +101,6 @@ public class MonitorWebSocket implements WebSocket.OnTextMessage, WebSocket.OnCo
93 @Override 101 @Override
94 public synchronized void onClose(int closeCode, String message) { 102 public synchronized void onClose(int closeCode, String message) {
95 destroyHandlers(); 103 destroyHandlers();
96 - log.info("GUI client disconnected [close-code={}, message={}]",
97 - closeCode, message);
98 } 104 }
99 105
100 @Override 106 @Override
...@@ -109,10 +115,9 @@ public class MonitorWebSocket implements WebSocket.OnTextMessage, WebSocket.OnCo ...@@ -109,10 +115,9 @@ public class MonitorWebSocket implements WebSocket.OnTextMessage, WebSocket.OnCo
109 try { 115 try {
110 ObjectNode message = (ObjectNode) mapper.reader().readTree(data); 116 ObjectNode message = (ObjectNode) mapper.reader().readTree(data);
111 // TODO: 117 // TODO:
112 - log.info("Got message: {}", message); 118 + print("Got message: %s", message);
113 } catch (Exception e) { 119 } catch (Exception e) {
114 - log.warn("Unable to parse GUI message {} due to {}", data, e); 120 + print("Unable to parse GUI message %s due to %s", data, e);
115 - log.debug("Boom!!!", e);
116 } 121 }
117 } 122 }
118 123
...@@ -122,20 +127,14 @@ public class MonitorWebSocket implements WebSocket.OnTextMessage, WebSocket.OnCo ...@@ -122,20 +127,14 @@ public class MonitorWebSocket implements WebSocket.OnTextMessage, WebSocket.OnCo
122 connection.sendMessage(message.toString()); 127 connection.sendMessage(message.toString());
123 } 128 }
124 } catch (IOException e) { 129 } catch (IOException e) {
125 - log.warn("Unable to send message {} to GUI due to {}", message, e); 130 + print("Unable to send message %s to GUI due to %s", message, e);
126 - log.debug("Boom!!!", e);
127 } 131 }
128 } 132 }
129 133
130 - public synchronized void sendMessage(String type, long sid, ObjectNode payload) { 134 + public ObjectNode message(String type, ObjectNode payload) {
131 - ObjectNode message = mapper.createObjectNode(); 135 + ObjectNode message = mapper.createObjectNode().put("event", type);
132 - message.put("event", type);
133 - if (sid > 0) {
134 - message.put("sid", sid);
135 - }
136 message.set("payload", payload); 136 message.set("payload", payload);
137 - sendMessage(message); 137 + return message;
138 -
139 } 138 }
140 139
141 // Creates new message handlers. 140 // Creates new message handlers.
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
15 */ 15 */
16 package org.onlab.stc; 16 package org.onlab.stc;
17 17
18 +import com.fasterxml.jackson.databind.node.ObjectNode;
18 import com.google.common.io.ByteStreams; 19 import com.google.common.io.ByteStreams;
19 import com.google.common.net.MediaType; 20 import com.google.common.net.MediaType;
20 import org.eclipse.jetty.websocket.WebSocket; 21 import org.eclipse.jetty.websocket.WebSocket;
...@@ -34,11 +35,13 @@ import java.util.TimerTask; ...@@ -34,11 +35,13 @@ import java.util.TimerTask;
34 /** 35 /**
35 * Web socket servlet capable of creating web sockets for the STC monitor. 36 * Web socket servlet capable of creating web sockets for the STC monitor.
36 */ 37 */
37 -public class MonitorWebSocketServlet extends WebSocketServlet { 38 +public class MonitorWebSocketServlet extends WebSocketServlet
39 + implements MonitorDelegate {
38 40
39 private static final long PING_DELAY_MS = 5000; 41 private static final long PING_DELAY_MS = 5000;
40 private static final String DOT = "."; 42 private static final String DOT = ".";
41 43
44 + private static Monitor monitor;
42 private static MonitorWebSocketServlet instance; 45 private static MonitorWebSocketServlet instance;
43 46
44 private final Set<MonitorWebSocket> sockets = new HashSet<>(); 47 private final Set<MonitorWebSocket> sockets = new HashSet<>();
...@@ -46,6 +49,15 @@ public class MonitorWebSocketServlet extends WebSocketServlet { ...@@ -46,6 +49,15 @@ public class MonitorWebSocketServlet extends WebSocketServlet {
46 private final TimerTask pruner = new Pruner(); 49 private final TimerTask pruner = new Pruner();
47 50
48 /** 51 /**
52 + * Binds the shared process flow monitor.
53 + *
54 + * @param m process monitor reference
55 + */
56 + public static void setMonitor(Monitor m) {
57 + monitor = m;
58 + }
59 +
60 + /**
49 * Closes all currently open monitor web-sockets. 61 * Closes all currently open monitor web-sockets.
50 */ 62 */
51 public static void closeAll() { 63 public static void closeAll() {
...@@ -59,7 +71,7 @@ public class MonitorWebSocketServlet extends WebSocketServlet { ...@@ -59,7 +71,7 @@ public class MonitorWebSocketServlet extends WebSocketServlet {
59 public void init() throws ServletException { 71 public void init() throws ServletException {
60 super.init(); 72 super.init();
61 instance = this; 73 instance = this;
62 - System.out.println("Yo!!!!"); 74 + monitor.setDelegate(this);
63 timer.schedule(pruner, PING_DELAY_MS, PING_DELAY_MS); 75 timer.schedule(pruner, PING_DELAY_MS, PING_DELAY_MS);
64 } 76 }
65 77
...@@ -92,14 +104,20 @@ public class MonitorWebSocketServlet extends WebSocketServlet { ...@@ -92,14 +104,20 @@ public class MonitorWebSocketServlet extends WebSocketServlet {
92 104
93 @Override 105 @Override
94 public WebSocket doWebSocketConnect(HttpServletRequest request, String protocol) { 106 public WebSocket doWebSocketConnect(HttpServletRequest request, String protocol) {
95 - System.out.println("Wazup????"); 107 + MonitorWebSocket socket = new MonitorWebSocket(monitor);
96 - MonitorWebSocket socket = new MonitorWebSocket();
97 synchronized (sockets) { 108 synchronized (sockets) {
98 sockets.add(socket); 109 sockets.add(socket);
99 } 110 }
100 return socket; 111 return socket;
101 } 112 }
102 113
114 + @Override
115 + public void notify(ObjectNode event) {
116 + if (instance != null) {
117 + instance.sockets.forEach(ws -> ws.sendMessage(event));
118 + }
119 + }
120 +
103 // Task for pruning web-sockets that are idle. 121 // Task for pruning web-sockets that are idle.
104 private class Pruner extends TimerTask { 122 private class Pruner extends TimerTask {
105 @Override 123 @Override
......
...@@ -23,9 +23,10 @@ public interface StepProcessListener { ...@@ -23,9 +23,10 @@ public interface StepProcessListener {
23 /** 23 /**
24 * Indicates that process step has started. 24 * Indicates that process step has started.
25 * 25 *
26 - * @param step subject step 26 + * @param step subject step
27 + * @param command actual command executed; includes run-time substitutions
27 */ 28 */
28 - default void onStart(Step step) { 29 + default void onStart(Step step, String command) {
29 } 30 }
30 31
31 /** 32 /**
......
...@@ -23,6 +23,7 @@ import java.io.IOException; ...@@ -23,6 +23,7 @@ import java.io.IOException;
23 import java.io.InputStream; 23 import java.io.InputStream;
24 import java.io.InputStreamReader; 24 import java.io.InputStreamReader;
25 import java.io.PrintWriter; 25 import java.io.PrintWriter;
26 +import java.util.function.Function;
26 27
27 import static java.lang.String.format; 28 import static java.lang.String.format;
28 import static org.onlab.stc.Coordinator.Status.FAILED; 29 import static org.onlab.stc.Coordinator.Status.FAILED;
...@@ -41,26 +42,32 @@ class StepProcessor implements Runnable { ...@@ -41,26 +42,32 @@ class StepProcessor implements Runnable {
41 42
42 private final Step step; 43 private final Step step;
43 private final File logDir; 44 private final File logDir;
45 + private String command;
44 46
45 private Process process; 47 private Process process;
46 private StepProcessListener delegate; 48 private StepProcessListener delegate;
49 + private Function<String, String> substitutor;
47 50
48 /** 51 /**
49 * Creates a process monitor. 52 * Creates a process monitor.
50 * 53 *
51 - * @param step step or group to be executed 54 + * @param step step or group to be executed
52 - * @param logDir directory where step process log should be stored 55 + * @param logDir directory where step process log should be stored
53 - * @param delegate process lifecycle listener 56 + * @param delegate process lifecycle listener
57 + * @param substitutor function to substitute var reference in command
54 */ 58 */
55 - StepProcessor(Step step, File logDir, StepProcessListener delegate) { 59 + StepProcessor(Step step, File logDir, StepProcessListener delegate,
60 + Function<String, String> substitutor) {
56 this.step = step; 61 this.step = step;
57 this.logDir = logDir; 62 this.logDir = logDir;
58 this.delegate = delegate; 63 this.delegate = delegate;
64 + this.substitutor = substitutor;
59 } 65 }
60 66
61 @Override 67 @Override
62 public void run() { 68 public void run() {
63 - delegate.onStart(step); 69 + command = substitutor != null ? substitutor.apply(command()) : command();
70 + delegate.onStart(step, command);
64 int code = execute(); 71 int code = execute();
65 boolean ignoreCode = step.env() != null && step.env.equals(IGNORE_CODE); 72 boolean ignoreCode = step.env() != null && step.env.equals(IGNORE_CODE);
66 Status status = ignoreCode || code == 0 ? SUCCEEDED : FAILED; 73 Status status = ignoreCode || code == 0 ? SUCCEEDED : FAILED;
...@@ -74,7 +81,7 @@ class StepProcessor implements Runnable { ...@@ -74,7 +81,7 @@ class StepProcessor implements Runnable {
74 */ 81 */
75 private int execute() { 82 private int execute() {
76 try (PrintWriter pw = new PrintWriter(logFile())) { 83 try (PrintWriter pw = new PrintWriter(logFile())) {
77 - process = Runtime.getRuntime().exec(command()); 84 + process = Runtime.getRuntime().exec(command);
78 processOutput(pw); 85 processOutput(pw);
79 86
80 // Wait for the process to complete and get its exit code. 87 // Wait for the process to complete and get its exit code.
......
1 +{
2 + "requirements": [
3 + {
4 + "dst": "Reactive-Forwarding.Ping-2",
5 + "isSoft": false,
6 + "src": "Reactive-Forwarding.Link-2-Down"
7 + },
8 + {
9 + "dst": "Final-Check-Logs-2",
10 + "isSoft": true,
11 + "src": "Fetch-Logs-2"
12 + },
13 + {
14 + "dst": "Host-Intent.Ping-4",
15 + "isSoft": false,
16 + "src": "Host-Intent.Link-2-Up"
17 + },
18 + {
19 + "dst": "Install-1",
20 + "isSoft": false,
21 + "src": "Wait-for-Start-1"
22 + },
23 + {
24 + "dst": "Host-Intent.Link-1-Down",
25 + "isSoft": false,
26 + "src": "Host-Intent.Ping-2"
27 + },
28 + {
29 + "dst": "Host-Intent.Link-2-Up",
30 + "isSoft": false,
31 + "src": "Host-Intent.Ping-5"
32 + },
33 + {
34 + "dst": "Host-Intent.Ping-2",
35 + "isSoft": false,
36 + "src": "Host-Intent.Link-2-Down"
37 + },
38 + {
39 + "dst": "Reinstall-App-With-CLI",
40 + "isSoft": false,
41 + "src": "Verify-CLI"
42 + },
43 + {
44 + "dst": "Create-App-UI-Overlay",
45 + "isSoft": false,
46 + "src": "Build-App-With-UI"
47 + },
48 + {
49 + "dst": "Secure-SSH",
50 + "isSoft": true,
51 + "src": "Wait-for-Start-1"
52 + },
53 + {
54 + "dst": "Pause-For-Masters",
55 + "isSoft": true,
56 + "src": "Check-Flows"
57 + },
58 + {
59 + "dst": "Secure-SSH",
60 + "isSoft": true,
61 + "src": "Wait-for-Start-3"
62 + },
63 + {
64 + "dst": "Uninstall-3",
65 + "isSoft": false,
66 + "src": "Kill-3"
67 + },
68 + {
69 + "dst": "Balance-Masters",
70 + "isSoft": false,
71 + "src": "Pause-For-Masters"
72 + },
73 + {
74 + "dst": "Reactive-Forwarding.Net-Pingall",
75 + "isSoft": true,
76 + "src": "Reactive-Forwarding.Net-Link-Down-Up"
77 + },
78 + {
79 + "dst": "Wait-for-Start-3",
80 + "isSoft": true,
81 + "src": "Check-Logs-3"
82 + },
83 + {
84 + "dst": "Wait-for-Start-2",
85 + "isSoft": true,
86 + "src": "Check-Components-2"
87 + },
88 + {
89 + "dst": "Uninstall-Reactive-Forwarding",
90 + "isSoft": false,
91 + "src": "Find-Host-1"
92 + },
93 + {
94 + "dst": "Wipe-Out-Data-Before",
95 + "isSoft": true,
96 + "src": "Initial-Summary-Check"
97 + },
98 + {
99 + "dst": "Reactive-Forwarding.Ping-3",
100 + "isSoft": false,
101 + "src": "Reactive-Forwarding.Link-1-Up"
102 + },
103 + {
104 + "dst": "Archetypes",
105 + "isSoft": true,
106 + "src": "Wrapup"
107 + },
108 + {
109 + "dst": "Reactive-Forwarding.Ping-4",
110 + "isSoft": false,
111 + "src": "Reactive-Forwarding.Link-2-Up"
112 + },
113 + {
114 + "dst": "Host-Intent-Connectivity",
115 + "isSoft": true,
116 + "src": "Net-Teardown"
117 + },
118 + {
119 + "dst": "Host-Intent.Ping-3",
120 + "isSoft": false,
121 + "src": "Host-Intent.Link-1-Up"
122 + },
123 + {
124 + "dst": "Host-Intent.Ping-1",
125 + "isSoft": false,
126 + "src": "Host-Intent.Link-1-Down"
127 + },
128 + {
129 + "dst": "Install-App",
130 + "isSoft": false,
131 + "src": "Create-App-CLI-Overlay"
132 + },
133 + {
134 + "dst": "Final-Check-Logs-3",
135 + "isSoft": true,
136 + "src": "Fetch-Logs-3"
137 + },
138 + {
139 + "dst": "Install-App",
140 + "isSoft": false,
141 + "src": "Verify-App"
142 + },
143 + {
144 + "dst": "Host-Intent.Link-2-Down",
145 + "isSoft": false,
146 + "src": "Host-Intent.Ping-3"
147 + },
148 + {
149 + "dst": "Prerequisites",
150 + "isSoft": false,
151 + "src": "Setup"
152 + },
153 + {
154 + "dst": "Verify-App",
155 + "isSoft": true,
156 + "src": "Reinstall-App-With-CLI"
157 + },
158 + {
159 + "dst": "Net-Smoke",
160 + "isSoft": true,
161 + "src": "Archetypes"
162 + },
163 + {
164 + "dst": "Setup",
165 + "isSoft": true,
166 + "src": "Wrapup"
167 + },
168 + {
169 + "dst": "Start-Mininet",
170 + "isSoft": false,
171 + "src": "Wait-For-Mininet"
172 + },
173 + {
174 + "dst": "Verify-UI",
175 + "isSoft": false,
176 + "src": "Uninstall-App"
177 + },
178 + {
179 + "dst": "Kill-3",
180 + "isSoft": false,
181 + "src": "Install-3"
182 + },
183 + {
184 + "dst": "Wait-for-Start-1",
185 + "isSoft": true,
186 + "src": "Check-Components-1"
187 + },
188 + {
189 + "dst": "Wait-for-Start-1",
190 + "isSoft": true,
191 + "src": "Check-Nodes-1"
192 + },
193 + {
194 + "dst": "Push-Topos",
195 + "isSoft": false,
196 + "src": "Start-Mininet"
197 + },
198 + {
199 + "dst": "Reactive-Forwarding.Check-Summary-For-Hosts",
200 + "isSoft": true,
201 + "src": "Reactive-Forwarding.Config-Topo"
202 + },
203 + {
204 + "dst": "Reactive-Forwarding.Install-Apps",
205 + "isSoft": false,
206 + "src": "Reactive-Forwarding.Check-Apps"
207 + },
208 + {
209 + "dst": "Push-Bits",
210 + "isSoft": false,
211 + "src": "Install-2"
212 + },
213 + {
214 + "dst": "Install-1",
215 + "isSoft": false,
216 + "src": "Secure-SSH"
217 + },
218 + {
219 + "dst": "Create-Intent",
220 + "isSoft": false,
221 + "src": "Host-Intent.Net-Link-Down-Up"
222 + },
223 + {
224 + "dst": "Verify-CLI",
225 + "isSoft": true,
226 + "src": "Reinstall-App-With-UI"
227 + },
228 + {
229 + "dst": "Wait-for-Start-3",
230 + "isSoft": true,
231 + "src": "Check-Apps-3"
232 + },
233 + {
234 + "dst": "Net-Smoke",
235 + "isSoft": true,
236 + "src": "Wrapup"
237 + },
238 + {
239 + "dst": "Initial-Summary-Check",
240 + "isSoft": false,
241 + "src": "Start-Mininet"
242 + },
243 + {
244 + "dst": "Install-3",
245 + "isSoft": false,
246 + "src": "Wait-for-Start-3"
247 + },
248 + {
249 + "dst": "Reactive-Forwarding.Link-1-Up",
250 + "isSoft": false,
251 + "src": "Reactive-Forwarding.Ping-4"
252 + },
253 + {
254 + "dst": "Check-Summary",
255 + "isSoft": true,
256 + "src": "Balance-Masters"
257 + },
258 + {
259 + "dst": "Reactive-Forwarding.Net-Link-Down-Up",
260 + "isSoft": true,
261 + "src": "Host-Intent-Connectivity"
262 + },
263 + {
264 + "dst": "Secure-SSH",
265 + "isSoft": true,
266 + "src": "Wait-for-Start-2"
267 + },
268 + {
269 + "dst": "Build-App-With-CLI",
270 + "isSoft": false,
271 + "src": "Reinstall-App-With-CLI"
272 + },
273 + {
274 + "dst": "Uninstall-1",
275 + "isSoft": false,
276 + "src": "Kill-1"
277 + },
278 + {
279 + "dst": "Find-Host-1",
280 + "isSoft": false,
281 + "src": "Find-Host-2"
282 + },
283 + {
284 + "dst": "Create-App-CLI-Overlay",
285 + "isSoft": false,
286 + "src": "Build-App-With-CLI"
287 + },
288 + {
289 + "dst": "Net-Setup",
290 + "isSoft": false,
291 + "src": "Reactive-Forwarding.Net-Link-Down-Up"
292 + },
293 + {
294 + "dst": "Kill-2",
295 + "isSoft": false,
296 + "src": "Install-2"
297 + },
298 + {
299 + "dst": "Wait-for-Start-1",
300 + "isSoft": true,
301 + "src": "Check-Logs-1"
302 + },
303 + {
304 + "dst": "Wait-for-Start-2",
305 + "isSoft": true,
306 + "src": "Check-Nodes-2"
307 + },
308 + {
309 + "dst": "Reactive-Forwarding.Ping-All-And-Verify",
310 + "isSoft": true,
311 + "src": "Reactive-Forwarding.Check-Summary-For-Hosts"
312 + },
313 + {
314 + "dst": "Clean-Up",
315 + "isSoft": false,
316 + "src": "Create-App"
317 + },
318 + {
319 + "dst": "Host-Intent.Link-1-Up",
320 + "isSoft": false,
321 + "src": "Host-Intent.Ping-4"
322 + },
323 + {
324 + "dst": "Build-App-With-UI",
325 + "isSoft": false,
326 + "src": "Reinstall-App-With-UI"
327 + },
328 + {
329 + "dst": "Install-2",
330 + "isSoft": false,
331 + "src": "Secure-SSH"
332 + },
333 + {
334 + "dst": "Wait-For-Mininet",
335 + "isSoft": false,
336 + "src": "Check-Summary"
337 + },
338 + {
339 + "dst": "Host-Intent.Net-Link-Down-Up",
340 + "isSoft": false,
341 + "src": "Remove-Intent"
342 + },
343 + {
344 + "dst": "Net-Setup",
345 + "isSoft": false,
346 + "src": "Host-Intent-Connectivity"
347 + },
348 + {
349 + "dst": "Net-Setup",
350 + "isSoft": false,
351 + "src": "Reactive-Forwarding.Net-Pingall"
352 + },
353 + {
354 + "dst": "Reactive-Forwarding.Link-2-Down",
355 + "isSoft": false,
356 + "src": "Reactive-Forwarding.Ping-3"
357 + },
358 + {
359 + "dst": "Find-Host-2",
360 + "isSoft": false,
361 + "src": "Create-Intent"
362 + },
363 + {
364 + "dst": "Wait-for-Start-2",
365 + "isSoft": true,
366 + "src": "Check-Apps-2"
367 + },
368 + {
369 + "dst": "Final-Check-Logs-1",
370 + "isSoft": true,
371 + "src": "Fetch-Logs-1"
372 + },
373 + {
374 + "dst": "Install-2",
375 + "isSoft": false,
376 + "src": "Wait-for-Start-2"
377 + },
378 + {
379 + "dst": "Reactive-Forwarding.Ping-1",
380 + "isSoft": false,
381 + "src": "Reactive-Forwarding.Link-1-Down"
382 + },
383 + {
384 + "dst": "Create-App",
385 + "isSoft": false,
386 + "src": "Build-App"
387 + },
388 + {
389 + "dst": "Check-Summary",
390 + "isSoft": true,
391 + "src": "Check-Flows"
392 + },
393 + {
394 + "dst": "Build-App",
395 + "isSoft": false,
396 + "src": "Install-App"
397 + },
398 + {
399 + "dst": "Reinstall-App-With-UI",
400 + "isSoft": false,
401 + "src": "Verify-UI"
402 + },
403 + {
404 + "dst": "Uninstall-2",
405 + "isSoft": false,
406 + "src": "Kill-2"
407 + },
408 + {
409 + "dst": "Setup",
410 + "isSoft": false,
411 + "src": "Archetypes"
412 + },
413 + {
414 + "dst": "Setup",
415 + "isSoft": false,
416 + "src": "Net-Smoke"
417 + },
418 + {
419 + "dst": "Kill-1",
420 + "isSoft": false,
421 + "src": "Install-1"
422 + },
423 + {
424 + "dst": "Reactive-Forwarding.Link-1-Down",
425 + "isSoft": false,
426 + "src": "Reactive-Forwarding.Ping-2"
427 + },
428 + {
429 + "dst": "Wait-for-Start-2",
430 + "isSoft": true,
431 + "src": "Check-Logs-2"
432 + },
433 + {
434 + "dst": "Wait-for-Start-3",
435 + "isSoft": true,
436 + "src": "Check-Components-3"
437 + },
438 + {
439 + "dst": "Wait-for-Start-3",
440 + "isSoft": true,
441 + "src": "Check-Nodes-3"
442 + },
443 + {
444 + "dst": "Stop-Mininet-If-Needed",
445 + "isSoft": false,
446 + "src": "Start-Mininet"
447 + },
448 + {
449 + "dst": "Reactive-Forwarding.Link-2-Up",
450 + "isSoft": false,
451 + "src": "Reactive-Forwarding.Ping-5"
452 + },
453 + {
454 + "dst": "Reactive-Forwarding.Check-Apps",
455 + "isSoft": false,
456 + "src": "Reactive-Forwarding.Ping-All-And-Verify"
457 + },
458 + {
459 + "dst": "Install-3",
460 + "isSoft": false,
461 + "src": "Secure-SSH"
462 + },
463 + {
464 + "dst": "Push-Bits",
465 + "isSoft": false,
466 + "src": "Install-3"
467 + },
468 + {
469 + "dst": "Reinstall-App-With-CLI",
470 + "isSoft": false,
471 + "src": "Create-App-UI-Overlay"
472 + },
473 + {
474 + "dst": "Push-Bits",
475 + "isSoft": false,
476 + "src": "Install-1"
477 + },
478 + {
479 + "dst": "Wait-for-Start-1",
480 + "isSoft": true,
481 + "src": "Check-Apps-1"
482 + }
483 + ],
484 + "steps": [
485 + {
486 + "group": "Net-Setup",
487 + "isGroup": false,
488 + "name": "Check-Summary",
489 + "status": "waiting"
490 + },
491 + {
492 + "group": "Net-Setup",
493 + "isGroup": false,
494 + "name": "Check-Flows",
495 + "status": "waiting"
496 + },
497 + {
498 + "group": "Wrapup",
499 + "isGroup": false,
500 + "name": "Final-Check-Logs-1",
501 + "status": "waiting"
502 + },
503 + {
504 + "group": "Wrapup",
505 + "isGroup": false,
506 + "name": "Final-Check-Logs-2",
507 + "status": "waiting"
508 + },
509 + {
510 + "group": "Archetypes",
511 + "isGroup": false,
512 + "name": "Clean-Up",
513 + "status": "waiting"
514 + },
515 + {
516 + "group": "Archetypes",
517 + "isGroup": false,
518 + "name": "Build-App-With-UI",
519 + "status": "waiting"
520 + },
521 + {
522 + "group": "Archetypes",
523 + "isGroup": false,
524 + "name": "Uninstall-App",
525 + "status": "waiting"
526 + },
527 + {
528 + "group": "Wrapup",
529 + "isGroup": false,
530 + "name": "Final-Check-Logs-3",
531 + "status": "waiting"
532 + },
533 + {
534 + "group": "Host-Intent.Net-Link-Down-Up",
535 + "isGroup": false,
536 + "name": "Host-Intent.Link-2-Down",
537 + "status": "waiting"
538 + },
539 + {
540 + "group": "Wrapup",
541 + "isGroup": false,
542 + "name": "Fetch-Logs-3",
543 + "status": "waiting"
544 + },
545 + {
546 + "group": "Wrapup",
547 + "isGroup": false,
548 + "name": "Fetch-Logs-2",
549 + "status": "waiting"
550 + },
551 + {
552 + "group": "Setup",
553 + "isGroup": false,
554 + "name": "Check-Components-3",
555 + "status": "waiting"
556 + },
557 + {
558 + "group": "Wrapup",
559 + "isGroup": false,
560 + "name": "Fetch-Logs-1",
561 + "status": "waiting"
562 + },
563 + {
564 + "group": "Net-Setup",
565 + "isGroup": false,
566 + "name": "Push-Topos",
567 + "status": "waiting"
568 + },
569 + {
570 + "group": "Reactive-Forwarding.Net-Pingall",
571 + "isGroup": false,
572 + "name": "Reactive-Forwarding.Check-Apps",
573 + "status": "waiting"
574 + },
575 + {
576 + "group": "Setup",
577 + "isGroup": false,
578 + "name": "Wait-for-Start-3",
579 + "status": "waiting"
580 + },
581 + {
582 + "group": "Setup",
583 + "isGroup": false,
584 + "name": "Wait-for-Start-2",
585 + "status": "waiting"
586 + },
587 + {
588 + "group": "Setup",
589 + "isGroup": false,
590 + "name": "Wait-for-Start-1",
591 + "status": "waiting"
592 + },
593 + {
594 + "group": "Net-Smoke",
595 + "isGroup": true,
596 + "name": "Host-Intent-Connectivity",
597 + "status": "waiting"
598 + },
599 + {
600 + "group": "Host-Intent-Connectivity",
601 + "isGroup": false,
602 + "name": "Create-Intent",
603 + "status": "waiting"
604 + },
605 + {
606 + "isGroup": true,
607 + "name": "Prerequisites",
608 + "status": "in_progress"
609 + },
610 + {
611 + "group": "Setup",
612 + "isGroup": false,
613 + "name": "Push-Bits",
614 + "status": "waiting"
615 + },
616 + {
617 + "group": "Setup",
618 + "isGroup": false,
619 + "name": "Check-Logs-2",
620 + "status": "waiting"
621 + },
622 + {
623 + "group": "Setup",
624 + "isGroup": false,
625 + "name": "Check-Logs-3",
626 + "status": "waiting"
627 + },
628 + {
629 + "group": "Setup",
630 + "isGroup": false,
631 + "name": "Kill-1",
632 + "status": "waiting"
633 + },
634 + {
635 + "group": "Setup",
636 + "isGroup": false,
637 + "name": "Kill-3",
638 + "status": "waiting"
639 + },
640 + {
641 + "group": "Setup",
642 + "isGroup": false,
643 + "name": "Kill-2",
644 + "status": "waiting"
645 + },
646 + {
647 + "group": "Host-Intent-Connectivity",
648 + "isGroup": true,
649 + "name": "Host-Intent.Net-Link-Down-Up",
650 + "status": "waiting"
651 + },
652 + {
653 + "group": "Host-Intent.Net-Link-Down-Up",
654 + "isGroup": false,
655 + "name": "Host-Intent.Ping-1",
656 + "status": "waiting"
657 + },
658 + {
659 + "group": "Archetypes",
660 + "isGroup": false,
661 + "name": "Verify-UI",
662 + "status": "waiting"
663 + },
664 + {
665 + "group": "Host-Intent.Net-Link-Down-Up",
666 + "isGroup": false,
667 + "name": "Host-Intent.Ping-2",
668 + "status": "waiting"
669 + },
670 + {
671 + "group": "Host-Intent.Net-Link-Down-Up",
672 + "isGroup": false,
673 + "name": "Host-Intent.Ping-3",
674 + "status": "waiting"
675 + },
676 + {
677 + "group": "Setup",
678 + "isGroup": false,
679 + "name": "Uninstall-1",
680 + "status": "waiting"
681 + },
682 + {
683 + "group": "Setup",
684 + "isGroup": false,
685 + "name": "Check-Logs-1",
686 + "status": "waiting"
687 + },
688 + {
689 + "group": "Host-Intent.Net-Link-Down-Up",
690 + "isGroup": false,
691 + "name": "Host-Intent.Ping-4",
692 + "status": "waiting"
693 + },
694 + {
695 + "group": "Setup",
696 + "isGroup": false,
697 + "name": "Uninstall-3",
698 + "status": "waiting"
699 + },
700 + {
701 + "group": "Host-Intent.Net-Link-Down-Up",
702 + "isGroup": false,
703 + "name": "Host-Intent.Ping-5",
704 + "status": "waiting"
705 + },
706 + {
707 + "group": "Setup",
708 + "isGroup": false,
709 + "name": "Uninstall-2",
710 + "status": "waiting"
711 + },
712 + {
713 + "group": "Reactive-Forwarding.Net-Pingall",
714 + "isGroup": false,
715 + "name": "Reactive-Forwarding.Install-Apps",
716 + "status": "waiting"
717 + },
718 + {
719 + "group": "Net-Smoke",
720 + "isGroup": true,
721 + "name": "Reactive-Forwarding.Net-Link-Down-Up",
722 + "status": "waiting"
723 + },
724 + {
725 + "group": "Prerequisites",
726 + "isGroup": false,
727 + "name": "Check-ONOS-Bits",
728 + "status": "in_progress"
729 + },
730 + {
731 + "isGroup": true,
732 + "name": "Wrapup",
733 + "status": "waiting"
734 + },
735 + {
736 + "group": "Setup",
737 + "isGroup": false,
738 + "name": "Install-2",
739 + "status": "waiting"
740 + },
741 + {
742 + "group": "Host-Intent-Connectivity",
743 + "isGroup": false,
744 + "name": "Find-Host-1",
745 + "status": "waiting"
746 + },
747 + {
748 + "group": "Setup",
749 + "isGroup": false,
750 + "name": "Install-1",
751 + "status": "waiting"
752 + },
753 + {
754 + "group": "Net-Setup",
755 + "isGroup": false,
756 + "name": "Wipe-Out-Data-Before",
757 + "status": "waiting"
758 + },
759 + {
760 + "group": "Net-Setup",
761 + "isGroup": false,
762 + "name": "Pause-For-Masters",
763 + "status": "waiting"
764 + },
765 + {
766 + "group": "Reactive-Forwarding.Net-Link-Down-Up",
767 + "isGroup": false,
768 + "name": "Reactive-Forwarding.Link-2-Up",
769 + "status": "waiting"
770 + },
771 + {
772 + "group": "Net-Smoke",
773 + "isGroup": true,
774 + "name": "Reactive-Forwarding.Net-Pingall",
775 + "status": "waiting"
776 + },
777 + {
778 + "group": "Setup",
779 + "isGroup": false,
780 + "name": "Check-Components-2",
781 + "status": "waiting"
782 + },
783 + {
784 + "group": "Setup",
785 + "isGroup": false,
786 + "name": "Check-Components-1",
787 + "status": "waiting"
788 + },
789 + {
790 + "group": "Archetypes",
791 + "isGroup": false,
792 + "name": "Reinstall-App-With-UI",
793 + "status": "waiting"
794 + },
795 + {
796 + "group": "Archetypes",
797 + "isGroup": false,
798 + "name": "Reinstall-App-With-CLI",
799 + "status": "waiting"
800 + },
801 + {
802 + "group": "Archetypes",
803 + "isGroup": false,
804 + "name": "Build-App-With-CLI",
805 + "status": "waiting"
806 + },
807 + {
808 + "group": "Host-Intent-Connectivity",
809 + "isGroup": false,
810 + "name": "Uninstall-Reactive-Forwarding",
811 + "status": "waiting"
812 + },
813 + {
814 + "group": "Host-Intent.Net-Link-Down-Up",
815 + "isGroup": false,
816 + "name": "Host-Intent.Link-2-Up",
817 + "status": "waiting"
818 + },
819 + {
820 + "group": "Net-Teardown",
821 + "isGroup": false,
822 + "name": "Stop-Mininet",
823 + "status": "waiting"
824 + },
825 + {
826 + "group": "Reactive-Forwarding.Net-Pingall",
827 + "isGroup": false,
828 + "name": "Reactive-Forwarding.Config-Topo",
829 + "status": "waiting"
830 + },
831 + {
832 + "group": "Archetypes",
833 + "isGroup": false,
834 + "name": "Create-App-CLI-Overlay",
835 + "status": "waiting"
836 + },
837 + {
838 + "group": "Reactive-Forwarding.Net-Link-Down-Up",
839 + "isGroup": false,
840 + "name": "Reactive-Forwarding.Link-1-Down",
841 + "status": "waiting"
842 + },
843 + {
844 + "isGroup": true,
845 + "name": "Net-Smoke",
846 + "status": "waiting"
847 + },
848 + {
849 + "group": "Prerequisites",
850 + "isGroup": false,
851 + "name": "Check-Passwordless-Login-2",
852 + "status": "in_progress"
853 + },
854 + {
855 + "group": "Prerequisites",
856 + "isGroup": false,
857 + "name": "Check-Passwordless-Login-1",
858 + "status": "in_progress"
859 + },
860 + {
861 + "group": "Prerequisites",
862 + "isGroup": false,
863 + "name": "Check-Passwordless-Login-3",
864 + "status": "in_progress"
865 + },
866 + {
867 + "group": "Setup",
868 + "isGroup": false,
869 + "name": "Secure-SSH",
870 + "status": "waiting"
871 + },
872 + {
873 + "group": "Net-Smoke",
874 + "isGroup": true,
875 + "name": "Net-Setup",
876 + "status": "waiting"
877 + },
878 + {
879 + "group": "Setup",
880 + "isGroup": false,
881 + "name": "Check-Nodes-1",
882 + "status": "waiting"
883 + },
884 + {
885 + "group": "Setup",
886 + "isGroup": false,
887 + "name": "Install-3",
888 + "status": "waiting"
889 + },
890 + {
891 + "group": "Host-Intent-Connectivity",
892 + "isGroup": false,
893 + "name": "Find-Host-2",
894 + "status": "waiting"
895 + },
896 + {
897 + "group": "Net-Setup",
898 + "isGroup": false,
899 + "name": "Initial-Summary-Check",
900 + "status": "waiting"
901 + },
902 + {
903 + "group": "Archetypes",
904 + "isGroup": false,
905 + "name": "Create-App",
906 + "status": "waiting"
907 + },
908 + {
909 + "group": "Setup",
910 + "isGroup": false,
911 + "name": "Check-Nodes-3",
912 + "status": "waiting"
913 + },
914 + {
915 + "group": "Setup",
916 + "isGroup": false,
917 + "name": "Check-Nodes-2",
918 + "status": "waiting"
919 + },
920 + {
921 + "group": "Reactive-Forwarding.Net-Link-Down-Up",
922 + "isGroup": false,
923 + "name": "Reactive-Forwarding.Link-2-Down",
924 + "status": "waiting"
925 + },
926 + {
927 + "isGroup": true,
928 + "name": "Setup",
929 + "status": "waiting"
930 + },
931 + {
932 + "group": "Archetypes",
933 + "isGroup": false,
934 + "name": "Verify-App",
935 + "status": "waiting"
936 + },
937 + {
938 + "group": "Reactive-Forwarding.Net-Link-Down-Up",
939 + "isGroup": false,
940 + "name": "Reactive-Forwarding.Ping-1",
941 + "status": "waiting"
942 + },
943 + {
944 + "group": "Reactive-Forwarding.Net-Link-Down-Up",
945 + "isGroup": false,
946 + "name": "Reactive-Forwarding.Ping-2",
947 + "status": "waiting"
948 + },
949 + {
950 + "group": "Net-Setup",
951 + "isGroup": false,
952 + "name": "Start-Mininet",
953 + "status": "waiting"
954 + },
955 + {
956 + "group": "Reactive-Forwarding.Net-Link-Down-Up",
957 + "isGroup": false,
958 + "name": "Reactive-Forwarding.Ping-3",
959 + "status": "waiting"
960 + },
961 + {
962 + "group": "Reactive-Forwarding.Net-Link-Down-Up",
963 + "isGroup": false,
964 + "name": "Reactive-Forwarding.Ping-4",
965 + "status": "waiting"
966 + },
967 + {
968 + "group": "Reactive-Forwarding.Net-Link-Down-Up",
969 + "isGroup": false,
970 + "name": "Reactive-Forwarding.Ping-5",
971 + "status": "waiting"
972 + },
973 + {
974 + "group": "Archetypes",
975 + "isGroup": false,
976 + "name": "Verify-CLI",
977 + "status": "waiting"
978 + },
979 + {
980 + "group": "Reactive-Forwarding.Net-Pingall",
981 + "isGroup": false,
982 + "name": "Reactive-Forwarding.Check-Summary-For-Hosts",
983 + "status": "waiting"
984 + },
985 + {
986 + "group": "Net-Smoke",
987 + "isGroup": true,
988 + "name": "Net-Teardown",
989 + "status": "waiting"
990 + },
991 + {
992 + "group": "Host-Intent.Net-Link-Down-Up",
993 + "isGroup": false,
994 + "name": "Host-Intent.Link-1-Up",
995 + "status": "waiting"
996 + },
997 + {
998 + "group": "Host-Intent-Connectivity",
999 + "isGroup": false,
1000 + "name": "Remove-Intent",
1001 + "status": "waiting"
1002 + },
1003 + {
1004 + "group": "Archetypes",
1005 + "isGroup": false,
1006 + "name": "Install-App",
1007 + "status": "waiting"
1008 + },
1009 + {
1010 + "group": "Archetypes",
1011 + "isGroup": false,
1012 + "name": "Create-App-UI-Overlay",
1013 + "status": "waiting"
1014 + },
1015 + {
1016 + "group": "Reactive-Forwarding.Net-Link-Down-Up",
1017 + "isGroup": false,
1018 + "name": "Reactive-Forwarding.Link-1-Up",
1019 + "status": "waiting"
1020 + },
1021 + {
1022 + "group": "Net-Setup",
1023 + "isGroup": false,
1024 + "name": "Wait-For-Mininet",
1025 + "status": "waiting"
1026 + },
1027 + {
1028 + "group": "Setup",
1029 + "isGroup": false,
1030 + "name": "Check-Apps-3",
1031 + "status": "waiting"
1032 + },
1033 + {
1034 + "group": "Setup",
1035 + "isGroup": false,
1036 + "name": "Check-Apps-2",
1037 + "status": "waiting"
1038 + },
1039 + {
1040 + "group": "Setup",
1041 + "isGroup": false,
1042 + "name": "Check-Apps-1",
1043 + "status": "waiting"
1044 + },
1045 + {
1046 + "group": "Net-Setup",
1047 + "isGroup": false,
1048 + "name": "Stop-Mininet-If-Needed",
1049 + "status": "waiting"
1050 + },
1051 + {
1052 + "group": "Prerequisites",
1053 + "isGroup": false,
1054 + "name": "Check-Environment",
1055 + "status": "in_progress"
1056 + },
1057 + {
1058 + "isGroup": true,
1059 + "name": "Archetypes",
1060 + "status": "waiting"
1061 + },
1062 + {
1063 + "group": "Host-Intent.Net-Link-Down-Up",
1064 + "isGroup": false,
1065 + "name": "Host-Intent.Link-1-Down",
1066 + "status": "waiting"
1067 + },
1068 + {
1069 + "group": "Net-Setup",
1070 + "isGroup": false,
1071 + "name": "Balance-Masters",
1072 + "status": "waiting"
1073 + },
1074 + {
1075 + "group": "Reactive-Forwarding.Net-Pingall",
1076 + "isGroup": false,
1077 + "name": "Reactive-Forwarding.Ping-All-And-Verify",
1078 + "status": "waiting"
1079 + },
1080 + {
1081 + "group": "Archetypes",
1082 + "isGroup": false,
1083 + "name": "Build-App",
1084 + "status": "waiting"
1085 + }
1086 + ]
1087 +}
...@@ -16,14 +16,14 @@ ...@@ -16,14 +16,14 @@
16 --> 16 -->
17 <html> 17 <html>
18 <head lang="en"> 18 <head lang="en">
19 - <meta charset="UTF-8"> 19 + <meta charset="utf-8">
20 <title>Scenario Test Coordinator</title> 20 <title>Scenario Test Coordinator</title>
21 21
22 - <script src="stc.js"></script>
23 <link rel="stylesheet" href="stc.css"> 22 <link rel="stylesheet" href="stc.css">
23 +
24 + <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
25 + <script src="stc.js"></script>
24 </head> 26 </head>
25 <body> 27 <body>
26 -<h1>Scenario Test Coordinator</h1>
27 -
28 </body> 28 </body>
29 </html> 29 </html>
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -15,5 +15,23 @@ ...@@ -15,5 +15,23 @@
15 */ 15 */
16 16
17 .body { 17 .body {
18 - font-family: Helvetica, Arial; 18 + font-family: Helvetica, Arial, sans-serif;
19 } 19 }
20 +
21 +.node {
22 + stroke: #fff;
23 + stroke-width: 1.5px;
24 +}
25 +
26 +.link {
27 + stroke: #999;
28 + stroke-opacity: .6;
29 +}
30 +
31 +text {
32 + font-family: 'DejaVu Sans', Arial, Helvetica, sans-serif;
33 + stroke: #000;
34 + stroke-width: 0.2;
35 + font-weight: normal;
36 + font-size: 0.6em;
37 +}
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -15,4 +15,134 @@ ...@@ -15,4 +15,134 @@
15 */ 15 */
16 (function () { 16 (function () {
17 17
18 + var ws, flow,
19 + nodes = [],
20 + links = [],
21 + nodeIndexes = {};
22 +
23 + var width = 2400,
24 + height = 2400;
25 +
26 + var color = d3.scale.category20();
27 +
28 + var force = d3.layout.force()
29 + .charge(-820)
30 + .linkDistance(50)
31 + .size([width, height]);
32 +
33 + // Process flow graph layout
34 + function createNode(n) {
35 + nodeIndexes[n.name] = nodes.push(n) - 1;
36 + }
37 +
38 + function createLink(e) {
39 + e.source = nodeIndexes[e.src];
40 + e.target = nodeIndexes[e.dst];
41 + links.push(e);
42 + }
43 +
44 + // Returns the newly computed bounding box of the rectangle
45 + function adjustRectToFitText(n) {
46 + var text = n.select('text'),
47 + box = text.node().getBBox();
48 +
49 + text.attr('text-anchor', 'left')
50 + .attr('y', 2)
51 + .attr('x', 4);
52 +
53 + // add padding
54 + box.x -= 4;
55 + box.width += 8;
56 + box.y -= 2;
57 + box.height += 4;
58 +
59 + n.select("rect").attr(box);
60 + }
61 +
62 + function processFlow() {
63 + var svg = d3.select("body").append("svg")
64 + .attr("width", width)
65 + .attr("height", height);
66 +
67 + flow.steps.forEach(createNode);
68 + flow.requirements.forEach(createLink);
69 +
70 + force
71 + .nodes(nodes)
72 + .links(links)
73 + .start();
74 +
75 + var link = svg.selectAll(".link")
76 + .data(links)
77 + .enter().append("line")
78 + .attr("class", "link")
79 + .style("stroke-width", function(d) { return d.isSoft ? 1 : 2; });
80 +
81 + var node = svg.selectAll(".node")
82 + .data(nodes)
83 + .enter().append("g")
84 + .attr("class", "node")
85 + .call(force.drag);
86 +
87 + node.append("rect")
88 + .attr({ rx: 5, ry:5, width:180, height:18 })
89 + .style("fill", function(d) { return color(d.group); });
90 +
91 + node.append("text").text( function(d) { return d.name; })
92 + .attr({ dy:"1.1em", width:100, height:16, x:4, y:2 });
93 +
94 + node.append("title")
95 + .text(function(d) { return d.name; });
96 +
97 + force.on("tick", function() {
98 + link.attr("x1", function(d) { return d.source.x; })
99 + .attr("y1", function(d) { return d.source.y; })
100 + .attr("x2", function(d) { return d.target.x; })
101 + .attr("y2", function(d) { return d.target.y; });
102 +
103 + node.attr("transform", function(d) { return "translate(" + (d.x - 180/2) + "," + (d.y - 18/2) + ")"; });
104 + });
105 + }
106 +
107 +
108 + // Web socket callbacks
109 +
110 + function handleOpen() {
111 + console.log('WebSocket open');
112 + }
113 +
114 + // Handles the specified (incoming) message using handler bindings.
115 + function handleMessage(msg) {
116 + console.log('rx: ', msg);
117 + evt = JSON.parse(msg.data);
118 + if (evt.event === 'progress') {
119 +
120 + } else if (evt.event === 'log') {
121 +
122 + } else if (evt.event === 'flow') {
123 + flow = evt.payload;
124 + processFlow();
125 + }
126 + }
127 +
128 + function handleClose() {
129 + console.log('WebSocket closed');
130 + }
131 +
132 + if (false) {
133 + d3.json("data.json", function (error, data) {
134 + flow = data;
135 + processFlow();
136 + });
137 + return;
138 + }
139 +
140 + // Open the web-socket
141 + ws = new WebSocket(document.location.href.replace('http:', 'ws:'));
142 + if (ws) {
143 + ws.onopen = handleOpen;
144 + ws.onmessage = handleMessage;
145 + ws.onclose = handleClose;
146 + }
147 +
18 })(); 148 })();
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -52,11 +52,11 @@ public class CompilerTest { ...@@ -52,11 +52,11 @@ public class CompilerTest {
52 System.setProperty("test.dir", TEST_DIR.getAbsolutePath()); 52 System.setProperty("test.dir", TEST_DIR.getAbsolutePath());
53 } 53 }
54 54
55 - public static FileInputStream getStream(String name) throws FileNotFoundException { 55 + static FileInputStream getStream(String name) throws FileNotFoundException {
56 return new FileInputStream(new File(TEST_DIR, name)); 56 return new FileInputStream(new File(TEST_DIR, name));
57 } 57 }
58 58
59 - private static void stageTestResource(String name) throws IOException { 59 + static void stageTestResource(String name) throws IOException {
60 byte[] bytes = toByteArray(CompilerTest.class.getResourceAsStream(name)); 60 byte[] bytes = toByteArray(CompilerTest.class.getResourceAsStream(name));
61 write(bytes, new File(TEST_DIR, name)); 61 write(bytes, new File(TEST_DIR, name));
62 } 62 }
......
...@@ -66,8 +66,8 @@ public class CoordinatorTest { ...@@ -66,8 +66,8 @@ public class CoordinatorTest {
66 66
67 private class Listener implements StepProcessListener { 67 private class Listener implements StepProcessListener {
68 @Override 68 @Override
69 - public void onStart(Step step) { 69 + public void onStart(Step step, String command) {
70 - print("> %s: started", step.name()); 70 + print("> %s: started; %s", step.name(), command);
71 } 71 }
72 72
73 @Override 73 @Override
......
1 +/*
2 + * Copyright 2015 Open Networking Laboratory
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + */
16 +package org.onlab.stc;
17 +
18 +import org.junit.Test;
19 +import org.onlab.stc.MonitorLayout.Box;
20 +
21 +import java.io.IOException;
22 +
23 +import static org.junit.Assert.assertEquals;
24 +import static org.onlab.stc.CompilerTest.getStream;
25 +import static org.onlab.stc.CompilerTest.stageTestResource;
26 +import static org.onlab.stc.MonitorLayout.SLOT_WIDTH;
27 +import static org.onlab.stc.Scenario.loadScenario;
28 +
29 +/**
30 + * Tests of the monitor layout functionality.
31 + */
32 +public class MonitorLayoutTest {
33 +
34 + private MonitorLayout layout;
35 +
36 + private Compiler getCompiler(String name) throws IOException {
37 + stageTestResource(name);
38 + Scenario scenario = loadScenario(getStream(name));
39 + Compiler compiler = new Compiler(scenario);
40 + compiler.compile();
41 + return compiler;
42 + }
43 +
44 + @Test
45 + public void basic() throws IOException {
46 + layout = new MonitorLayout(getCompiler("layout-basic.xml"));
47 + validate(layout, null, 0, 1, 5, 2);
48 + validate(layout, "a", 1, 1, 1, 1, 1, -SLOT_WIDTH / 2);
49 + validate(layout, "b", 2, 2, 1, 1, 0, 0);
50 + validate(layout, "f", 3, 3, 1);
51 +
52 + validate(layout, "g", 1, 1, 4, 1, 1, SLOT_WIDTH / 2);
53 + validate(layout, "c", 2, 1, 1);
54 + validate(layout, "d", 3, 2, 1);
55 + validate(layout, "e", 4, 3, 1);
56 + }
57 +
58 + @Test
59 + public void basicNest() throws IOException {
60 + layout = new MonitorLayout(getCompiler("layout-basic-nest.xml"));
61 + validate(layout, null, 0, 1, 6, 2);
62 + validate(layout, "a", 1, 1, 1, 1, 1, -SLOT_WIDTH / 2);
63 + validate(layout, "b", 2, 2, 1);
64 + validate(layout, "f", 3, 3, 1);
65 +
66 + validate(layout, "g", 1, 1, 5, 1);
67 + validate(layout, "c", 2, 1, 1);
68 +
69 + validate(layout, "gg", 3, 2, 3, 1);
70 + validate(layout, "d", 4, 1, 1);
71 + validate(layout, "e", 5, 2, 1);
72 + }
73 +
74 + @Test
75 + public void staggeredDependencies() throws IOException {
76 + layout = new MonitorLayout(getCompiler("layout-staggered-dependencies.xml"));
77 + validate(layout, null, 0, 1, 7, 4);
78 + validate(layout, "a", 1, 1, 1, 1, 1, -SLOT_WIDTH - SLOT_WIDTH / 2);
79 + validate(layout, "aa", 1, 1, 1, 1, 1, -SLOT_WIDTH / 2);
80 + validate(layout, "b", 2, 2, 1);
81 + validate(layout, "f", 3, 3, 1);
82 +
83 + validate(layout, "g", 1, 1, 5, 2, 1, +SLOT_WIDTH / 2);
84 + validate(layout, "c", 2, 1, 1);
85 +
86 + validate(layout, "gg", 3, 2, 3, 2);
87 + validate(layout, "d", 4, 1, 1);
88 + validate(layout, "dd", 4, 1, 1);
89 + validate(layout, "e", 5, 2, 1);
90 +
91 + validate(layout, "i", 6, 6, 1);
92 + }
93 +
94 + @Test
95 + public void deepNext() throws IOException {
96 + layout = new MonitorLayout(getCompiler("layout-deep-nest.xml"));
97 + validate(layout, null, 0, 1, 7, 6);
98 + validate(layout, "a", 1, 1, 1);
99 + validate(layout, "aa", 1, 1, 1);
100 + validate(layout, "b", 2, 2, 1);
101 + validate(layout, "f", 3, 3, 1);
102 +
103 + validate(layout, "g", 1, 1, 5, 2);
104 + validate(layout, "c", 2, 1, 1);
105 +
106 + validate(layout, "gg", 3, 2, 3, 2);
107 + validate(layout, "d", 4, 1, 1);
108 + validate(layout, "dd", 4, 1, 1);
109 + validate(layout, "e", 5, 2, 1);
110 +
111 + validate(layout, "i", 6, 6, 1);
112 +
113 + validate(layout, "g1", 1, 1, 6, 2);
114 + validate(layout, "g2", 2, 1, 5, 2);
115 + validate(layout, "g3", 3, 1, 4, 2);
116 + validate(layout, "u", 4, 1, 1);
117 + validate(layout, "v", 4, 1, 1);
118 + validate(layout, "w", 5, 2, 1);
119 + validate(layout, "z", 6, 3, 1);
120 + }
121 +
122 +
123 + private void validate(MonitorLayout layout, String name,
124 + int absoluteTier, int tier, int depth, int breadth) {
125 + Box b = layout.get(name);
126 + assertEquals("incorrect absolute tier", absoluteTier, b.absoluteTier());
127 + assertEquals("incorrect tier", tier, b.tier());
128 + assertEquals("incorrect depth", depth, b.depth());
129 + assertEquals("incorrect breadth", breadth, b.breadth());
130 + }
131 +
132 + private void validate(MonitorLayout layout, String name,
133 + int absoluteTier, int tier, int depth, int breadth,
134 + int top, int center) {
135 + validate(layout, name, absoluteTier, tier, depth, breadth);
136 + Box b = layout.get(name);
137 + assertEquals("incorrect top", top, b.top());
138 + assertEquals("incorrect center", center, b.center());
139 + }
140 +
141 + private void validate(MonitorLayout layout, String name,
142 + int absoluteTier, int tier, int depth) {
143 + validate(layout, name, absoluteTier, tier, depth, 1);
144 + }
145 +
146 +}
...\ No newline at end of file ...\ No newline at end of file
...@@ -51,7 +51,7 @@ public class StepProcessorTest { ...@@ -51,7 +51,7 @@ public class StepProcessorTest {
51 @Test 51 @Test
52 public void basics() { 52 public void basics() {
53 Step step = new Step("foo", "ls " + DIR.getAbsolutePath(), null, null, null); 53 Step step = new Step("foo", "ls " + DIR.getAbsolutePath(), null, null, null);
54 - StepProcessor processor = new StepProcessor(step, DIR, delegate); 54 + StepProcessor processor = new StepProcessor(step, DIR, delegate, null);
55 processor.run(); 55 processor.run();
56 assertTrue("should be started", delegate.started); 56 assertTrue("should be started", delegate.started);
57 assertTrue("should be stopped", delegate.stopped); 57 assertTrue("should be stopped", delegate.stopped);
...@@ -65,7 +65,7 @@ public class StepProcessorTest { ...@@ -65,7 +65,7 @@ public class StepProcessorTest {
65 private boolean started, stopped, output; 65 private boolean started, stopped, output;
66 66
67 @Override 67 @Override
68 - public void onStart(Step step) { 68 + public void onStart(Step step, String command) {
69 started = true; 69 started = true;
70 } 70 }
71 71
......
1 +<!--
2 + ~ Copyright 2015 Open Networking Laboratory
3 + ~
4 + ~ Licensed under the Apache License, Version 2.0 (the "License");
5 + ~ you may not use this file except in compliance with the License.
6 + ~ You may obtain a copy of the License at
7 + ~
8 + ~ http://www.apache.org/licenses/LICENSE-2.0
9 + ~
10 + ~ Unless required by applicable law or agreed to in writing, software
11 + ~ distributed under the License is distributed on an "AS IS" BASIS,
12 + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + ~ See the License for the specific language governing permissions and
14 + ~ limitations under the License.
15 + -->
16 +<scenario name="basic-nest">
17 + <step name="a"/>
18 + <step name="b" requires="a"/>
19 + <step name="f" requires="b"/>
20 + <group name="g">
21 + <step name="c"/>
22 + <group name="gg" requires="c">
23 + <step name="d"/>
24 + <step name="e" requires="d"/>
25 + </group>
26 + </group>
27 +</scenario>
...\ No newline at end of file ...\ No newline at end of file
1 +<!--
2 + ~ Copyright 2015 Open Networking Laboratory
3 + ~
4 + ~ Licensed under the Apache License, Version 2.0 (the "License");
5 + ~ you may not use this file except in compliance with the License.
6 + ~ You may obtain a copy of the License at
7 + ~
8 + ~ http://www.apache.org/licenses/LICENSE-2.0
9 + ~
10 + ~ Unless required by applicable law or agreed to in writing, software
11 + ~ distributed under the License is distributed on an "AS IS" BASIS,
12 + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + ~ See the License for the specific language governing permissions and
14 + ~ limitations under the License.
15 + -->
16 +<scenario name="basic">
17 + <step name="a"/>
18 + <step name="b" requires="a"/>
19 + <step name="f" requires="b"/>
20 + <group name="g">
21 + <step name="c"/>
22 + <step name="d" requires="c"/>
23 + <step name="e" requires="d"/>
24 + </group>
25 +</scenario>
...\ No newline at end of file ...\ No newline at end of file
1 +<!--
2 + ~ Copyright 2015 Open Networking Laboratory
3 + ~
4 + ~ Licensed under the Apache License, Version 2.0 (the "License");
5 + ~ you may not use this file except in compliance with the License.
6 + ~ You may obtain a copy of the License at
7 + ~
8 + ~ http://www.apache.org/licenses/LICENSE-2.0
9 + ~
10 + ~ Unless required by applicable law or agreed to in writing, software
11 + ~ distributed under the License is distributed on an "AS IS" BASIS,
12 + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + ~ See the License for the specific language governing permissions and
14 + ~ limitations under the License.
15 + -->
16 +<scenario name="basic-nest">
17 + <step name="a"/>
18 + <step name="aa"/>
19 + <step name="b" requires="a"/>
20 + <step name="f" requires="b,aa"/>
21 + <group name="g">
22 + <step name="c"/>
23 + <group name="gg" requires="c">
24 + <step name="d"/>
25 + <step name="dd" requires="c"/>
26 + <step name="e" requires="d"/>
27 + </group>
28 + </group>
29 + <step name="i" requires="f,g"/>
30 +
31 + <group name="g1">
32 + <group name="g2">
33 + <group name="g3">
34 + <step name="u"/>
35 + <step name="v"/>
36 + <step name="w" requires="u,v"/>
37 + <step name="z" requires="u,w"/>
38 + </group>
39 + </group>
40 + </group>
41 +</scenario>
...\ No newline at end of file ...\ No newline at end of file
1 +<!--
2 + ~ Copyright 2015 Open Networking Laboratory
3 + ~
4 + ~ Licensed under the Apache License, Version 2.0 (the "License");
5 + ~ you may not use this file except in compliance with the License.
6 + ~ You may obtain a copy of the License at
7 + ~
8 + ~ http://www.apache.org/licenses/LICENSE-2.0
9 + ~
10 + ~ Unless required by applicable law or agreed to in writing, software
11 + ~ distributed under the License is distributed on an "AS IS" BASIS,
12 + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + ~ See the License for the specific language governing permissions and
14 + ~ limitations under the License.
15 + -->
16 +<scenario name="basic-nest">
17 + <step name="a"/>
18 + <step name="aa"/>
19 + <step name="b" requires="a"/>
20 + <step name="f" requires="b,aa"/>
21 + <group name="g">
22 + <step name="c"/>
23 + <group name="gg" requires="c">
24 + <step name="d"/>
25 + <step name="dd" requires="c"/>
26 + <step name="e" requires="d"/>
27 + </group>
28 + </group>
29 + <step name="i" requires="f,g"/>
30 +</scenario>
...\ No newline at end of file ...\ No newline at end of file