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;
                     });
         }
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 d29b3fc..7f67df3 100644
--- a/core/api/src/main/java/org/onosproject/net/OchSignal.java
+++ b/core/api/src/main/java/org/onosproject/net/OchSignal.java
@@ -203,10 +203,6 @@
         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.stream()
             .mapToInt(OchSignal::spacingMultiplier)
diff --git a/drivers/ciena/src/main/java/org/onosproject/drivers/ciena/CienaFlowRuleProgrammable.java b/drivers/ciena/src/main/java/org/onosproject/drivers/ciena/CienaFlowRuleProgrammable.java
index e514188..36eb087 100644
--- a/drivers/ciena/src/main/java/org/onosproject/drivers/ciena/CienaFlowRuleProgrammable.java
+++ b/drivers/ciena/src/main/java/org/onosproject/drivers/ciena/CienaFlowRuleProgrammable.java
@@ -42,6 +42,7 @@
     public Collection<FlowEntry> getFlowEntries() {
         DeviceId deviceId = handler().data().deviceId();
         log.debug("getting flow entries for device {}", deviceId);
+        //TODO: implement getFlowEntries
         log.debug("getFlowEntries not supported for device {}", deviceId);
         return Collections.EMPTY_LIST;
     }
@@ -60,11 +61,9 @@
     @Override
     public Collection<FlowRule> removeFlowRules(Collection<FlowRule> rules) {
         log.debug("removing flow rules: {}", rules);
-        Collection<FlowRule> removed = rules.stream()
-                .map(r -> createCrossConnectFlowRule(r))
-                .filter(xc -> removeCrossConnect(xc))
-                .collect(Collectors.toList());
-        return removed;
+        //TODO: implement remove rule
+        log.debug("ignoring remove rule request");
+        return rules;
     }
 
     private CrossConnectFlowRule createCrossConnectFlowRule(FlowRule r) {
@@ -100,8 +99,7 @@
         // only handling lineside rule
         if (xc.isAddRule()) {
             PortNumber outPort = xc.addDrop();
-            OchSignal signal = OchSignal.newDwdmSlot(xc.ochSignal().channelSpacing(),
-                                                     -CienaRestDevice.getMultiplierOffset());
+            OchSignal signal = OchSignal.newDwdmSlot(xc.ochSignal().channelSpacing(), 0);
             return install(outPort, signal);
         }
         return false;
diff --git a/drivers/ciena/src/main/java/org/onosproject/drivers/ciena/CienaRestDevice.java b/drivers/ciena/src/main/java/org/onosproject/drivers/ciena/CienaRestDevice.java
index 7627959..ebe362f 100644
--- a/drivers/ciena/src/main/java/org/onosproject/drivers/ciena/CienaRestDevice.java
+++ b/drivers/ciena/src/main/java/org/onosproject/drivers/ciena/CienaRestDevice.java
@@ -40,7 +40,6 @@
     private static final String ENABLED = "enabled";
     private static final String DISABLED = "disabled";
     private static final Frequency BASE_FREQUENCY = Frequency.ofGHz(193_950);
-    private static final int MULTIPLIER_OFFSET = 80;
 
     //URIs
     private static final String PORT_URI = "ws-ptps/ptps/%s";
@@ -74,10 +73,10 @@
 
     }
 
-    private String genFrequencyChangeRequest(long wavelength) {
+    private String genFrequencyChangeRequest(long frequency) {
         String request = "{\n" +
                 "\"ciena-ws-ptp-modem:frequency\": {\n" +
-                "\"value\": " + Long.toString(wavelength) + "\n" +
+                "\"value\": " + Long.toString(frequency) + "\n" +
                 "}\n" +
                 "}";
         log.debug("request:\n{}", request);
@@ -123,7 +122,7 @@
 
     public final boolean changeChannel(OchSignal signal, PortNumber outPort) {
         String uri = genUri(CHANNEL_URI, outPort);
-        int channel = signal.spacingMultiplier() + MULTIPLIER_OFFSET;
+        int channel = signal.spacingMultiplier();
         log.debug("channel is {} for port {} on device {}", channel, outPort.name(), deviceId);
         String request = genChannelChangeRequest(channel);
         return putNoReply(uri, request);
@@ -135,10 +134,6 @@
         return Double.valueOf(frequency).longValue();
     }
 
-    public static int getMultiplierOffset() {
-        return MULTIPLIER_OFFSET;
-    }
-
     private int put(String uri, String request) {
         InputStream payload = new ByteArrayInputStream(request.getBytes(StandardCharsets.UTF_8));
         int response = controller.put(deviceId, uri, payload, MediaType.valueOf(MediaType.APPLICATION_JSON));
diff --git a/drivers/ciena/src/main/java/org/onosproject/drivers/ciena/CienaWaveServerLambdaQuery.java b/drivers/ciena/src/main/java/org/onosproject/drivers/ciena/CienaWaveServerLambdaQuery.java
new file mode 100644
index 0000000..0698287
--- /dev/null
+++ b/drivers/ciena/src/main/java/org/onosproject/drivers/ciena/CienaWaveServerLambdaQuery.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.drivers.ciena;
+
+import org.onosproject.net.ChannelSpacing;
+import org.onosproject.net.OchSignal;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.behaviour.LambdaQuery;
+import org.onosproject.net.driver.AbstractHandlerBehaviour;
+
+
+import com.google.common.collect.ImmutableSet;
+
+import java.util.Set;
+import java.util.stream.IntStream;
+
+/**
+ * Ciena WaveServer Lambda query.
+ * 88 50GHz flex grid channels with 12.5 slot width, starting from 0 to 87.
+ */
+public class CienaWaveServerLambdaQuery extends AbstractHandlerBehaviour implements LambdaQuery {
+
+    @Override
+    public Set<OchSignal> queryLambdas(PortNumber port) {
+        //88 channels of 50 GHz with 12.5 GHz slothWidth
+        int slots = (int) (ChannelSpacing.CHL_50GHZ.frequency().asHz() /
+                ChannelSpacing.CHL_12P5GHZ.frequency().asHz());
+        int channels = 88;
+        //total lambdas are equal to: channels * slots
+        return IntStream.rangeClosed(0, channels * slots)
+                .mapToObj(x -> OchSignal.newFlexGridSlot(2 * x))
+                .collect(ImmutableSet.toImmutableSet());
+    }
+
+}
+
+
diff --git a/drivers/ciena/src/main/resources/ciena-drivers.xml b/drivers/ciena/src/main/resources/ciena-drivers.xml
index 57ed4cf..02f7a97 100644
--- a/drivers/ciena/src/main/resources/ciena-drivers.xml
+++ b/drivers/ciena/src/main/resources/ciena-drivers.xml
@@ -26,7 +26,7 @@
         <behaviour api="org.onosproject.net.flow.FlowRuleProgrammable"
                    impl="org.onosproject.drivers.ciena.CienaFlowRuleProgrammable"/>
         <behaviour api="org.onosproject.net.behaviour.LambdaQuery"
-                   impl="org.onosproject.driver.optical.query.CBand50LambdaQuery"/>
+                   impl="org.onosproject.drivers.ciena.CienaWaveServerLambdaQuery"/>
     </driver>
 </drivers>