Allow static lambda and port mappings (ONOS-2067).
Fix bug in device resource store.

Change-Id: I219a4de9ec803b3d142a6b957868f64dc599fa24
diff --git a/core/api/src/main/java/org/onosproject/net/AnnotationKeys.java b/core/api/src/main/java/org/onosproject/net/AnnotationKeys.java
index 8427d76..adea3af 100644
--- a/core/api/src/main/java/org/onosproject/net/AnnotationKeys.java
+++ b/core/api/src/main/java/org/onosproject/net/AnnotationKeys.java
@@ -85,6 +85,10 @@
      */
     public static final String ROUTER_ID = "routerId";
 
+    public static final String STATIC_LAMBDA = "staticLambda";
+
+    public static final String STATIC_PORT = "staticPort";
+
     /**
      * Returns the value annotated object for the specified annotation key.
      * The annotated value is expected to be String that can be parsed as double.
diff --git a/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/OpticalCircuitIntentCompiler.java b/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/OpticalCircuitIntentCompiler.java
index fc62b25..557288c 100644
--- a/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/OpticalCircuitIntentCompiler.java
+++ b/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/OpticalCircuitIntentCompiler.java
@@ -23,8 +23,8 @@
 import org.apache.felix.scr.annotations.ReferenceCardinality;
 import org.onosproject.core.ApplicationId;
 import org.onosproject.core.CoreService;
+import org.onosproject.net.AnnotationKeys;
 import org.onosproject.net.ConnectPoint;
-import org.onosproject.net.DeviceId;
 import org.onosproject.net.OchPort;
 import org.onosproject.net.OduCltPort;
 import org.onosproject.net.OduSignalType;
@@ -138,7 +138,7 @@
                     .signalType(OduSignalType.ODU4)
                     .bidirectional(intent.isBidirectional())
                     .build();
-            intents.add(connIntent);
+            intentService.submit(connIntent);
         }
 
         // Create optical circuit intent
@@ -155,7 +155,7 @@
         circuitIntent = new FlowRuleIntent(appId, rules, intent.resources());
 
         // Save circuit to connectivity intent mapping
-        deviceResourceService.requestMapping(connIntent.id(), circuitIntent.id());
+        deviceResourceService.requestMapping(connIntent.id(), intent.id());
         intents.add(circuitIntent);
 
         return intents;
@@ -163,16 +163,43 @@
 
     /**
      * Checks if current allocations on given resource can satisfy request.
+     * If the resource is null, return true.
      *
      * @param request
      * @param resource
      * @return
      */
     private boolean isAvailable(Intent request, IntentId resource) {
+        if (resource == null) {
+            return true;
+        }
+
         Set<IntentId> mapping = deviceResourceService.getMapping(resource);
 
-        // TODO: hardcoded 10 x 10G
-        return mapping.size() < 10;
+        if (mapping == null) {
+            return true;
+        }
+
+        // TODO: hardcoded 80% utilization
+        return mapping.size() < 8;
+    }
+
+    private boolean isAllowed(OpticalCircuitIntent circuitIntent, OpticalConnectivityIntent connIntent) {
+        ConnectPoint srcStaticPort = staticPort(circuitIntent.getSrc());
+        if (srcStaticPort != null) {
+            if (!srcStaticPort.equals(connIntent.getSrc())) {
+                return false;
+            }
+        }
+
+        ConnectPoint dstStaticPort = staticPort(circuitIntent.getDst());
+        if (dstStaticPort != null) {
+            if (!dstStaticPort.equals(connIntent.getDst())) {
+                return false;
+            }
+        }
+
+        return true;
     }
 
     /**
@@ -191,7 +218,13 @@
 
             ConnectPoint src = circuitIntent.getSrc();
             ConnectPoint dst = circuitIntent.getDst();
-            if (!src.equals(connIntent.getSrc()) && !dst.equals(connIntent.getDst())) {
+            // Ignore if the intents don't have identical src and dst devices
+            if (!src.deviceId().equals(connIntent.getSrc().deviceId()) &&
+                    !dst.deviceId().equals(connIntent.getDst().deviceId())) {
+                continue;
+            }
+
+            if (!isAllowed(circuitIntent, connIntent)) {
                 continue;
             }
 
@@ -203,21 +236,44 @@
         return null;
     }
 
-    private OchPort findAvailableOchPort(DeviceId deviceId, OpticalCircuitIntent circuitIntent) {
-        List<Port> ports = deviceService.getPorts(deviceId);
+    private ConnectPoint staticPort(ConnectPoint connectPoint) {
+        Port port = deviceService.getPort(connectPoint.deviceId(), connectPoint.port());
+
+        String staticPort = port.annotations().value(AnnotationKeys.STATIC_PORT);
+
+        // FIXME: need a better way to match the port
+        if (staticPort != null) {
+            for (Port p : deviceService.getPorts(connectPoint.deviceId())) {
+                if (staticPort.equals(p.number().name())) {
+                    return new ConnectPoint(p.element().id(), p.number());
+                }
+            }
+        }
+
+        return null;
+    }
+
+    private OchPort findAvailableOchPort(ConnectPoint oduPort, OpticalCircuitIntent circuitIntent) {
+        // First see if the port mappings are constrained
+        ConnectPoint ochCP = staticPort(oduPort);
+
+        if (ochCP != null) {
+            OchPort ochPort = (OchPort) deviceService.getPort(ochCP.deviceId(), ochCP.port());
+            IntentId intentId = deviceResourceService.getAllocations(ochPort);
+            if (isAvailable(circuitIntent, intentId)) {
+                return ochPort;
+            }
+        }
+
+        // No port constraints, so find any port that works
+        List<Port> ports = deviceService.getPorts(oduPort.deviceId());
 
         for (Port port : ports) {
             if (!(port instanceof OchPort)) {
                 continue;
             }
 
-            // Port is not used
             IntentId intentId = deviceResourceService.getAllocations(port);
-            if (intentId == null) {
-                return (OchPort) port;
-            }
-
-            // Port is used but has free resources
             if (isAvailable(circuitIntent, intentId)) {
                 return (OchPort) port;
             }
@@ -226,16 +282,14 @@
         return null;
     }
 
-    // TODO: Add constraints for OduClt to OCh port mappings
-    // E.g., ports need to belong to same line card.
     private Pair<OchPort, OchPort> findPorts(OpticalCircuitIntent intent) {
 
-        OchPort srcPort = findAvailableOchPort(intent.getSrc().deviceId(), intent);
+        OchPort srcPort = findAvailableOchPort(intent.getSrc(), intent);
         if (srcPort == null) {
             return null;
         }
 
-        OchPort dstPort = findAvailableOchPort(intent.getDst().deviceId(), intent);
+        OchPort dstPort = findAvailableOchPort(intent.getDst(), intent);
         if (dstPort == null) {
             return null;
         }
diff --git a/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/OpticalConnectivityIntentCompiler.java b/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/OpticalConnectivityIntentCompiler.java
index 13a2b6b..e5a8e3b 100644
--- a/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/OpticalConnectivityIntentCompiler.java
+++ b/core/net/src/main/java/org/onosproject/net/intent/impl/compiler/OpticalConnectivityIntentCompiler.java
@@ -26,8 +26,10 @@
 import org.apache.felix.scr.annotations.Reference;
 import org.apache.felix.scr.annotations.ReferenceCardinality;
 import org.onlab.util.Frequency;
+import org.onosproject.net.AnnotationKeys;
 import org.onosproject.net.ChannelSpacing;
 import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
 import org.onosproject.net.GridType;
 import org.onosproject.net.Link;
 import org.onosproject.net.OchPort;
@@ -123,17 +125,26 @@
 
         // Use first path that can be successfully reserved
         for (Path path : paths) {
-            // Request and reserve lambda on path
-            LinkResourceAllocations linkAllocs = assignWavelength(intent, path);
-            if (linkAllocs == null) {
-                continue;
+
+            // Static or dynamic lambda allocation
+            LambdaResourceAllocation lambdaAlloc;
+            String staticLambda = srcPort.annotations().value(AnnotationKeys.STATIC_LAMBDA);
+            if (staticLambda != null) {
+                // FIXME: need to actually reserve the lambda
+                lambdaAlloc = new LambdaResourceAllocation(LambdaResource.valueOf(Integer.parseInt(staticLambda)));
+            } else {
+                // Request and reserve lambda on path
+                LinkResourceAllocations linkAllocs = assignWavelength(intent, path);
+                if (linkAllocs == null) {
+                    continue;
+                }
+                lambdaAlloc = getWavelength(path, linkAllocs);
             }
 
             OmsPort omsPort = (OmsPort) deviceService.getPort(path.src().deviceId(), path.src().port());
 
             // Create installable optical path intent
-            LambdaResourceAllocation lambdaAlloc = getWavelength(path, linkAllocs);
-            OchSignal ochSignal = getOchSignal(lambdaAlloc, omsPort.minFrequency(), omsPort.grid());
+            OchSignal ochSignal = getOchSignal(lambdaAlloc, omsPort.maxFrequency(), omsPort.grid());
             // Only support fixed grid for now
             OchSignalType signalType = OchSignalType.FIXED_GRID;
 
@@ -188,7 +199,10 @@
 
         LinkResourceAllocations allocations = linkResourceService.requestResources(request.build());
 
-        checkWavelengthContinuity(allocations, path);
+        if (!checkWavelengthContinuity(allocations, path)) {
+            linkResourceService.releaseResources(allocations);
+            return null;
+        }
 
         return allocations;
     }
@@ -229,15 +243,15 @@
      * Convert lambda resource allocation in OCh signal.
      *
      * @param alloc lambda resource allocation
-     * @param minFrequency minimum frequency
+     * @param maxFrequency maximum frequency
      * @param grid grid spacing frequency
      * @return OCh signal
      */
