First round of cleanups in optical path provisioner. No more user input for packet/optical mininet script.

Change-Id: Ibbfa6a17a97432da8dee63e9cd15fa6b1c2c1e46
diff --git a/apps/optical/src/main/java/org/onosproject/optical/OpticalPathProvisioner.java b/apps/optical/src/main/java/org/onosproject/optical/OpticalPathProvisioner.java
index c5ec560..6b95d63 100644
--- a/apps/optical/src/main/java/org/onosproject/optical/OpticalPathProvisioner.java
+++ b/apps/optical/src/main/java/org/onosproject/optical/OpticalPathProvisioner.java
@@ -18,6 +18,7 @@
 import com.google.common.collect.Lists;
 import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
 import org.apache.felix.scr.annotations.Reference;
 import org.apache.felix.scr.annotations.ReferenceCardinality;
 import org.onosproject.cluster.ClusterService;
@@ -44,19 +45,23 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.util.Collections;
 import java.util.Iterator;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 
+import static com.google.common.base.Preconditions.checkArgument;
 import static org.onosproject.net.intent.IntentState.INSTALLED;
 
+import static com.google.common.base.Preconditions.checkNotNull;
+
 /**
- * OpticalPathProvisioner listens event notifications from the Intent F/W.
+ * OpticalPathProvisioner listens for event notifications from the Intent F/W.
  * It generates one or more opticalConnectivityIntent(s) and submits (or withdraws) to Intent F/W
  * for adding/releasing capacity at the packet layer.
- *
  */
 
 @Component(immediate = true)
@@ -86,12 +91,12 @@
     private ApplicationId appId;
 
     // TODO use a shared map for distributed operation
-    protected final Map<ConnectPoint, OpticalConnectivityIntent> inStatusTportMap =
+    private final Map<ConnectPoint, OpticalConnectivityIntent> inStatusTportMap =
             new ConcurrentHashMap<>();
-    protected final Map<ConnectPoint, OpticalConnectivityIntent> outStatusTportMap =
+    private final Map<ConnectPoint, OpticalConnectivityIntent> outStatusTportMap =
             new ConcurrentHashMap<>();
 
-    protected final Map<ConnectPoint, Map<ConnectPoint, Intent>> intentMap =
+    private final Map<ConnectPoint, Map<ConnectPoint, Intent>> intentMap =
             new ConcurrentHashMap<>();
 
     private final InternalOpticalPathProvisioner pathProvisioner = new InternalOpticalPathProvisioner();
@@ -101,7 +106,13 @@
         intentService.addListener(pathProvisioner);
         appId = coreService.registerApplication("org.onosproject.optical");
         initTport();
-        log.info("Starting optical path provisioning...");
+        log.info("Started");
+    }
+
+    @Deactivate
+    protected void deactivate() {
+        intentService.removeListener(pathProvisioner);
+        log.info("Stopped");
     }
 
     protected void initTport() {
@@ -119,10 +130,6 @@
         }
     }
 
