Adding sequential construct to STC to serially chain sequences based on env properties.

Change-Id: I1df66d2a704309f5450eeca08a6e9b89c02e8346
diff --git a/tools/test/scenarios/sequential-example.xml b/tools/test/scenarios/sequential-example.xml
new file mode 100644
index 0000000..eee32d3
--- /dev/null
+++ b/tools/test/scenarios/sequential-example.xml
@@ -0,0 +1,27 @@
+<!--
+  ~ Copyright 2015 Open Networking Laboratory
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<scenario name="example" description="sequential scenario example">
+    <group name="Wrapup">
+        <!-- 'starts' is a comma-separated list of patterns that name steps starting the current iteration of the sequence -->
+        <!-- 'ends' is a comma-separated list of patterns that name steps ending the previous iteration of the sequence -->
+        <!-- In this example each Final-Check-Logs-(N) will become dependent on Fetch-Logs-(N-1), for N > 1 -->
+        <sequential var="${OC#}" starts="Final-Check-Logs-${#}" ends="Fetch-Logs-${#-1}">
+            <step name="Final-Check-Logs-${#}" exec="onos-check-logs ${OC#}"/>
+            <step name="Fetch-Logs-${#}" exec="onos-fetch-logs ${OC#}"
+                  cwd="${WORKSPACE}/tmp/stc" requires="~^"/>
+        </sequential>
+    </group>
+</scenario>
diff --git a/utils/stc/src/main/java/org/onlab/stc/Compiler.java b/utils/stc/src/main/java/org/onlab/stc/Compiler.java
index add71eb..919cbd5 100644
--- a/utils/stc/src/main/java/org/onlab/stc/Compiler.java
+++ b/utils/stc/src/main/java/org/onlab/stc/Compiler.java
@@ -48,6 +48,7 @@
     private static final String GROUP = "group";
     private static final String STEP = "step";
     private static final String PARALLEL = "parallel";
+    private static final String SEQUENTIAL = "sequential";
     private static final String DEPENDENCY = "dependency";
 
     private static final String LOG_DIR = "[@logDir]";
@@ -59,12 +60,16 @@
     private static final String IF = "[@if]";
     private static final String UNLESS = "[@unless]";
     private static final String VAR = "[@var]";
+    private static final String STARTS = "[@starts]";
+    private static final String ENDS = "[@ends]";
     private static final String FILE = "[@file]";
     private static final String NAMESPACE = "[@namespace]";
 
     static final String PROP_START = "${";
     static final String PROP_END = "}";
+
     private static final String HASH = "#";
+    private static final String HASH_PREV = "#-1";
 
     private final Scenario scenario;
 
@@ -72,7 +77,7 @@
     private final Map<String, Step> inactiveSteps = Maps.newHashMap();
     private final Map<String, String> requirements = Maps.newHashMap();
     private final Set<Dependency> dependencies = Sets.newHashSet();
-    private final List<Integer> parallels = Lists.newArrayList();
+    private final List<Integer> clonables = Lists.newArrayList();
 
     private ProcessFlow processFlow;
     private File logDir;
@@ -175,6 +180,10 @@
         cfg.configurationsAt(PARALLEL)
                 .forEach(c -> processParallelGroup(c, namespace, parentGroup));
 
+        // Scan all sequential groups
+        cfg.configurationsAt(SEQUENTIAL)
+                .forEach(c -> processSequentialGroup(c, namespace, parentGroup));
+
         // Scan all dependencies
         cfg.configurationsAt(DEPENDENCY)
                 .forEach(c -> processDependency(c, namespace));
