Deprecate IndexedLambda and remove from optical intent compiler.
Allow drivers to report any spectral grid. Bugfixes.

ONOS-3495

Change-Id: Ied946660d48e482c1746d1e87735498b1637bb4b
diff --git a/apps/optical/src/main/java/org/onosproject/optical/testapp/LambdaForwarding.java b/apps/optical/src/main/java/org/onosproject/optical/testapp/LambdaForwarding.java
index 05d6f12..da3cc13 100644
--- a/apps/optical/src/main/java/org/onosproject/optical/testapp/LambdaForwarding.java
+++ b/apps/optical/src/main/java/org/onosproject/optical/testapp/LambdaForwarding.java
@@ -15,11 +15,6 @@
  */
 package org.onosproject.optical.testapp;
 
-import static org.slf4j.LoggerFactory.getLogger;
-
-import java.util.HashMap;
-import java.util.Map;
-
 import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Deactivate;
 import org.apache.felix.scr.annotations.Reference;
@@ -44,9 +39,17 @@
 import org.onosproject.net.flow.instructions.Instructions;
 import org.slf4j.Logger;
 
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.slf4j.LoggerFactory.getLogger;
+
 /**
  * Sample reactive forwarding application.
+ *
+ * @deprecated in Emu (ONOS 1.4).
  */
