ONOS-7080 and ONOS-7070:
	- added support for user-defined signal in add-optical-intent CLI
	- implemented LambdaQuery behavior for restCiena driver
	- added port based filtering of paths in OpticalIntentCompiler

Change-Id: Ibb61cc3722d5b3a52859d5585decf82a50ef5be0
diff --git a/apps/optical-model/src/main/java/org/onosproject/net/optical/cli/AddOpticalIntentCommand.java b/apps/optical-model/src/main/java/org/onosproject/net/optical/cli/AddOpticalIntentCommand.java
index 905585f..f1d686d 100644
--- a/apps/optical-model/src/main/java/org/onosproject/net/optical/cli/AddOpticalIntentCommand.java
+++ b/apps/optical-model/src/main/java/org/onosproject/net/optical/cli/AddOpticalIntentCommand.java
@@ -18,7 +18,6 @@
 import org.apache.karaf.shell.commands.Argument;
 import org.apache.karaf.shell.commands.Command;
 import org.apache.karaf.shell.commands.Option;
-import org.onlab.util.Spectrum;
 import org.onosproject.cli.app.AllApplicationNamesCompleter;
 import org.onosproject.cli.net.ConnectPointCompleter;
 import org.onosproject.cli.net.ConnectivityIntentCommand;
@@ -40,18 +39,35 @@
 import org.onosproject.net.optical.OchPort;
 import org.onosproject.net.optical.OduCltPort;
 
+import java.util.Map;
+import java.util.HashMap;
+import java.util.AbstractMap.SimpleEntry;
 import java.util.List;
+import java.util.stream.Stream;
+import java.util.stream.Collectors;
 
 import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
 import static org.onosproject.net.optical.device.OpticalDeviceServiceView.opticalView;
 
 /**
  * Installs optical connectivity or circuit intents, depending on given port types.
  */
 @Command(scope = "onos", name = "add-optical-intent",
-         description = "Installs optical connectivity intent")
+        description = "Installs optical connectivity intent")
 public class AddOpticalIntentCommand extends ConnectivityIntentCommand {
-
+    private static final String SIGNAL_FORMAT = "slotGranularity/channelSpacing(in GHz e.g 6.25,12.5,25,50,100)/" +
+            "spaceMultiplier/gridType(cwdm, flex, dwdm) " + "e.g 1/6.25/1/flex";
+    private static final String FLEX = "FLEX";
+    private static final String DWDM = "DWDM";
+    private static final String CWDM = "CWDM";
+    private static final String CH_6P25 = "6.25";
+    private static final String CH_12P5 = "12.5";
+    private static final String CH_25 = "25";
+    private static final String CH_50 = "50";
+    private static final String CH_100 = "100";
+    private static final Map<String, ChannelSpacing> CHANNEL_SPACING_MAP = createChannelSpacingMap();
+    private static final Map<String, GridType> GRID_TYPE_MAP = createGridTypeMap();
     // OSGi workaround
     @SuppressWarnings("unused")
     private ConnectPointCompleter cpCompleter;
@@ -61,33 +77,51 @@
     private AllApplicationNamesCompleter appCompleter;
 
     @Argument(index = 0, name = "ingress",
-              description = "Ingress Device/Port Description",
-              required = true, multiValued = false)
+            description = "Ingress Device/Port Description",
+            required = true, multiValued = false)
     String ingressString = "";
 
     @Argument(index = 1, name = "egress",
-              description = "Egress Device/Port Description",
-              required = true, multiValued = false)
+            description = "Egress Device/Port Description",
+            required = true, multiValued = false)
     String egressString = "";
 
     @Option(name = "-b", aliases = "--bidirectional",
             description = "If this argument is passed the optical link created will be bidirectional, " +
-            "else the link will be unidirectional.",
+                    "else the link will be unidirectional.",
             required = false, multiValued = false)
     private boolean bidirectional = false;
 
