ONOS-6306 Lamda resource allocation aware PCE

Change-Id: I9c6cc729c7bbfdc68f591f868e8d7143f207d5d3
diff --git a/apps/optical-model/src/main/java/org/onosproject/net/optical/intent/impl/compiler/OpticalConnectivityIntentCompiler.java b/apps/optical-model/src/main/java/org/onosproject/net/optical/intent/impl/compiler/OpticalConnectivityIntentCompiler.java
index f361e99..1fb4969 100644
--- a/apps/optical-model/src/main/java/org/onosproject/net/optical/intent/impl/compiler/OpticalConnectivityIntentCompiler.java
+++ b/apps/optical-model/src/main/java/org/onosproject/net/optical/intent/impl/compiler/OpticalConnectivityIntentCompiler.java
@@ -46,6 +46,7 @@
 import org.onosproject.net.resource.Resource;
 import org.onosproject.net.resource.ResourceService;
 import org.onosproject.net.resource.Resources;
+import org.onosproject.net.topology.AdapterLinkWeigher;
 import org.onosproject.net.topology.LinkWeight;
 import org.onosproject.net.topology.Topology;
 import org.onosproject.net.topology.TopologyEdge;
@@ -54,7 +55,9 @@
 import org.slf4j.LoggerFactory;
 
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
@@ -63,6 +66,7 @@
 import java.util.stream.Stream;
 
 import static com.google.common.base.Preconditions.checkArgument;
