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
Showing
26 changed files
with
787 additions
and
67 deletions
| ... | @@ -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 | ... | ... |
tools/test/scenarios/example.xml
0 → 100644
| 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 | +} |
This diff is collapsed. Click to expand it.
| ... | @@ -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. | ... | ... |
utils/stc/src/main/resources/data.json
0 → 100644
This diff is collapsed. Click to expand it.
| ... | @@ -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 |
-
Please register or login to post a comment