-    @Option(name = "-c", aliases = "--channel",
-            description = "Optical channel in GHz to use for the intent (e.g., 193.1). " +
-            "Uses 50 GHz spaced DWDM channel plan by default.",
+    @Option(name = "-s", aliases = "--signal",
+            description = "Optical Signal. Format = " + SIGNAL_FORMAT,
             required = false, multiValued = false)
-    private Double channel;
+    private String signal;
 
+    private static final Map<String, ChannelSpacing> createChannelSpacingMap() {
+        return new HashMap(Stream.of(
+                new SimpleEntry(CH_6P25, ChannelSpacing.CHL_6P25GHZ),
+                new SimpleEntry(CH_12P5, ChannelSpacing.CHL_12P5GHZ),
+                new SimpleEntry(CH_25, ChannelSpacing.CHL_25GHZ),
+                new SimpleEntry(CH_50, ChannelSpacing.CHL_50GHZ),
+                new SimpleEntry(CH_100, ChannelSpacing.CHL_100GHZ))
+                                   .collect(Collectors.toMap((e) -> e.getKey(), (e) -> e.getValue())));
+
+    }
+
+    private static final Map<String, GridType> createGridTypeMap() {
+        return new HashMap(Stream.of(
+                new SimpleEntry(FLEX, GridType.FLEX),
+                new SimpleEntry(DWDM, GridType.DWDM),
+                new SimpleEntry(CWDM, GridType.CWDM))
+                                   .collect(Collectors.toMap((e) -> e.getKey(), (e) -> e.getValue())));
+
+    }
 
     private ConnectPoint createConnectPoint(String devicePortString) {
         String[] splitted = devicePortString.split("/");
 
         checkArgument(splitted.length == 2,
-                "Connect point must be in \"deviceUri/portNumber\" format");
+                      "Connect point must be in \"deviceUri/portNumber\" format");
 
         DeviceId deviceId = DeviceId.deviceId(splitted[0]);
         DeviceService deviceService = get(DeviceService.class);
@@ -103,16 +137,36 @@
         return null;
     }
 
-    private OchSignal createOchSignal(Double channel) {
-        if (channel == null) {
+    private OchSignal createOchSignal() throws IllegalArgumentException {
+        if (signal == null) {
             return null;
         }
-
-        ChannelSpacing spacing = ChannelSpacing.CHL_50GHZ;
-        int multiplier = (int) (Math.round(channel - Spectrum.CENTER_FREQUENCY.asHz() / spacing.frequency().asHz()));
-        return new OchSignal(GridType.DWDM, spacing, multiplier, 4);
+        try {
+            String[] splitted = signal.split("/");
+            checkArgument(splitted.length == 4,
+                          "signal requires 4 parameters: " + SIGNAL_FORMAT);
+            int slotGranularity = Integer.parseInt(splitted[0]);
+            String chSpacing = splitted[1];
+            ChannelSpacing channelSpacing = checkNotNull(CHANNEL_SPACING_MAP.get(chSpacing),
+                                                         String.format("invalid channel spacing: %s", chSpacing));
+            int multiplier = Integer.parseInt(splitted[2]);
+            String gdType = splitted[3].toUpperCase();
+            GridType gridType = checkNotNull(GRID_TYPE_MAP.get(gdType),
+                                             String.format("invalid grid type: %s", gdType));
+            return new OchSignal(gridType, channelSpacing, multiplier, slotGranularity);
+        } catch (RuntimeException e) {
+            /* catching RuntimeException as both NullPointerException (thrown by
+             * checkNotNull) and IllegalArgumentException (thrown by checkArgument)
+             * are subclasses of RuntimeException.
+             */
+            String msg = String.format("Invalid signal format: %s, expected format is %s.",
+                    signal, SIGNAL_FORMAT);
+            print(msg);
+            throw new IllegalArgumentException(msg, e);
+        }
     }
 
+
     @Override
     protected void execute() {
         IntentService service = get(IntentService.class);
@@ -175,7 +229,7 @@
                     .dst(egress)
                     .signalType(signalType)
                     .bidirectional(bidirectional)
-                    .ochSignal(createOchSignal(channel))
+                    .ochSignal(createOchSignal())
                     .build();
         } else {
             print("Unable to create optical intent between connect points %s and %s", ingress, egress);
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 11b78c4..9a2b6bf 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
@@ -29,6 +29,7 @@
 import org.onosproject.net.ConnectPoint;
 import org.onosproject.net.DefaultOchSignalComparator;
 import org.onosproject.net.DeviceId;
+import org.onosproject.net.GridType;
 import org.onosproject.net.Link;
 import org.onosproject.net.OchSignal;
 import org.onosproject.net.OchSignalType;
@@ -63,6 +64,7 @@
 import java.util.Set;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
+import java.util.stream.IntStream;
 
 import static com.google.common.base.Preconditions.checkArgument;
 import static org.onosproject.net.optical.device.OpticalDeviceServiceView.opticalView;
@@ -132,19 +134,16 @@
         // Find first path that has the required resources
         Stream<Path> paths = getOpticalPaths(intent);
         Optional<Map.Entry<Path, List<OchSignal>>> found = paths
-                .map(path ->
-                        Maps.immutableEntry(path, findFirstAvailableLambda(intent, path)))
+                .map(path -> Maps.immutableEntry(path, findFirstAvailableLambda(intent, path)))
                 .filter(entry -> !entry.getValue().isEmpty())
                 .filter(entry -> convertToResources(entry.getKey(),
-                        entry.getValue()).stream().allMatch(resourceService::isAvailable))
+                                                    entry.getValue()).stream().allMatch(resourceService::isAvailable))
                 .findFirst();
 
         // Allocate resources and create optical path intent
         if (found.isPresent()) {
             resources.addAll(convertToResources(found.get().getKey(), found.get().getValue()));
-
             allocateResources(intent, resources);
-
             OchSignal ochSignal = OchSignal.toFixedGrid(found.get().getValue(), ChannelSpacing.CHL_50GHZ);
             return ImmutableList.of(createIntent(intent, found.get().getKey(), ochSignal));
         } else {
@@ -207,8 +206,8 @@
             log.error("Resource allocation for {} failed (resource request: {})", intent.key(), resources);
             if (log.isDebugEnabled()) {
                 log.debug("requested resources:\n\t{}", resources.stream()
-                                  .map(Resource::toString)
-                                  .collect(Collectors.joining("\n\t")));
+                        .map(Resource::toString)
+                        .collect(Collectors.joining("\n\t")));
             }
             throw new OpticalIntentCompilationException("Unable to allocate resources: " + resources);
         }