-    protected void deactivate() {
-        intentService.removeListener(pathProvisioner);
-    }
-
     public class InternalOpticalPathProvisioner implements IntentListener {
         @Override
         public void event(IntentEvent event) {
@@ -132,11 +139,11 @@
                 case INSTALLED:
                     break;
                 case FAILED:
-                    log.info("packet intent {} failed, calling optical path provisioning APP.", event.subject());
+                    log.info("Intent {} failed, calling optical path provisioning app.", event.subject());
                     setupLightpath(event.subject());
                     break;
                 case WITHDRAWN:
-                    log.info("intent {} withdrawn.", event.subject());
+                    log.info("Intent {} withdrawn.", event.subject());
                     //FIXME
                     //teardownLightpath(event.subject());
                     break;
@@ -162,6 +169,7 @@
 
         /**
          * Registers an intent from src to dst.
+         *
          * @param src source point
          * @param dst destination point
          * @param intent intent to be registered
@@ -242,78 +250,124 @@
             }
         }
 
+        /**
+         * Returns list of cross connection points of missing optical path sections.
+         *
+         * Scans the given multi-layer path and looks for sections that use cross connect links.
+         * The ingress and egress points in the optical layer are returned in a list.
+         *
+         * @param path the multi-layer path
+         * @return list of cross connection points on the optical layer
+         */
+        private List<ConnectPoint> getCrossConnectPoints(Path path) {
+            boolean scanning = false;
+            List<ConnectPoint> connectPoints = new LinkedList<ConnectPoint>();
+
+            for (Link link : path.links()) {
+                if (!isCrossConnectLink(link)) {
+                    continue;
+                }
+
+                if (scanning) {
+                    connectPoints.add(checkNotNull(link.src()));
+                    scanning = false;
+                } else {
+                    connectPoints.add(checkNotNull(link.dst()));
+                    scanning = true;
+                }
+            }
+
+            return connectPoints;
+        }
+
+        /**
+         * Checks availability of cross connect points by verifying T port status.
+         * TODO: refactor after rewriting OpticalConnectivityIntentCompiler
+         *
+         * @param crossConnectPoints list of cross connection points
+         * @return true if all cross connect points are available, false otherwise
+         */
+        private boolean checkCrossConnectPoints(List<ConnectPoint> crossConnectPoints) {
+            checkArgument(crossConnectPoints.size() % 2 == 0);
+
+            Iterator<ConnectPoint> itr = crossConnectPoints.iterator();
+
+            while (itr.hasNext()) {
+                // checkArgument at start ensures we'll always have pairs of connect points
+                ConnectPoint src = itr.next();
+                ConnectPoint dst = itr.next();
+
+                if (inStatusTportMap.get(src) != null || outStatusTportMap.get(dst) != null) {
+                    return false;
+                }
+            }
+
+            return true;
+        }
+
+        /**
+         * Scans the list of cross connection points and returns a list of optical connectivity intents
+         * in both directions.
+         *
+         * @param crossConnectPoints list of cross connection points
+         * @return list of optical connectivity intents
+         */
+        private List<Intent> getIntents(List<ConnectPoint> crossConnectPoints) {
+            checkArgument(crossConnectPoints.size() % 2 == 0);
+
+            List<Intent> intents = new LinkedList<Intent>();
+            Iterator<ConnectPoint> itr = crossConnectPoints.iterator();
+
+            while (itr.hasNext()) {
+                // checkArgument at start ensures we'll always have pairs of connect points
+                ConnectPoint src = itr.next();
+                ConnectPoint dst = itr.next();
+
+                // TODO: should have option for bidirectional OpticalConnectivityIntent
+                Intent opticalIntent = OpticalConnectivityIntent.builder()
+                        .appId(appId)
+                        .src(src)
+                        .dst(dst)
+                        .build();
+                Intent opticalIntentRev = OpticalConnectivityIntent.builder()
+                        .appId(appId)
+                        .src(dst)
+                        .dst(src)
+                        .build();
+                intents.add(opticalIntent);
+                intents.add(opticalIntentRev);
+            }
+
+            return intents;
+        }
+
         private List<Intent> getOpticalPath(ConnectPoint ingress, ConnectPoint egress) {
             Set<Path> paths = pathService.getPaths(ingress.deviceId(),
-                                                   egress.deviceId(),
-                                                   new OpticalLinkWeight());
+                    egress.deviceId(),
+                    new OpticalLinkWeight());
 
             if (paths.isEmpty()) {
-                return Lists.newArrayList();
+                return Collections.emptyList();
             }
 
             List<Intent> connectionList = Lists.newArrayList();
 
+            // Iterate over all paths until a suitable one has been found
             Iterator<Path> itrPath = paths.iterator();
             while (itrPath.hasNext()) {
-                boolean usedTportFound = false;
                 Path nextPath = itrPath.next();
-                log.info(nextPath.links().toString()); // TODO drop log level
 
-                Iterator<Link> itrLink = nextPath.links().iterator();
-                while (itrLink.hasNext()) {
-                    ConnectPoint srcWdmPoint, dstWdmPoint;
-                    Link link1 = itrLink.next();
-                    if (!isOpticalLink(link1)) {
-                        continue;
-                    } else {
-                        srcWdmPoint = link1.dst();
-                        dstWdmPoint = srcWdmPoint;
-                    }
+                List<ConnectPoint> crossConnectPoints = getCrossConnectPoints(nextPath);
 
-                    while (itrLink.hasNext()) {
-                        Link link2 = itrLink.next();
-                        if (isOpticalLink(link2)) {
-                            dstWdmPoint = link2.src();
-                        } else {
-                            break;
-                        }
-                    }
-
-                    if (inStatusTportMap.get(srcWdmPoint) != null ||
-                            outStatusTportMap.get(dstWdmPoint) != null) {
-                        usedTportFound = true;
-                        // log.info("used ConnectPoint {} to {} were found", srcWdmPoint, dstWdmPoint);
-                        break;
-                    }
-
-                    Intent opticalIntent = OpticalConnectivityIntent.builder()
-                            .appId(appId)
-                            .src(srcWdmPoint)
-                            .dst(dstWdmPoint)
-                            .build();
-                    Intent opticalIntent2 = OpticalConnectivityIntent.builder()
-                            .appId(appId)
-                            .src(dstWdmPoint)
-                            .dst(srcWdmPoint)
-                            .build();
-                    log.info("Creating optical intent from {} to {}", srcWdmPoint, dstWdmPoint);
-                    log.info("Creating optical intent from {} to {}", dstWdmPoint, srcWdmPoint);
-                    connectionList.add(opticalIntent);
-                    connectionList.add(opticalIntent2);
-
-                    break;
+                // Skip to next path if not all connect points are available
+                if (!checkCrossConnectPoints(crossConnectPoints)) {
+                    continue;
                 }
 
-                if (!usedTportFound) {
-                    break;
-                } else {
-                    // reset the connection list
-                    connectionList = Lists.newArrayList();
-                }
-
+                return getIntents(crossConnectPoints);
             }
 
-            return connectionList;
+            return Collections.emptyList();
         }
 
         private void teardownLightpath(Intent intent) {
@@ -330,26 +384,45 @@
 
     }
 
-    private static boolean isOpticalLink(Link link) {
-        boolean isOptical = false;
-        Link.Type lt = link.type();
-        if (lt == Link.Type.OPTICAL) {
-            isOptical = true;
+    /**
+     * Verifies if given link is cross-connect between packet and optical layer.
+     *
+     * @param link the link
+     * @return true if the link is a cross-connect link
+     */
+    public static boolean isCrossConnectLink(Link link) {
+        if (link.type() != Link.Type.OPTICAL) {
+            return false;
         }
-        return isOptical;
+
+        checkNotNull(link.annotations());
+        checkNotNull(link.annotations().value("optical.type"));
+
+        if (link.annotations().value("optical.type").equals("cross-connect")) {
+            return true;
+        }
+
+        return false;
     }
 
+    /**
+     * Link weight function that emphasizes re-use of packet links.
+     */
     private static class OpticalLinkWeight implements LinkWeight {
         @Override
         public double weight(TopologyEdge edge) {
+            // Ignore inactive links
             if (edge.link().state() == Link.State.INACTIVE) {
-                return -1; // ignore inactive links
+                return -1;
             }
-            if (isOpticalLink(edge.link())) {
-                return 1000;  // optical links
-            } else {
-                return 1;     // packet links
+
+            // Transport links have highest weight
+            if (edge.link().type() == Link.Type.OPTICAL) {
+                return 1000;
             }
+
+            // Packet links
+            return 1;
         }
     }
 
diff --git a/tools/test/topos/opticalUtils.py b/tools/test/topos/opticalUtils.py
index 21dd1f2..116c0cc 100644
--- a/tools/test/topos/opticalUtils.py
+++ b/tools/test/topos/opticalUtils.py
@@ -56,6 +56,7 @@
 import json
 import os
 from time import sleep
+import urllib2
 
 from mininet.node import Switch, RemoteController
 from mininet.topo import Topo
@@ -65,6 +66,10 @@
 from mininet.link import Link, Intf
 from mininet.cli import CLI
 
+# Sleep time and timeout values in seconds
+SLEEP_TIME = .5
+TIMEOUT = 60
+
 class OpticalSwitch(Switch):
     """
     For now, same as Switch class.
@@ -414,17 +419,37 @@
                             intf2 = intfList[ 0 ]
                             intf.node.attach(LINCSwitch.findTap(intf2.node, intf2.node.ports[ intf2 ]))
 
-        info('*** Press ENTER to push Topology.json to onos...\n')
-        raw_input()  # FIXME... we should eventually remove this
+        info('*** Waiting for all devices to be available in ONOS...\n')
+        url = 'http://%s:8181/onos/v1/devices' % LINCSwitch.controllers[0].ip
+        time = 0
+        while True:
+            response = json.load(urllib2.urlopen(url))
+            devs = response.get('devices')
+
+            # Wait for all devices to be registered & available
+            if (len(devices) == len(devs)):
+                for d in devs:
+                    if not d['available']:
+                        continue
+                break
+
+            if (time >= TIMEOUT):
+                error('***ERROR: ONOS did not register devices within %s seconds\n' % TIMEOUT)
+                break
+
+            time += SLEEP_TIME
+            sleep(SLEEP_TIME)
+
         info('*** Pushing Topology.json to ONOS\n')
         output = quietRun('%s/tools/test/bin/onos-topo-cfg %s Topology.json' % (LINCSwitch.onosDir, LINCSwitch.controllers[ 0 ].ip), shell=True)
+
         # successful output contains the two characters '{}'
         # if there is more output than this, there is an issue
         if output.strip('{}'):
-            warn('***WARNING: Could not push topology file to ONOS: %s' % output)
+            warn('***WARNING: Could not push topology file to ONOS: %s\n' % output)
 
     @staticmethod
-    def waitStarted(net, timeout=None):
+    def waitStarted(net, timeout=TIMEOUT):
         "wait until all tap interfaces are available"
         tapCount = 0
         time = 0
@@ -437,11 +462,11 @@
             if str(tapCount) == quietRun('ip addr | grep tap | wc -l', shell=True).strip('\n'):
                 return True
             if timeout:
-                if time >= timeout:
-                    error('***ERROR: Linc OE did not start within %s seconds' % timeout)
+                if time >= TIMEOUT:
+                    error('***ERROR: LINC OE did not start within %s seconds\n' % TIMEOUT)
                     return False
-                time += .5
-            sleep(.5)
+                time += SLEEP_TIME
+            sleep(SLEEP_TIME)
 
     @staticmethod
     def shutdownOE():