@@ -309,14 +318,62 @@
 
         int i = 1;
         while (condition(var, i).length() > 0) {
-            parallels.add(0, i);
+            clonables.add(0, i);
             compile(cfg, namespace, parentGroup);
-            parallels.remove(0);
+            clonables.remove(0);
             i++;
         }
     }
 
     /**
+     * Processes a sequential clone group directive.
+     *
+     * @param cfg         hierarchical definition
+     * @param namespace   optional namespace
+     * @param parentGroup optional parent group
+     */
+    private void processSequentialGroup(HierarchicalConfiguration cfg,
+                                        String namespace, Group parentGroup) {
+        String var = cfg.getString(VAR);
+        String starts = cfg.getString(STARTS);
+        String ends = cfg.getString(ENDS);
+        print("sequential var=%s", var);
+
+        int i = 1;
+        while (condition(var, i).length() > 0) {
+            clonables.add(0, i);
+            compile(cfg, namespace, parentGroup);
+            if (i > 1) {
+                processSequentialRequirements(starts, ends, namespace);
+            }
+            clonables.remove(0);
+            i++;
+        }
+    }
+
+    /**
+     * Hooks starts of this sequence tier to the previous tier.
+     *
+     * @param starts    comma-separated list of start steps
+     * @param ends      comma-separated list of end steps
+     * @param namespace optional namespace
+     */
+    private void processSequentialRequirements(String starts, String ends,
+                                               String namespace) {
+        for (String s : split(starts)) {
+            String start = expand(prefix(s, namespace));
+            String reqs = requirements.get(s);
+            for (String n : split(ends)) {
+                boolean isSoft = n.startsWith("~");
+                String name = n.replaceFirst("^~", "");
+                name = (isSoft ? "~" : "") + expand(prefix(name, namespace));
+                reqs = reqs == null ? name : (reqs + "," + name);
+            }
+            requirements.put(start, reqs);
+        }
+    }
+
+    /**
      * Returns the elaborated repetition construct conditional.
      *
      * @param var repetition var property
@@ -413,9 +470,11 @@
             String prop = pString.substring(start + PROP_START.length(), end);
             String value;
             if (prop.equals(HASH)) {
-                value = parallels.get(0).toString();
+                value = Integer.toString(clonables.get(0));
+            } else if (prop.equals(HASH_PREV)) {
+                value = Integer.toString(clonables.get(0) - 1);
             } else if (prop.endsWith(HASH)) {
-                pString = pString.replaceFirst("#}", parallels.get(0).toString() + "}");
+                pString = pString.replaceFirst("#}", clonables.get(0) + "}");
                 last = start;
                 continue;
             } else {
diff --git a/utils/stc/src/test/java/org/onlab/stc/CompilerTest.java b/utils/stc/src/test/java/org/onlab/stc/CompilerTest.java
index d70eff0..59b5530 100644
--- a/utils/stc/src/test/java/org/onlab/stc/CompilerTest.java
+++ b/utils/stc/src/test/java/org/onlab/stc/CompilerTest.java
@@ -69,8 +69,8 @@
         ProcessFlow flow = compiler.processFlow();
 
         assertSame("incorrect scenario", scenario, compiler.scenario());
-        assertEquals("incorrect step count", 24, flow.getVertexes().size());
-        assertEquals("incorrect dependency count", 16, flow.getEdges().size());
+        assertEquals("incorrect step count", 33, flow.getVertexes().size());
+        assertEquals("incorrect dependency count", 26, flow.getEdges().size());
         assertEquals("incorrect logDir",
                      new File(TEST_DIR.getAbsolutePath(), "foo"), compiler.logDir());
 
diff --git a/utils/stc/src/test/resources/org/onlab/stc/scenario.xml b/utils/stc/src/test/resources/org/onlab/stc/scenario.xml
index 34e67fd..533b11e 100644
--- a/utils/stc/src/test/resources/org/onlab/stc/scenario.xml
+++ b/utils/stc/src/test/resources/org/onlab/stc/scenario.xml
@@ -44,4 +44,11 @@
         <step name="ding-${#}" exec="asdads" requires="ping-${#},pong-${#}"/>
         <dependency name="maybe" requires="ding-${#}"/>
     </parallel>
+
+    <sequential var="${TOC#}" requires="alpha" starts="fifi-${#},gigi-${#}" ends="gaga-${#-1}">
+        <step name="fifi-${#}" exec="asdads ${TOC#}"/>
+        <step name="gigi-${#}" exec="asdads"/>
+        <step name="gaga-${#}" exec="asdads" requires="fifi-${#},gigi-${#}"/>
+        <dependency name="maybe" requires="gaga-${#}"/>
+    </sequential>
 </scenario>
\ No newline at end of file