+import static org.onosproject.net.OchSignal.toFlexGrid;
 import static org.onosproject.net.optical.device.OpticalDeviceServiceView.opticalView;
 
 /**
@@ -129,41 +133,79 @@
         resources.add(dstPortResource);
 
         // Calculate available light paths
-        Set<Path> paths = getOpticalPaths(intent);
-
-        if (paths.isEmpty()) {
-            throw new OpticalIntentCompilationException("Unable to find suitable lightpath for intent " + intent);
-        }
+        Stream<Path> paths = getOpticalPaths(intent);
 
         // Static or dynamic lambda allocation
         String staticLambda = srcPort.annotations().value(AnnotationKeys.STATIC_LAMBDA);
         OchPort srcOchPort = (OchPort) srcPort;
         OchPort dstOchPort = (OchPort) dstPort;
 
-        Path firstPath = paths.iterator().next();
         // FIXME: need to actually reserve the lambda for static lambda's
         // static lambda case: early return
         if (staticLambda != null) {
-            allocateResources(intent, resources);
 
             OchSignal lambda = new OchSignal(Frequency.ofHz(Long.parseLong(staticLambda)),
-                    srcOchPort.lambda().channelSpacing(),
-                    srcOchPort.lambda().slotGranularity());
-            return ImmutableList.of(createIntent(intent, firstPath, lambda));
+                                             srcOchPort.lambda().channelSpacing(),
+                                             srcOchPort.lambda().slotGranularity());
+            log.debug("Using staticalluy assigned lambda : {}", lambda);
+
+            List<Resource> res = new ArrayList<>();
+
+            Path satPath = paths.filter(path -> {
+                // FIXME trimming both ends (staticLambda assigned ports)
+                // Proper fix is to let device advertise static lambda as resource.
+                LinkedList<Link> links = new LinkedList<>(path.links());
+                links.pollFirst();
+                links.pollLast();
+
+                List<Resource> r = convertToResources(links, toFlexGrid(lambda));
+                if (r.stream().allMatch(resourceService::isAvailable)) {
+                    // FIXME bad practice to have side-effect during stream
+                    res.addAll(r);
+                    return true;
+                }
+                return false;
+            }).findFirst().orElse(null);
+
+            if (satPath == null) {
+                // FIXME doing something very wrong to preserve old behavior
+                // as a fallback
+                log.warn("No feasible path between {}-{} using {}",
+                         src, dst, lambda);
+
+                satPath = getOpticalPaths(intent).iterator().next();
+                if (satPath == null) {
+                    throw new OpticalIntentCompilationException(
+                        "Unable to find suitable lightpath for intent " + intent);
+                }
+                return ImmutableList.of(createIntent(intent, satPath, lambda));
+            }
+
+            resources.addAll(res);
+            allocateResources(intent, resources);
+
+            return ImmutableList.of(createIntent(intent, satPath, lambda));
         }
 
         // FIXME: also check destination OCh port
         // non-tunable case: early return
         if (!srcOchPort.isTunable() || !dstOchPort.isTunable()) {
-            allocateResources(intent, resources);
-
+            Path firstPath = paths.findAny().orElse(null);
+            if (firstPath == null) {
+                throw new OpticalIntentCompilationException("Unable to find suitable lightpath for intent " + intent);
+            }
             OchSignal lambda = srcOchPort.lambda();
+            // TODO apply the same as static lambda case above
+            // - pick feasible path
+            // - allocate resources
+            //resources.addAll(convertToResources(firstPath.links(), toFlexGrid(lambda)));
+            allocateResources(intent, resources);
             return ImmutableList.of(createIntent(intent, firstPath, lambda));
         }
 
         // remaining cases
         // Use first path that the required resources are available
-        Optional<Map.Entry<Path, List<OchSignal>>> found = paths.stream()
+        Optional<Map.Entry<Path, List<OchSignal>>> found = paths
                 .map(path -> Maps.immutableEntry(path, findFirstAvailableOch(path)))
                 .filter(entry -> !entry.getValue().isEmpty())
                 .filter(entry -> convertToResources(entry.getKey().links(),
@@ -192,7 +234,6 @@
                 .key(parentIntent.key())
                 .src(parentIntent.getSrc())
                 .dst(parentIntent.getDst())
-                // calling paths.iterator().next() is safe because of non-empty set
                 .path(path)
                 .lambda(lambda)
                 .signalType(signalType)
@@ -205,7 +246,12 @@
         // reserve all of required resources
         List<ResourceAllocation> allocations = resourceService.allocate(intent.id(), resources);
         if (allocations.isEmpty()) {
-            log.info("Resource allocation for {} failed (resource request: {})", intent, resources);
+            log.error("Resource allocation for {} failed (resource request: {})", intent.id(), resources);
+            if (log.isDebugEnabled()) {
+                log.debug("requested resources:\n\t{}", resources.stream()
+                                  .map(Resource::toString)
+                                  .collect(Collectors.joining("\n\t")));
+            }
             throw new OpticalIntentCompilationException("Unable to allocate resources: " + resources);
         }
     }
@@ -219,7 +265,7 @@
         return findFirstLambda(lambdas, slotCount());
     }
 
-    private List<Resource> convertToResources(List<Link> links, List<OchSignal> lambda) {
+    private List<Resource> convertToResources(List<Link> links, Collection<OchSignal> lambda) {
         return links.stream()
                 .flatMap(x -> Stream.of(
                         Resources.discrete(x.src().deviceId(), x.src().port()).resource(),
@@ -299,7 +345,7 @@
      * @param intent optical connectivity intent
      * @return set of paths in WDM topology
      */
-    private Set<Path> getOpticalPaths(OpticalConnectivityIntent intent) {
+    private Stream<Path> getOpticalPaths(OpticalConnectivityIntent intent) {
         // Route in WDM topology
         Topology topology = topologyService.currentTopology();
         LinkWeight weight = new LinkWeight() {
@@ -335,9 +381,21 @@
 
         ConnectPoint start = intent.getSrc();
         ConnectPoint end = intent.getDst();
-        Set<Path> paths = topologyService.getPaths(topology, start.deviceId(),
-                end.deviceId(), weight);
-
+        Stream<Path> paths = topologyService.getKShortestPaths(topology,
+                                                            start.deviceId(),
+                                                            end.deviceId(),
+                                                            AdapterLinkWeigher.adapt(weight));
+        if (log.isDebugEnabled()) {
+            return paths
+                    .map(path -> {
+                        // no-op map stage to add debug logging
+                        log.debug("Candidate path: {}",
+                                  path.links().stream()
+                                      .map(lk -> lk.src() + "-" + lk.dst())
+                                      .collect(Collectors.toList()));
+                        return path;
+                    });
+        }
         return paths;
     }
 }