@@ -222,7 +221,25 @@
      */
     private List<OchSignal> findFirstAvailableLambda(OpticalConnectivityIntent intent, Path path) {
         if (intent.ochSignal().isPresent()) {
-            return Collections.singletonList(intent.ochSignal().get());
+            //create lambdas w.r.t. slotGanularity/slotWidth
+            OchSignal ochSignal = intent.ochSignal().get();
+            if (ochSignal.gridType() == GridType.FLEX) {
+                // multiplier sits in the middle of slots
+                int startMultiplier = ochSignal.spacingMultiplier() - (ochSignal.slotGranularity() / 2);
+                return IntStream.range(0, ochSignal.slotGranularity())
+                        .mapToObj(x -> OchSignal.newFlexGridSlot(startMultiplier + (2 * x)))
+                        .collect(Collectors.toList());
+            } else if (ochSignal.gridType() == GridType.DWDM) {
+                int startMultiplier = (int) (1 - ochSignal.slotGranularity() +
+                        ochSignal.spacingMultiplier() * ochSignal.channelSpacing().frequency().asHz() /
+                                ChannelSpacing.CHL_6P25GHZ.frequency().asHz());
+                return IntStream.range(0, ochSignal.slotGranularity())
+                        .mapToObj(x -> OchSignal.newFlexGridSlot(startMultiplier + (2 * x)))
+                        .collect(Collectors.toList());
+            }
+            //TODO: add support for other gridTypes
+            log.error("Grid type: {} not supported for user defined signal intents", ochSignal.gridType());
+            return Collections.emptyList();
         }
 
         Set<OchSignal> lambdas = findCommonLambdas(path);
@@ -312,6 +329,7 @@
     private Stream<Path> getOpticalPaths(OpticalConnectivityIntent intent) {
         // Route in WDM topology
         Topology topology = topologyService.currentTopology();
+        //TODO: refactor with LinkWeigher class Implementation
         LinkWeight weight = new LinkWeight() {
 
             @Override
@@ -345,18 +363,22 @@
 
         ConnectPoint start = intent.getSrc();
         ConnectPoint end = intent.getDst();
+        //head link's src port should be same as intent src port and tail link dst port
+        //should be same as intent dst port in the path.
         Stream<Path> paths = topologyService.getKShortestPaths(topology,
-                                                            start.deviceId(),
-                                                            end.deviceId(),
-                                                            AdapterLinkWeigher.adapt(weight));
+                start.deviceId(),
+                end.deviceId(),
+                AdapterLinkWeigher.adapt(weight))
+                .filter(p -> p.links().get(0).src().port().equals(start.port()) &&
+                        p.links().get(p.links().size() - 1).dst().port().equals(end.port()));
         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()));
+                                          .map(lk -> lk.src() + "-" + lk.dst())
+                                          .collect(Collectors.toList()));
                         return path;
                     });
         }