+@Deprecated
 //@Component(immediate = true)
 public class LambdaForwarding {
 
diff --git a/core/api/src/main/java/org/onosproject/net/ChannelSpacing.java b/core/api/src/main/java/org/onosproject/net/ChannelSpacing.java
index 4da9d6b..42183aa 100644
--- a/core/api/src/main/java/org/onosproject/net/ChannelSpacing.java
+++ b/core/api/src/main/java/org/onosproject/net/ChannelSpacing.java
@@ -25,7 +25,7 @@
     CHL_50GHZ(50),          // 50 GHz
     CHL_25GHZ(25),          // 25 GHz
     CHL_12P5GHZ(12.5),      // 12.5 GHz
-    CHL_6P25GHZ(6.5);       // 6.25 GHz
+    CHL_6P25GHZ(6.25);       // 6.25 GHz
 
     private final Frequency frequency;
 
diff --git a/core/api/src/main/java/org/onosproject/net/IndexedLambda.java b/core/api/src/main/java/org/onosproject/net/IndexedLambda.java
index 6b5fa65..84aa370 100644
--- a/core/api/src/main/java/org/onosproject/net/IndexedLambda.java
+++ b/core/api/src/main/java/org/onosproject/net/IndexedLambda.java
@@ -19,7 +19,10 @@
 
 /**
  * Implementation of Lambda simply designated by an index number of wavelength.
+ *
+ * @deprecated in Emu (ONOS 1.4).
  */
+@Deprecated
 public class IndexedLambda implements Lambda {
 
     private final long index;
diff --git a/core/api/src/main/java/org/onosproject/net/OchSignal.java b/core/api/src/main/java/org/onosproject/net/OchSignal.java
index 3adc908..d0dfdf3 100644
--- a/core/api/src/main/java/org/onosproject/net/OchSignal.java
+++ b/core/api/src/main/java/org/onosproject/net/OchSignal.java
@@ -16,10 +16,18 @@
 package org.onosproject.net;
 
 import com.google.common.base.MoreObjects;
+import com.google.common.collect.ImmutableSet;
 import org.onlab.util.Frequency;
 import org.onlab.util.Spectrum;
 
+import java.util.List;
 import java.util.Objects;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
 
 import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.common.base.Preconditions.checkNotNull;
@@ -33,14 +41,13 @@
  */
 public class OchSignal implements Lambda {
 
-    public static final Frequency FLEX_GRID_SLOT = Frequency.ofGHz(12.5);
+    public static final Set<Integer> FIXED_GRID_SLOT_GRANULARITIES = ImmutableSet.of(1, 2, 4, 8);
     private static final GridType DEFAULT_OCH_GRIDTYPE = GridType.DWDM;
     private static final ChannelSpacing DEFAULT_CHANNEL_SPACING = ChannelSpacing.CHL_50GHZ;
 
-
     private final GridType gridType;
     private final ChannelSpacing channelSpacing;
-    // Frequency = 193.1 THz + spacingMultiplier * channelSpacing
+    // Nominal central frequency = 193.1 THz + spacingMultiplier * channelSpacing
     private final int spacingMultiplier;
     // Slot width = slotGranularity * 12.5 GHz
     private final int slotGranularity;
@@ -118,9 +125,9 @@
     }
 
     /**
-     * Returns slow width granularity.
+     * Returns slot width granularity.
      *
-     * @return slow width granularity
+     * @return slot width granularity
      */
     public int slotGranularity() {
         return slotGranularity;
@@ -141,7 +148,56 @@
      * @return slot width
      */
     public Frequency slotWidth() {
-        return FLEX_GRID_SLOT.multiply(slotGranularity);
+        return ChannelSpacing.CHL_12P5GHZ.frequency().multiply(slotGranularity);
+    }
+
+    /**
+     * Convert fixed grid OCh signal to sorted set of flex grid slots with 6.25 GHz spacing and 12.5 GHz slot width.
+     *
+     * @param ochSignal fixed grid lambda
+     * @return sorted set of flex grid OCh lambdas
+     */
+    public static SortedSet<OchSignal> toFlexGrid(OchSignal ochSignal) {
+        checkArgument(ochSignal.gridType() != GridType.FLEX);
+        checkArgument(ochSignal.channelSpacing() != ChannelSpacing.CHL_6P25GHZ);
+        checkArgument(FIXED_GRID_SLOT_GRANULARITIES.contains(ochSignal.slotGranularity()));
+
+        int startMultiplier = (int) (1 - ochSignal.slotGranularity() +
+                ochSignal.spacingMultiplier() * ochSignal.channelSpacing().frequency().asHz() /
+                        ChannelSpacing.CHL_6P25GHZ.frequency().asHz());
+
+        Supplier<SortedSet<OchSignal>> supplier = () -> new TreeSet<>(new DefaultOchSignalComparator());
+        return IntStream.range(0, ochSignal.slotGranularity())
+                .mapToObj(i -> new OchSignal(GridType.FLEX, ChannelSpacing.CHL_6P25GHZ, startMultiplier + 2 * i, 1))
+                .collect(Collectors.toCollection(supplier));
+    }
+
+    /**
+     * Convert list of lambdas with flex grid 6.25 GHz spacing and 12.5 GHz width into fixed grid OCh signal.
+     *
+     * @param lambdas list of flex grid lambdas in sorted order
+     * @param spacing desired fixed grid spacing
+     * @return fixed grid lambda
+     */
+    public static OchSignal toFixedGrid(List<OchSignal> lambdas, ChannelSpacing spacing) {
+        // Number of slots of 12.5 GHz that fit into requested spacing
+        int ratio = (int) (spacing.frequency().asHz() / ChannelSpacing.CHL_12P5GHZ.frequency().asHz());
+        checkArgument(lambdas.size() == ratio);
+        lambdas.forEach(x -> checkArgument(x.gridType() == GridType.FLEX));
+        lambdas.forEach(x -> checkArgument(x.channelSpacing() == ChannelSpacing.CHL_6P25GHZ));
+        lambdas.forEach(x -> checkArgument(x.slotGranularity() == 1));
+        // Consecutive lambdas (multiplier increments by 2 because spacing is 6.25 GHz but slot width is 12.5 GHz)
+        IntStream.range(1, lambdas.size())
+                .forEach(i -> checkArgument(
+                        lambdas.get(i).spacingMultiplier() == lambdas.get(i - 1).spacingMultiplier() + 2));
+        // Is center frequency compatible with requested spacing
+        Frequency center = lambdas.get(ratio / 2).centralFrequency().subtract(ChannelSpacing.CHL_6P25GHZ.frequency());
+        checkArgument(Spectrum.CENTER_FREQUENCY.subtract(center).asHz() % spacing.frequency().asHz() == 0);
+
+        // Multiplier sits in middle of given lambdas, then convert from 6.25 to requested spacing
+        int spacingMultiplier = (lambdas.get(ratio / 2).spacingMultiplier() + 1) / (ratio * 2);
+
+        return new OchSignal(GridType.DWDM, spacing, spacingMultiplier, lambdas.size());
     }
 
     @Override
diff --git a/core/api/src/main/java/org/onosproject/net/flow/criteria/IndexedLambdaCriterion.java b/core/api/src/main/java/org/onosproject/net/flow/criteria/IndexedLambdaCriterion.java
index 88a6fe1..bc612cd 100644
--- a/core/api/src/main/java/org/onosproject/net/flow/criteria/IndexedLambdaCriterion.java
+++ b/core/api/src/main/java/org/onosproject/net/flow/criteria/IndexedLambdaCriterion.java
@@ -25,6 +25,7 @@
 /**
  * Implementation of indexed lambda criterion.
  */
+@Deprecated
 public class IndexedLambdaCriterion implements Criterion {
 
     private final IndexedLambda lambda;
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 e017ac5..2d4545c 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,14 +26,14 @@
 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.DefaultOchSignalComparator;
 import org.onosproject.net.DeviceId;
-import org.onosproject.net.IndexedLambda;
 import org.onosproject.net.Link;
 import org.onosproject.net.OchPort;
 import org.onosproject.net.OchSignal;
 import org.onosproject.net.OchSignalType;
-import org.onosproject.net.OmsPort;
 import org.onosproject.net.Path;
 import org.onosproject.net.Port;
 import org.onosproject.net.device.DeviceService;
@@ -53,6 +53,7 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 import java.util.Set;
@@ -64,11 +65,12 @@
 /**
  * An intent compiler for {@link org.onosproject.net.intent.OpticalConnectivityIntent}.
  */
-// For now, remove component designation until dependency on the new resource manager is available.
 @Component(immediate = true)
 public class OpticalConnectivityIntentCompiler implements IntentCompiler<OpticalConnectivityIntent> {
 
     protected static final Logger log = LoggerFactory.getLogger(OpticalConnectivityIntentCompiler.class);
+    // By default, allocate 50 GHz lambdas (4 slots of 12.5 GHz) for each intent.
+    private static final int SLOT_COUNT = 4;
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected IntentExtensionService intentManager;
@@ -133,16 +135,15 @@
                         srcOchPort.lambda().channelSpacing(),
                         srcOchPort.lambda().slotGranularity());
             } else if (!srcOchPort.isTunable() || !dstOchPort.isTunable()) {
-                // FIXME: also check OCh port
+                // FIXME: also check destination OCh port
                 ochSignal = srcOchPort.lambda();
             } else {
                 // Request and reserve lambda on path
-                IndexedLambda lambda = assignWavelength(intent, path);
-                if (lambda == null) {
+                List<OchSignal> lambdas = assignWavelength(intent, path);
+                if (lambdas.isEmpty()) {
                     continue;
                 }
-                OmsPort omsPort = (OmsPort) deviceService.getPort(path.src().deviceId(), path.src().port());
-                ochSignal = new OchSignal((int) lambda.index(), omsPort.maxFrequency(), omsPort.grid());
+                ochSignal = OchSignal.toFixedGrid(lambdas, ChannelSpacing.CHL_50GHZ);
             }
 
             // Create installable optical path intent
@@ -174,48 +175,77 @@
      * @param path path in WDM topology
      * @return first available lambda allocated
      */
-    private IndexedLambda assignWavelength(Intent intent, Path path) {
-        Set<IndexedLambda> lambdas = findCommonLambdasOverLinks(path.links());
+    private List<OchSignal> assignWavelength(Intent intent, Path path) {
+        Set<OchSignal> lambdas = findCommonLambdasOverLinks(path.links());
         if (lambdas.isEmpty()) {
-            return null;
+            return Collections.emptyList();
         }
 
-        IndexedLambda minLambda = findFirstLambda(lambdas);
+        List<OchSignal> minLambda = findFirstLambda(lambdas, slotCount());
         List<ResourcePath> lambdaResources = path.links().stream()
                 .flatMap(x -> Stream.of(
                         ResourcePath.discrete(x.src().deviceId(), x.src().port()),
                         ResourcePath.discrete(x.dst().deviceId(), x.dst().port())
                 ))
-                .map(x -> x.child(minLambda))
+                .flatMap(x -> minLambda.stream().map(l -> x.child(l)))
                 .collect(Collectors.toList());
 
         List<ResourceAllocation> allocations = resourceService.allocate(intent.id(), lambdaResources);
         if (allocations.isEmpty()) {
             log.info("Resource allocation for {} failed (resource request: {})", intent, lambdaResources);
-            return null;
+            return Collections.emptyList();
         }
 
         return minLambda;
     }
 
-    private Set<IndexedLambda> findCommonLambdasOverLinks(List<Link> links) {
+    /**
+     * Get the number of 12.5 GHz slots required for the path.
+     *
+     * For now this returns a constant value of 4 (i.e., fixed grid 50 GHz slot),
+     * but in the future can depend on optical reach, line rate, transponder port capabilities, etc.
+     *
+     * @return number of slots
+     */
+    private int slotCount() {
+        return SLOT_COUNT;
+    }
+
+    private Set<OchSignal> findCommonLambdasOverLinks(List<Link> links) {
         return links.stream()
                 .flatMap(x -> Stream.of(
                         ResourcePath.discrete(x.src().deviceId(), x.src().port()),
                         ResourcePath.discrete(x.dst().deviceId(), x.dst().port())
                 ))
                 .map(resourceService::getAvailableResources)
-                .map(x -> Iterables.filter(x, r -> r.last() instanceof IndexedLambda))
-                .map(x -> Iterables.transform(x, r -> (IndexedLambda) r.last()))
-                .map(x -> (Set<IndexedLambda>) ImmutableSet.copyOf(x))
+                .map(x -> Iterables.filter(x, r -> r.last() instanceof OchSignal))
+                .map(x -> Iterables.transform(x, r -> (OchSignal) r.last()))
+                .map(x -> (Set<OchSignal>) ImmutableSet.copyOf(x))
                 .reduce(Sets::intersection)
                 .orElse(Collections.emptySet());
     }
 
-    private IndexedLambda findFirstLambda(Set<IndexedLambda> lambdas) {
-        return lambdas.stream()
-                .findFirst()
-                .get();
+    /**
+     * Returns list of consecutive resources in given set of lambdas.
+     *
+     * @param lambdas list of lambdas
+     * @param count number of consecutive lambdas to return
+     * @return list of consecutive lambdas
+     */
+    private List<OchSignal> findFirstLambda(Set<OchSignal> lambdas, int count) {
+        // Sort available lambdas
+        List<OchSignal> lambdaList = new ArrayList<>(lambdas);
+        lambdaList.sort(new DefaultOchSignalComparator());
+
+        // Look ahead by count and ensure spacing multiplier is as expected (i.e., no gaps)
+        for (int i = 0; i < lambdaList.size() - count; i++) {
+            if (lambdaList.get(i).spacingMultiplier() + 2 * count ==
+                    lambdaList.get(i + count).spacingMultiplier()) {
+                return lambdaList.subList(i, i + count);
+            }
+        }
+
+        return Collections.emptyList();
     }
 
     private ConnectPoint staticPort(ConnectPoint connectPoint) {
diff --git a/core/net/src/main/java/org/onosproject/net/newresource/impl/ResourceDeviceListener.java b/core/net/src/main/java/org/onosproject/net/newresource/impl/ResourceDeviceListener.java
index 46aeeb0..a1e545f 100644
--- a/core/net/src/main/java/org/onosproject/net/newresource/impl/ResourceDeviceListener.java
+++ b/core/net/src/main/java/org/onosproject/net/newresource/impl/ResourceDeviceListener.java
@@ -19,6 +19,7 @@
 import org.onlab.packet.MplsLabel;
 import org.onlab.packet.VlanId;
 import org.onlab.util.ItemNotFoundException;
+import org.onosproject.net.DefaultOchSignalComparator;
 import org.onosproject.net.Device;
 import org.onosproject.net.DeviceId;
 import org.onosproject.net.Port;
@@ -44,7 +45,9 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.SortedSet;
+import java.util.TreeSet;
 import java.util.concurrent.ExecutorService;
+import java.util.function.Supplier;
 import java.util.stream.Collectors;
 import java.util.stream.IntStream;
 
@@ -199,7 +202,10 @@
             }
             LambdaQuery query = handler.behaviour(LambdaQuery.class);
             if (query != null) {
-                return query.queryLambdas(port);
+                Supplier<SortedSet<OchSignal>> supplier = () -> new TreeSet<>(new DefaultOchSignalComparator());
+                return query.queryLambdas(port).stream()
+                        .flatMap(x -> OchSignal.toFlexGrid(x).stream())
+                        .collect(Collectors.toCollection(supplier));
             } else {
                 return Collections.emptySortedSet();
             }
diff --git a/drivers/src/main/java/org/onosproject/driver/handshaker/CalientFiberSwitchHandshaker.java b/drivers/src/main/java/org/onosproject/driver/handshaker/CalientFiberSwitchHandshaker.java
index 270008f..2f591d3 100644
--- a/drivers/src/main/java/org/onosproject/driver/handshaker/CalientFiberSwitchHandshaker.java
+++ b/drivers/src/main/java/org/onosproject/driver/handshaker/CalientFiberSwitchHandshaker.java
@@ -51,6 +51,7 @@
 import java.util.SortedSet;
 import java.util.TreeSet;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Supplier;
 import java.util.stream.Collectors;
 import java.util.stream.IntStream;
 
@@ -209,13 +210,12 @@
                 ChannelSpacing.CHL_12P5GHZ.frequency().asHz();
         long stopSpacingMultiplier = Spectrum.O_BAND_MAX.subtract(Spectrum.CENTER_FREQUENCY).asHz() /
                 ChannelSpacing.CHL_12P5GHZ.frequency().asHz();
-        List<OchSignal> lambdas = IntStream.rangeClosed((int) startSpacingMultiplier, (int) stopSpacingMultiplier)
-                .mapToObj(x -> new OchSignal(GridType.FLEX, ChannelSpacing.CHL_12P5GHZ, x, 1))
-                .collect(Collectors.toList());
+        Supplier<SortedSet<OchSignal>> supplier = () -> new TreeSet<>(new DefaultOchSignalComparator());
 
-        SortedSet<OchSignal> result = new TreeSet<>(new DefaultOchSignalComparator());
-        result.addAll(lambdas);
-
-        return result;
+        // Only consider odd values for the multiplier (for easy mapping to fixed grid)
+        return IntStream.rangeClosed((int) startSpacingMultiplier, (int) stopSpacingMultiplier)
+                .filter(i -> i % 2 == 1)
+                .mapToObj(i -> new OchSignal(GridType.FLEX, ChannelSpacing.CHL_6P25GHZ, i, 1))
+                .collect(Collectors.toCollection(supplier));
     }
 }
diff --git a/drivers/src/main/java/org/onosproject/driver/handshaker/OfOpticalSwitchImplLinc13.java b/drivers/src/main/java/org/onosproject/driver/handshaker/OfOpticalSwitchImplLinc13.java
index 23378e9..67a8f0e 100644
--- a/drivers/src/main/java/org/onosproject/driver/handshaker/OfOpticalSwitchImplLinc13.java
+++ b/drivers/src/main/java/org/onosproject/driver/handshaker/OfOpticalSwitchImplLinc13.java
@@ -59,6 +59,7 @@
 import java.util.SortedSet;
 import java.util.TreeSet;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Supplier;
 import java.util.stream.Collectors;
 import java.util.stream.IntStream;
 
@@ -74,7 +75,7 @@
  * As LINC implements custom OF optical extensions (in contrast to the final standard as specified in
  * ONF TS-022 (March 15, 2015), we need to rewrite flow stat requests and flow mods in {@link #sendMsg(OFMessage)}.
  *
- * LINC exposes OchSignal resources: 80 lambdas of 50 GHz around ITU-T G.694.1 center frequency 193.1 GHz.
+ * LINC exposes OchSignal resources: 80 lambdas of 50 GHz (fixed grid) around ITU-T G.694.1 center frequency 193.1 GHz.
  *
  */
 public class OfOpticalSwitchImplLinc13
@@ -224,8 +225,9 @@
             }
 
             OFActionSetField sf = (OFActionSetField) action;
-            if (!(sf instanceof OFOxmExpOchSigId)) {
+            if (!(sf.getField() instanceof OFOxmExpOchSigId)) {
                 newActions.add(action);
+                continue;
             }
 
             OFOxmExpOchSigId oxm = (OFOxmExpOchSigId) sf.getField();
@@ -259,7 +261,6 @@
             OFFlowMod fm = (OFFlowMod) msg;
             newMatch = rewriteMatch(fm.getMatch());
             List<OFAction> actions = rewriteActions(fm.getActions());
-
             newMsg = fm.createBuilder().setMatch(newMatch).setActions(actions).build();
         }
 
@@ -371,15 +372,10 @@
             return Collections.emptySortedSet();
         }
 
-        // OMS ports expose 80 lambdas of 50GHz width, centered around the ITU-T center frequency.
-        // We report these with a spacing of 12.5 GHz.
-        List<OchSignal> lambdas = IntStream.range(0, LAMBDA_COUNT)
-                .mapToObj(x -> new OchSignal(GridType.FLEX, ChannelSpacing.CHL_12P5GHZ, x - (LAMBDA_COUNT / 2), 1))
-                .collect(Collectors.toList());
-
-        SortedSet<OchSignal> result = new TreeSet<>(new DefaultOchSignalComparator());
-        result.addAll(lambdas);
-
-        return result;
+        // OMS ports expose 80 fixed grid lambdas of 50GHz width, centered around the ITU-T center frequency 193.1 THz.
+        Supplier<SortedSet<OchSignal>> supplier = () -> new TreeSet<>(new DefaultOchSignalComparator());
+        return IntStream.range(0, LAMBDA_COUNT)
+                .mapToObj(x -> new OchSignal(GridType.DWDM, ChannelSpacing.CHL_50GHZ, x - (LAMBDA_COUNT / 2), 4))
+                .collect(Collectors.toCollection(supplier));
     }
 }
diff --git a/tools/test/topos/opticalUtils.py b/tools/test/topos/opticalUtils.py
index 44f4e37..a990a8e 100644
--- a/tools/test/topos/opticalUtils.py
+++ b/tools/test/topos/opticalUtils.py
@@ -536,7 +536,9 @@
         for link in LINCSwitch.opticalJSON[ 'links' ]:
             linkDict = {}
             linkDict[ 'type' ] = link[ 'type' ]
-            linkDict.update(link[ 'annotations' ])
+            # FIXME: Clean up unnecessary link/device attributes, then re-enable annotations
+            linkDict['durable'] = True
+            # linkDict.update(link[ 'annotations' ])
 
             linkSubj = link[ 'src' ] + '-' + link[ 'dst' ]
             links[ linkSubj ] = { 'basic': linkDict }