Use k-shortest paths in optical path provisioner (ONOS-6289).

Change-Id: Ida22317e674e1b30dc8b385e30ff347ad040b8c8
diff --git a/apps/newoptical/BUCK b/apps/newoptical/BUCK
index ac24ee1..29f0fa9 100644
--- a/apps/newoptical/BUCK
+++ b/apps/newoptical/BUCK
@@ -8,6 +8,7 @@
 
 TEST_DEPS = [
   '//lib:TEST_ADAPTERS',
+  '//utils/osgi:onlab-osgi-tests',
 ]
 
 osgi_jar_with_tests (
diff --git a/apps/newoptical/pom.xml b/apps/newoptical/pom.xml
index daab122..c1b97df 100644
--- a/apps/newoptical/pom.xml
+++ b/apps/newoptical/pom.xml
@@ -114,6 +114,13 @@
             <scope>test</scope>
         </dependency>
 
+        <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onlab-osgi</artifactId>
+            <classifier>tests</classifier>
+            <scope>test</scope>
+        </dependency>
+
     </dependencies>
 
     <build>
diff --git a/apps/newoptical/src/main/java/org/onosproject/newoptical/OpticalPathProvisioner.java b/apps/newoptical/src/main/java/org/onosproject/newoptical/OpticalPathProvisioner.java
index 79d1d26..245a90f 100644
--- a/apps/newoptical/src/main/java/org/onosproject/newoptical/OpticalPathProvisioner.java
+++ b/apps/newoptical/src/main/java/org/onosproject/newoptical/OpticalPathProvisioner.java
@@ -21,23 +21,23 @@
 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.Modified;
+import org.apache.felix.scr.annotations.Property;
 import org.apache.felix.scr.annotations.Reference;
 import org.apache.felix.scr.annotations.ReferenceCardinality;
 import org.apache.felix.scr.annotations.Service;
+import org.onlab.graph.DefaultEdgeWeigher;
+import org.onlab.graph.ScalarWeight;
+import org.onlab.graph.Weight;
 import org.onlab.util.Bandwidth;
 import org.onlab.util.GuavaCollectors;
 import org.onlab.util.KryoNamespace;
+import org.onlab.util.Tools;
 import org.onosproject.cluster.ClusterService;
 import org.onosproject.core.ApplicationId;
 import org.onosproject.core.CoreService;
-import org.onosproject.event.ListenerTracker;
-import org.onosproject.net.optical.OchPort;
-import org.onosproject.net.optical.OduCltPort;
-import org.onosproject.newoptical.api.OpticalConnectivityId;
-import org.onosproject.newoptical.api.OpticalPathEvent;
-import org.onosproject.newoptical.api.OpticalPathListener;
-import org.onosproject.newoptical.api.OpticalPathService;
 import org.onosproject.event.AbstractListenerManager;
+import org.onosproject.event.ListenerTracker;
 import org.onosproject.mastership.MastershipService;
 import org.onosproject.net.ConnectPoint;
 import org.onosproject.net.Device;
@@ -47,6 +47,8 @@
 import org.onosproject.net.Path;
 import org.onosproject.net.Port;
 import org.onosproject.net.config.NetworkConfigService;
+import org.onosproject.net.config.basics.BandwidthCapacity;
+import org.onosproject.net.config.basics.BasicLinkConfig;
 import org.onosproject.net.device.DeviceService;
 import org.onosproject.net.intent.Intent;
 import org.onosproject.net.intent.IntentEvent;
@@ -58,16 +60,21 @@
 import org.onosproject.net.link.LinkEvent;
 import org.onosproject.net.link.LinkListener;
 import org.onosproject.net.link.LinkService;
-import org.onosproject.net.config.basics.BandwidthCapacity;
-import org.onosproject.net.config.basics.BasicLinkConfig;
+import org.onosproject.net.optical.OchPort;
+import org.onosproject.net.optical.OduCltPort;
 import org.onosproject.net.resource.ContinuousResource;
 import org.onosproject.net.resource.Resource;
 import org.onosproject.net.resource.ResourceAllocation;
 import org.onosproject.net.resource.ResourceService;
 import org.onosproject.net.resource.Resources;
-import org.onosproject.net.topology.LinkWeight;
-import org.onosproject.net.topology.PathService;
+import org.onosproject.net.topology.LinkWeigher;
 import org.onosproject.net.topology.TopologyEdge;
+import org.onosproject.net.topology.TopologyService;
+import org.onosproject.net.topology.TopologyVertex;
+import org.onosproject.newoptical.api.OpticalConnectivityId;
+import org.onosproject.newoptical.api.OpticalPathEvent;
+import org.onosproject.newoptical.api.OpticalPathListener;
+import org.onosproject.newoptical.api.OpticalPathService;
 import org.onosproject.store.serializers.KryoNamespaces;
 import org.onosproject.store.service.AtomicCounter;
 import org.onosproject.store.service.ConsistentMap;
@@ -77,6 +84,7 @@
 import org.onosproject.store.service.Serializer;
 import org.onosproject.store.service.StorageService;
 import org.onosproject.store.service.Versioned;
+import org.osgi.service.component.ComponentContext;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -84,6 +92,7 @@
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Dictionary;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
@@ -91,6 +100,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
+import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
 import java.util.stream.Collectors;
@@ -127,7 +137,7 @@
     protected IntentService intentService;
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
-    protected PathService pathService;
+    protected TopologyService topologyService;
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected CoreService coreService;
@@ -153,6 +163,11 @@
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected ResourceService resourceService;
 
+    private static final String MAX_PATHS = "maxPaths";
+    private static final int DEFAULT_MAX_PATHS = 10;
+    @Property(name = MAX_PATHS, intValue = DEFAULT_MAX_PATHS,
+            label = "Maximum number of paths to consider for path provisioning")
+    private int maxPaths = DEFAULT_MAX_PATHS;
 
     private ApplicationId appId;
 
@@ -192,7 +207,7 @@
             .register(KryoNamespaces.API);
 
     @Activate
-    protected void activate() {
+    protected void activate(ComponentContext context) {
         deviceService = opticalView(deviceService);
         appId = coreService.registerApplication("org.onosproject.newoptical");
 
@@ -225,6 +240,8 @@
 
         linkPathMap.addListener(storeListener);
 
+        readComponentConfiguration(context);
+
         log.info("Started");
     }
 
@@ -237,6 +254,22 @@
         log.info("Stopped");
     }
 
+    @Modified
+    public void modified(ComponentContext context) {
+        readComponentConfiguration(context);
+    }
+
+    /**
+     * Extracts properties from the component configuration context.
+     *
+     * @param context the component context
+     */
+    private void readComponentConfiguration(ComponentContext context) {
+        Dictionary<?, ?> properties = context.getProperties();
+        maxPaths = Tools.getIntegerProperty(properties, MAX_PATHS, DEFAULT_MAX_PATHS);
+        log.info("Configured. Maximum paths to consider is configured to {}", maxPaths);
+    }
+
     @Override
     public Collection<OpticalConnectivity> listConnectivity() {
         return connectivityMap.values().stream()
@@ -270,32 +303,28 @@
         checkNotNull(egress);
         log.info("setupConnectivity({}, {}, {}, {})", ingress, egress, bandwidth, latency);
 
-        bandwidth = (bandwidth == null) ? NO_BW_REQUIREMENT : bandwidth;
+        Bandwidth bw = (bandwidth == null) ? NO_BW_REQUIREMENT : bandwidth;
 
-        Set<Path> paths = pathService.getPaths(ingress.deviceId(), egress.deviceId(),
+        Stream<Path> paths = topologyService.getKShortestPaths(
+                topologyService.currentTopology(),
+                ingress.deviceId(), egress.deviceId(),
                 new BandwidthLinkWeight(bandwidth));
-        if (paths.isEmpty()) {
-            log.warn("Unable to find multi-layer path.");
-            return null;
+
+        // Path service calculates from node to node, we're only interested in port to port
+        Optional<OpticalConnectivityId> id =
+                paths.filter(p -> p.src().equals(ingress) && p.dst().equals(egress))
+                        .limit(maxPaths)
+                        .map(p -> setupPath(p, bw, latency))
+                        .filter(Objects::nonNull)
+                        .findFirst();
+
+        if (id.isPresent()) {
+            log.info("Assigned OpticalConnectivityId: {}", id);
+        } else {
+            log.error("setupConnectivity({}, {}, {}, {}) failed.", ingress, egress, bandwidth, latency);
         }
 
-        // Search path with available cross connect points
-        for (Path path : paths) {
-            // Path service calculates from node to node, we're only interested in port to port
-            if (!path.src().equals(ingress) || !path.dst().equals(egress)) {
-                continue;
-            }
-
-            OpticalConnectivityId id = setupPath(path, bandwidth, latency);
-            if (id != null) {
-                log.info("Assigned OpticalConnectivityId: {}", id);
-                return id;
-            }
-        }
-
-        log.error("setupConnectivity({}, {}, {}, {}) failed.", ingress, egress, bandwidth, latency);
-
-        return null;
+        return id.orElse(null);
     }
 
     /*
@@ -364,7 +393,6 @@
             return null;
         }
 
-
         // create set of PacketLinkRealizedByOptical
         Set<PacketLinkRealizedByOptical> packetLinks = createPacketLinkSet(crossConnectPoints,
                 intents, crossConnectPointMap);
@@ -647,7 +675,7 @@
         return linkDiscoveryEnabled(cp1) && linkDiscoveryEnabled(cp2);
     }
 
-    private class BandwidthLinkWeight implements LinkWeight {
+    private class BandwidthLinkWeight extends DefaultEdgeWeigher<TopologyVertex, TopologyEdge> implements LinkWeigher {
         private Bandwidth bandwidth = null;
 
         public BandwidthLinkWeight(Bandwidth bandwidth) {
@@ -655,42 +683,38 @@
         }
 
         @Override
-        public double weight(TopologyEdge edge) {
+        public Weight weight(TopologyEdge edge) {
             Link l = edge.link();
 
             // Avoid inactive links
             if (l.state() == Link.State.INACTIVE) {
                 log.trace("{} is not active", l);
-                return -1.0;
+                return ScalarWeight.NON_VIABLE_WEIGHT;
             }
 
             // Avoid cross connect links with used ports
             if (isCrossConnectLink(l) && usedCrossConnectLinkSet.contains(l)) {
                 log.trace("Cross connect {} in use", l);
-                return -1.0;
+                return ScalarWeight.NON_VIABLE_WEIGHT;
             }
 
             // Check availability of bandwidth
             if (bandwidth != null && !NO_BW_REQUIREMENT.equals(bandwidth)) {
                 if (hasEnoughBandwidth(l.src()) && hasEnoughBandwidth(l.dst())) {
-                    return 1.0;
+                    return new ScalarWeight(1.0);
                 } else {
                     log.trace("Not enough bandwidth on {}", l);
-                    return -1.0;
+                    return ScalarWeight.NON_VIABLE_WEIGHT;
                 }
+            // Allow everything else
             } else {
-                // Use everything except our own indirect links
-                if (l.type() == Link.Type.INDIRECT) {
-                    return -1.0;
-                } else {
-                    return 1.0;
-                }
+                return new ScalarWeight(1.0);
             }
         }
 
         private boolean hasEnoughBandwidth(ConnectPoint cp) {
             if (cp.elementId() instanceof DeviceId) {
-                Device device =  deviceService.getDevice(cp.deviceId());
+                Device device = deviceService.getDevice(cp.deviceId());
                 Device.Type type = device.type();
 
                 if (isTransportLayer(type)) {
@@ -711,7 +735,7 @@
                         return resourceService.isAvailable(resource);
                     } catch (Exception e) {
                         log.error("Resource service failed checking availability of {}",
-                                  resource, e);
+                                resource, e);
                         throw e;
                     }
                 }
@@ -720,7 +744,6 @@
         }
     }
 
-
     public class InternalIntentListener implements IntentListener {
         @Override
         public void event(IntentEvent event) {
diff --git a/apps/newoptical/src/main/java/org/onosproject/newoptical/api/OpticalPathService.java b/apps/newoptical/src/main/java/org/onosproject/newoptical/api/OpticalPathService.java
index 15837c7..5092d67 100644
--- a/apps/newoptical/src/main/java/org/onosproject/newoptical/api/OpticalPathService.java
+++ b/apps/newoptical/src/main/java/org/onosproject/newoptical/api/OpticalPathService.java
@@ -43,7 +43,7 @@
      * @param egress    egress port
      * @param bandwidth required bandwidth. No bandwidth is assured if null.
      * @param latency   required latency. No latency is assured if null.
-     * @return ID of created connectivity if successful. null otherwise.
+     * @return id of created connectivity if successful, null otherwise.
      */
     OpticalConnectivityId setupConnectivity(ConnectPoint ingress, ConnectPoint egress,
                                             Bandwidth bandwidth, Duration latency);
@@ -54,7 +54,7 @@
      * @param path      multi-layer path along which connectivity will be set up
      * @param bandwidth required bandwidth. No bandwidth is assured if null.
      * @param latency   required latency. No latency is assured if null.
-     * @return true if successful. false otherwise.
+     * @return id of created connectivity if successful, null otherwise.
      */
     OpticalConnectivityId setupPath(Path path, Bandwidth bandwidth, Duration latency);
 
diff --git a/apps/newoptical/src/test/java/org/onosproject/newoptical/OpticalPathProvisionerTest.java b/apps/newoptical/src/test/java/org/onosproject/newoptical/OpticalPathProvisionerTest.java
index 78a58b5..8a98864 100644
--- a/apps/newoptical/src/test/java/org/onosproject/newoptical/OpticalPathProvisionerTest.java
+++ b/apps/newoptical/src/test/java/org/onosproject/newoptical/OpticalPathProvisionerTest.java
@@ -20,6 +20,8 @@
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
+import org.onlab.graph.ScalarWeight;
+import org.onlab.osgi.ComponentContextAdapter;
 import org.onlab.packet.ChassisId;
 import org.onlab.packet.IpAddress;
 import org.onlab.util.Bandwidth;
@@ -45,7 +47,6 @@
 import org.onosproject.net.DefaultPort;
 import org.onosproject.net.Device;
 import org.onosproject.net.DeviceId;
-import org.onosproject.net.ElementId;
 import org.onosproject.net.Link;
 import org.onosproject.net.MastershipRole;
 import org.onosproject.net.OchSignal;
@@ -75,8 +76,9 @@
 import org.onosproject.net.resource.ResourceId;
 import org.onosproject.net.resource.ResourceListener;
 import org.onosproject.net.resource.ResourceService;
-import org.onosproject.net.topology.LinkWeight;
-import org.onosproject.net.topology.PathServiceAdapter;
+import org.onosproject.net.topology.LinkWeigher;
+import org.onosproject.net.topology.Topology;
+import org.onosproject.net.topology.TopologyServiceAdapter;
 import org.onosproject.newoptical.api.OpticalConnectivityId;
 import org.onosproject.newoptical.api.OpticalPathEvent;
 import org.onosproject.newoptical.api.OpticalPathListener;
@@ -98,7 +100,6 @@
 import java.time.Duration;
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -173,7 +174,7 @@
     protected TestListener listener = new TestListener();
     protected TestDeviceService deviceService;
     protected TestLinkService linkService;
-    protected TestPathService pathService;
+    protected TestTopologyService topologyService;
     protected TestIntentService intentService;
     protected TestMastershipService mastershipService;
     protected TestClusterService clusterService;
@@ -208,7 +209,7 @@
         linkService.links.addAll(Stream.of(LINK1, LINK2, LINK3, LINK4, LINK5, LINK6)
             .collect(Collectors.toList()));
 
-        this.pathService = new TestPathService();
+        this.topologyService = new TestTopologyService();
         this.intentService = new TestIntentService();
         this.mastershipService = new TestMastershipService();
         this.clusterService = new TestClusterService();
@@ -224,7 +225,7 @@
         this.target = new OpticalPathProvisioner();
         target.coreService = new TestCoreService();
         target.intentService = this.intentService;
-        target.pathService = this.pathService;
+        target.topologyService = this.topologyService;
         target.linkService = this.linkService;
         target.mastershipService = this.mastershipService;
         target.clusterService = this.clusterService;
@@ -235,7 +236,7 @@
         injectEventDispatcher(target, new TestEventDispatcher());
         target.addListener(listener);
 
-        target.activate();
+        target.activate(new ComponentContextAdapter());
 
         // To overwrite opticalView-ed deviceService
         target.deviceService = this.deviceService;
@@ -271,9 +272,9 @@
         assertNotNull(cid);
 
         // Checks path computation is called as expected
-        assertEquals(1, pathService.edges.size());
-        assertEquals(CP12.deviceId(), pathService.edges.get(0).getKey());
-        assertEquals(CP71.deviceId(), pathService.edges.get(0).getValue());
+        assertEquals(1, topologyService.edges.size());
+        assertEquals(CP12.deviceId(), topologyService.edges.get(0).getKey());
+        assertEquals(CP71.deviceId(), topologyService.edges.get(0).getValue());
 
         // Checks intents are installed as expected
         assertEquals(1, intentService.submitted.size());
@@ -292,7 +293,7 @@
         Duration latency = Duration.ofMillis(10);
         List<Link> links = Stream.of(LINK1, LINK2, LINK3, LINK4, LINK5, LINK6)
                 .collect(Collectors.toList());
-        Path path = new DefaultPath(PROVIDER_ID, links, 0);
+        Path path = new DefaultPath(PROVIDER_ID, links, new ScalarWeight(0));
 
         OpticalConnectivityId cid = target.setupPath(path, bandwidth, latency);
         assertNotNull(cid);
@@ -494,26 +495,25 @@
         }
     }
 
-    private static class TestPathService extends PathServiceAdapter {
+    private static class TestTopologyService extends TopologyServiceAdapter {
         List<Pair<DeviceId, DeviceId>> edges = new ArrayList<>();
 
         @Override
-        public Set<Path> getPaths(ElementId src, ElementId dst, LinkWeight weight) {
+        public Stream<Path> getKShortestPaths(Topology topology, DeviceId src, DeviceId dst, LinkWeigher weigher) {
             if (!(src instanceof DeviceId && dst instanceof DeviceId)) {
-                return Collections.emptySet();
+                return Stream.empty();
             }
 
-            edges.add(Pair.of((DeviceId) src, (DeviceId) dst));
+            edges.add(Pair.of(src, dst));
 
             Set<Path> paths = new HashSet<>();
             List<Link> links = Stream.of(LINK1, LINK2, LINK3, LINK4, LINK5, LINK6)
                     .collect(Collectors.toList());
-            paths.add(new DefaultPath(PROVIDER_ID, links, 0));
+            paths.add(new DefaultPath(PROVIDER_ID, links, new ScalarWeight(0)));
 
             // returns paths containing single path
-            return paths;
+            return paths.stream();
         }
-
     }
 
     private static class TestIntentService extends IntentServiceAdapter {
@@ -712,7 +712,6 @@
                 return newValue;
             }
 
-
             @Override
             public Set<Map.Entry<K, Versioned<V>>> entrySet() {
                 return map.entrySet();
@@ -765,7 +764,6 @@
                 return null;
             }
         }
-
     }
 
     private static class TestDeviceService extends DeviceServiceAdapter {
diff --git a/core/api/src/test/java/org/onosproject/net/topology/TopologyServiceAdapter.java b/core/api/src/test/java/org/onosproject/net/topology/TopologyServiceAdapter.java
index 79ec17c..bbc8fde 100644
--- a/core/api/src/test/java/org/onosproject/net/topology/TopologyServiceAdapter.java
+++ b/core/api/src/test/java/org/onosproject/net/topology/TopologyServiceAdapter.java
@@ -23,6 +23,7 @@
 
 import java.util.Map;
 import java.util.Set;
+import java.util.stream.Stream;
 
 /**
  * Test adapter for topology service.
@@ -83,6 +84,17 @@
     }
 
     @Override
+    public Set<Path> getKShortestPaths(Topology topology, DeviceId src, DeviceId dst,
+                                       LinkWeigher weigher, int maxPaths) {
+        return null;
+    }
+
+    @Override
+    public Stream<Path> getKShortestPaths(Topology topology, DeviceId src, DeviceId dst, LinkWeigher weigher) {
+        return null;
+    }
+
+    @Override
     public boolean isInfrastructure(Topology topology,
                                     ConnectPoint connectPoint) {
         return false;