-    private OchSignal getOchSignal(LambdaResourceAllocation alloc, Frequency minFrequency, Frequency grid) {
+    private OchSignal getOchSignal(LambdaResourceAllocation alloc, Frequency maxFrequency, Frequency grid) {
         int channel = alloc.lambda().toInt();
 
         // Calculate center frequency
-        Frequency centerFrequency = minFrequency.add(grid.multiply(channel)).add(grid.floorDivision(2));
+        Frequency centerFrequency = maxFrequency.subtract(grid.multiply(channel - 1));
 
         // Build OCh signal object
         int spacingMultiplier = (int) (centerFrequency.subtract(OchSignal.CENTER_FREQUENCY).asHz() / grid.asHz());
@@ -248,6 +262,23 @@
         return ochSignal;
     }
 
+    private ConnectPoint staticPort(ConnectPoint connectPoint) {
+        Port port = deviceService.getPort(connectPoint.deviceId(), connectPoint.port());
+
+        String staticPort = port.annotations().value(AnnotationKeys.STATIC_PORT);
+
+        // FIXME: need a better way to match the port
+        if (staticPort != null) {
+            for (Port p : deviceService.getPorts(connectPoint.deviceId())) {
+                if (staticPort.equals(p.number().name())) {
+                    return new ConnectPoint(p.element().id(), p.number());
+                }
+            }
+        }
+
+        return null;
+    }
+
     /**
      * Calculates optical paths in WDM topology.
      *
@@ -260,13 +291,29 @@
         LinkWeight weight = new LinkWeight() {
             @Override
             public double weight(TopologyEdge edge) {
+                // Disregard inactive or non-optical links
                 if (edge.link().state() == Link.State.INACTIVE) {
                     return -1;
                 }
                 if (edge.link().type() != Link.Type.OPTICAL) {
                     return -1;
                 }
-                //return edge.link().annotations().value("optical.type").equals("WDM") ? +1 : -1;
+                // Adhere to static port mappings
+                DeviceId srcDeviceId = edge.link().src().deviceId();
+                if (srcDeviceId.equals(intent.getSrc().deviceId())) {
+                    ConnectPoint srcStaticPort = staticPort(intent.getSrc());
+                    if (srcStaticPort != null) {
+                        return srcStaticPort.equals(edge.link().src()) ? 1 : -1;
+                    }
+                }
+                DeviceId dstDeviceId = edge.link().dst().deviceId();
+                if (dstDeviceId.equals(intent.getDst().deviceId())) {
+                    ConnectPoint dstStaticPort = staticPort(intent.getDst());
+                    if (dstStaticPort != null) {
+                        return dstStaticPort.equals(edge.link().dst()) ? 1 : -1;
+                    }
+                }
+
                 return 1;
             }
         };
diff --git a/core/store/dist/src/main/java/org/onosproject/store/resource/impl/ConsistentDeviceResourceStore.java b/core/store/dist/src/main/java/org/onosproject/store/resource/impl/ConsistentDeviceResourceStore.java
index c2299ef..bc45583 100644
--- a/core/store/dist/src/main/java/org/onosproject/store/resource/impl/ConsistentDeviceResourceStore.java
+++ b/core/store/dist/src/main/java/org/onosproject/store/resource/impl/ConsistentDeviceResourceStore.java
@@ -165,7 +165,13 @@
 
     @Override
     public Set<IntentId> getMapping(IntentId intentId) {
-        return intentMapping.get(intentId).value();
+        Versioned<Set<IntentId>> result = intentMapping.get(intentId);
+
+        if (result == null) {
+            return result.value();
+        }
+
+        return null;
     }
 
     @Override
@@ -174,10 +180,11 @@
 
 
         if (versionedIntents == null) {
-            intentMapping.put(keyIntentId, Collections.singleton(valIntentId));
+            Set<IntentId> newSet = new HashSet<>();
+            newSet.add(valIntentId);
+            intentMapping.put(keyIntentId, newSet);
         } else {
             versionedIntents.value().add(valIntentId);
-            intentMapping.put(keyIntentId, versionedIntents.value());
         }
 
         return true;
diff --git a/web/api/src/main/java/org/onosproject/rest/resources/ConfigProvider.java b/web/api/src/main/java/org/onosproject/rest/resources/ConfigProvider.java
index 55a14f3..296337f 100644
--- a/web/api/src/main/java/org/onosproject/rest/resources/ConfigProvider.java
+++ b/web/api/src/main/java/org/onosproject/rest/resources/ConfigProvider.java
@@ -214,15 +214,28 @@
     private void parsePorts(DeviceId deviceId, JsonNode nodes) {
         List<PortDescription> ports = new ArrayList<>();
         for (JsonNode node : nodes) {
-            ports.add(parsePort(node));
+            ports.add(parsePort(deviceId, node));
         }
         deviceProviderService.updatePorts(deviceId, ports);
     }
 
     // Parses the given node with port information.
-    private PortDescription parsePort(JsonNode node) {
+    private PortDescription parsePort(DeviceId deviceId, JsonNode node) {
         Port.Type type = Port.Type.valueOf(node.path("type").asText("COPPER"));
-        PortNumber port = portNumber(node.path("port").asLong(0));
+        // TL1-based ports have a name
+        PortNumber port = null;
+        if (node.has("name")) {
+            for (Port p : deviceService.getPorts(deviceId)) {
+                if (p.number().name().equals(node.get("name").asText())) {
+                    port = p.number();
+                    break;
+                }
+            }
+        } else {
+            port = portNumber(node.path("port").asLong(0));
+        }
+
+        checkNotNull(port);
         String portName = Strings.emptyToNull(port.name());
         SparseAnnotations annotations  = null;
         if (portName != null) {
@@ -239,6 +252,22 @@
                 return new OmsPortDescription(port, node.path("enabled").asBoolean(true),
                                               CENTER, CENTER.add(TOTAL),
                                               Frequency.ofGHz(100), annotations);
+            case ODUCLT:
+                annotations = annotations(node.get("annotations"));
+                OduCltPort oduCltPort = (OduCltPort) deviceService.getPort(deviceId, port);
+                return new OduCltPortDescription(port, node.path("enabled").asBoolean(true),
+                        oduCltPort.signalType(), annotations);
+            case OCH:
+                annotations = annotations(node.get("annotations"));
+                OchPort ochPort = (OchPort) deviceService.getPort(deviceId, port);
+                return new OchPortDescription(port, node.path("enabled").asBoolean(true),
+                        ochPort.signalType(), ochPort.isTunable(),
+                        ochPort.lambda(), annotations);
+            case OMS:
+                annotations = annotations(node.get("annotations"));
+                OmsPort omsPort = (OmsPort) deviceService.getPort(deviceId, port);
+                return new OmsPortDescription(port, node.path("enabled").asBoolean(true),
+                        omsPort.minFrequency(), omsPort.maxFrequency(), omsPort.grid(), annotations);
             default:
                 log.warn("{}: Unsupported Port Type");
         }