Adding Cli command to manage XC on ROADMs

Change-Id: I2088c7b272e18314a5d47c769d2fba658de329c8
diff --git a/apps/optical-model/src/main/java/org/onosproject/net/optical/util/OpticalChannelUtility.java b/apps/optical-model/src/main/java/org/onosproject/net/optical/util/OpticalChannelUtility.java
new file mode 100644
index 0000000..0a54be7
--- /dev/null
+++ b/apps/optical-model/src/main/java/org/onosproject/net/optical/util/OpticalChannelUtility.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright 2019-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.
+ *
+ * This work was done in Nokia Bell Labs.
+ *
+ */
+
+package org.onosproject.net.optical.util;
+
+
+import org.onlab.util.Frequency;
+import org.onosproject.net.ChannelSpacing;
+import org.onosproject.net.GridType;
+import org.onosproject.net.OchSignal;
+import org.slf4j.Logger;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.slf4j.LoggerFactory.getLogger;
+
+
+/**
+ * Optical Channel Utility is a set of methods to convert different
+ * set of parameters to the OchSignal instance and backwards.
+ */
+public final class OpticalChannelUtility {
+
+    private static final Logger log = getLogger(OpticalChannelUtility.class);
+    private static final double SLOT_GRANULARITY_CONSTANT = 12.5;
+    private static final int NOMINAL_CENTRAL_FREQUENCY_GHZ = 193100; // According to ITU-T 694.1
+
+    private OpticalChannelUtility() {}
+
+    /**
+     * This method creates OchSignal instance based on Central Frequency and
+     * the Slot Width of the channel.
+     * @param centralFrequency - central frequency of the connection.
+     * @param slotWidth - bandwidth of the optical channel.
+     * @param gridType - type of the frequency grid.
+     * @param channelSpacing - channel spacing.
+     * @return - returns created instance of OchSignal.
+     */
+    public static final OchSignal createOchSignal(Frequency centralFrequency, Frequency slotWidth,
+                                                  GridType gridType, ChannelSpacing channelSpacing) {
+
+        int spacingMultiplier = computeSpacingMultiplier(centralFrequency, channelSpacing);
+        int slotGranularity = computeSlotGranularity(slotWidth);
+
+        return (new OchSignal(gridType, channelSpacing, spacingMultiplier, slotGranularity));
+    }
+
+    /**
+     * This method creates OchSignal instance from frequency bounds.
+     * @param lowerBound - lower bound of the frequency.
+     * @param upperBound - upper bound of the frequency.
+     * @param gridType - type of the frequency grid.
+     * @param channelSpacing - channel spacing.
+     * @return - returns created instance of OchSignal.
+     */
+    public static final OchSignal createOchSignalFromBounds(
+            Frequency lowerBound, Frequency upperBound, GridType gridType,
+            ChannelSpacing channelSpacing) {
+
+        // Transferring everything to the frequencies
+        Frequency slotWidth = upperBound.subtract(lowerBound);
+        Frequency halfBw = slotWidth.floorDivision(2);
+        Frequency centralFrequency = lowerBound.add(halfBw);
+
+        int spacingMultiplier = computeSpacingMultiplier(centralFrequency, channelSpacing);
+        int slotGranularity = computeSlotGranularity(slotWidth);
+
+        return (new OchSignal(gridType, channelSpacing, spacingMultiplier, slotGranularity));
+    }
+
+    /**
+     * This method extracts frequency bounds from OchSignal instance.
+     * @param signal - OchSignal instance.
+     * @param channelSpacing - channel spacing.
+     * @return - HashMap with upper and lower bounds of frequency.
+     */
+    public static final Map<String, Frequency> extractOchFreqBounds(OchSignal signal, ChannelSpacing channelSpacing) {
+
+        // Initializing variables
+        int spacingMultiplier = signal.spacingMultiplier();
+        int slotGranularity = signal.slotGranularity();
+
+        // Computing central frequency
+        Frequency central = computeCentralFrequency(spacingMultiplier, channelSpacing);
+
+        // Computing HALF of slot width
+        Frequency halfSlotWidth = computeSlotWidth(slotGranularity).floorDivision(2);
+
+        // Getting frequency bounds
+        Frequency minFreq = central.subtract(halfSlotWidth);
+        Frequency maxFreq = central.add(halfSlotWidth);
+
+        Map<String, Frequency> freqs = new HashMap<String, Frequency>();
+        freqs.put("minFreq", minFreq);
+        freqs.put("maxFreq", maxFreq);
+
+        return freqs;
+    }
+
+    /**
+     * This method extracts Central Frequency and Slot Width from OchSignal instance.
+     * @param signal - OchSignal instance.
+     * @param channelSpacing - channel spacing.
+     * @return - HashMap with upper and lower bounds of frequency.
+     */
+    public static final Map<String, Frequency> extractOch(OchSignal signal, ChannelSpacing channelSpacing) {
+
+        // Initializing variables
+        int spacingMultiplier = signal.spacingMultiplier();
+        int slotGranularity = signal.slotGranularity();
+
+        // Computing central frequency
+        Frequency central = computeCentralFrequency(spacingMultiplier, channelSpacing);
+        // Computing slot width
+        Frequency sw = computeSlotWidth(slotGranularity);
+
+        Map<String, Frequency> freqs = new HashMap<String, Frequency>();
+        freqs.put("centralFrequency", central);
+        freqs.put("slotWidth", sw);
+
+        return freqs;
+    }
+
+    /**
+     * This method computes the Spacing Multiplier value
+     * from Central Frequency and Channel Spacing values.
+     * @param centralFrequency - central frequency.
+     * @param channelSpacing - channel spacing.
+     * @return - computed spacing multiplier.
+     */
+    public static final int computeSpacingMultiplier(
+            Frequency centralFrequency, ChannelSpacing channelSpacing) {
+
+        double centfreq = Double.parseDouble(String.valueOf(centralFrequency.asGHz()));
+        // Computing spacing multiplier from definition (see OchSignal class comments)
+        double spMult = (centfreq - NOMINAL_CENTRAL_FREQUENCY_GHZ) /
+                Double.parseDouble(String.valueOf(channelSpacing.frequency().asGHz()));
+
+        return ((int) (spMult));
+    }
+
+    /**
+     * This method computes Slot Granularity from Slot Width value.
+     * @param slotWidth - slot width.
+     * @return - computed slot granularity.
+     */
+    public static final int computeSlotGranularity(Frequency slotWidth) {
+
+        double slotw = Double.parseDouble(String.valueOf(slotWidth.asGHz()));
+        // Computing according to the definition
+        double slotgr = slotw / SLOT_GRANULARITY_CONSTANT;
+
+        return ((int) slotgr);
+    }
+
+    /**
+     * This method computes the Central Frequency value
+     * from Spacing Multiplier and Channel Spacing values.
+     * @param spacingMultiplier - spacing multiplier.
+     * @param channelSpacing - channel spacing.
+     * @return - central frequency as an instance of Frequency.
+     */
+    public static final Frequency computeCentralFrequency(
+            int spacingMultiplier, ChannelSpacing channelSpacing) {
+
+        // Computing central frequency
+        double centralFreq = NOMINAL_CENTRAL_FREQUENCY_GHZ + spacingMultiplier *
+                Double.parseDouble(String.valueOf(channelSpacing.frequency().asGHz()));
+
+        return Frequency.ofGHz(centralFreq);
+    }
+
+    /**
+     * This method computes Slot Width value from Slot Granularity value.
+     * @param slotGranularity - slot granularity.
+     * @return - slot width as an instance of Frequency.
+     */
+    public static final Frequency computeSlotWidth(int slotGranularity) {
+
+        // Computing slot width
+        double slotWidth = slotGranularity * SLOT_GRANULARITY_CONSTANT;
+
+        return Frequency.ofGHz(slotWidth);
+    }
+
+}
diff --git a/apps/roadm/app/BUILD b/apps/roadm/app/BUILD
index ca46936..f160348 100644
--- a/apps/roadm/app/BUILD
+++ b/apps/roadm/app/BUILD
@@ -1,4 +1,4 @@
-COMPILE_DEPS = CORE_DEPS + JACKSON + [
+COMPILE_DEPS = CORE_DEPS + JACKSON + CLI + [
     "//core/store/serializers:onos-core-serializers",
     "//apps/optical-model:onos-apps-optical-model",
 ]
@@ -8,6 +8,7 @@
 ]
 
 osgi_jar_with_tests(
+    karaf_command_packages = ["org.onosproject.roadm.cli"],
     test_deps = TEST_DEPS,
     deps = COMPILE_DEPS,
 )
diff --git a/apps/roadm/app/src/main/java/org/onosproject/roadm/cli/RoadmCrossConnectCommand.java b/apps/roadm/app/src/main/java/org/onosproject/roadm/cli/RoadmCrossConnectCommand.java
new file mode 100644
index 0000000..24f85e5
--- /dev/null
+++ b/apps/roadm/app/src/main/java/org/onosproject/roadm/cli/RoadmCrossConnectCommand.java
@@ -0,0 +1,299 @@
+/*
+ * Copyright 2019-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.
+ *
+ * This work was done in Nokia Bell Labs Paris
+ *
+ */
+package org.onosproject.roadm.cli;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.onlab.util.Frequency;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.cli.net.DeviceIdCompleter;
+import org.onosproject.net.ChannelSpacing;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.GridType;
+import org.onosproject.net.OchSignal;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.flow.FlowEntry;
+import org.onosproject.net.flow.FlowId;
+import org.onosproject.net.flow.FlowRuleService;
+import org.onosproject.net.flow.criteria.Criterion;
+import org.onosproject.net.flow.criteria.OchSignalCriterion;
+import org.onosproject.net.flow.criteria.PortCriterion;
+import org.onosproject.net.flow.instructions.Instruction;
+import org.onosproject.net.flow.instructions.Instructions;
+import org.onosproject.net.flow.instructions.L0ModificationInstruction;
+import org.onosproject.net.optical.util.OpticalChannelUtility;
+import org.onosproject.roadm.RoadmService;
+import org.slf4j.Logger;
+
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * This is the command for adding and dropping cross-connections on the ROADMs.
+ *
+ */
+@Service
+@Command(scope = "onos", name = "roadm-xc",
+        description = "Creates/Removes cross-connection on/from the ROADM")
+public class RoadmCrossConnectCommand extends AbstractShellCommand {
+
+    private static final Logger log = getLogger(RoadmCrossConnectCommand.class);
+
+    @Argument(index = 0, name = "operation",
+            description = "Specify Create or Remove action",
+            required = true, multiValued = false)
+    @Completion(RoadmCrossConnectCommandCompleter.class)
+    private String operation = null;
+
+    @Argument(index = 1, name = "deviceId",
+            description = "ROADM's device ID (from ONOS)",
+            required = true, multiValued = false)
+    @Completion(DeviceIdCompleter.class)
+    private String deviceId = null;
+
+    @Argument(index = 2, name = "srcPort",
+            description = "XC's source port {PortNumber}",
+            required = true, multiValued = false)
+    private String srcPort = null;
+
+    @Argument(index = 3, name = "dstPort",
+            description = "XC's destination port {PortNumber}",
+            required = true, multiValued = false)
+    private String dstPort = null;
+
+    @Argument(index = 4, name = "freq",
+            description = "XC's central frequency in [GHz], like 193100 or any other frequency",
+            required = true, multiValued = false)
+    private String freq = null;
+
+    @Argument(index = 5, name = "sw",
+            description = "Frequency Slot Width in [GHz], like 50 GHz or 62.5 GHz",
+            required = true, multiValued = false)
+    private String sw = null;
+
+    @Argument(index = 6, name = "gridType",
+            description = "Frequency grid type. Could be FLEX, CWDM, DWDM or UNKNOWN.",
+            required = false, multiValued = false)
+    private String gridType = null;
+
+    @Argument(index = 7, name = "channelSpacing",
+            description = "Channel spacing in [GHz]. " +
+                    "Could be CHL_0GHZ, CHL_6P25GHZ, CHL_12P5GHZ, " +
+                    "CHL_25GHZ, CHL_50GHZ, CHL_100GHZ",
+            required = false, multiValued = false)
+    private String channelSpacing = null;
+
+    static final String REMOVE = "remove";
+    static final String CREATE = "create";
+
+    @Override
+    protected void doExecute() throws Exception {
+
+        DeviceService deviceService = AbstractShellCommand.get(DeviceService.class);
+
+        if (deviceService.isAvailable(DeviceId.deviceId(deviceId))) {
+            if (deviceService.getPort(DeviceId.deviceId(deviceId), PortNumber.portNumber(srcPort)).isEnabled()) {
+                if (deviceService.getPort(DeviceId.deviceId(deviceId), PortNumber.portNumber(dstPort)).isEnabled()) {
+                    if (operation.equals(CREATE)) {
+
+                        FlowId check = addRule();
+                        if (check != null) {
+                            print("Rule %s was successfully added", check.toString());
+                            log.info("Rule {} was successfully added", check.toString());
+                        } else {
+                            print("Your rule wasn't added. Something went wrong during the process " +
+                                          "(issue on the driver side).");
+                            log.error("Your rule wasn't added. " +
+                                              "Something went wrong during the process (issue on the driver side).");
+                        }
+
+                    } else if (operation.equals(REMOVE)) {
+
+                        FlowId check = dropRule();
+                        if (check != null) {
+                            print("Rule %s was successfully dropped", check.toString());
+                            log.info("Rule {} was successfully dropped", check.toString());
+                        } else {
+                            print("Your rule wasn't dropped. No match found.");
+                            log.error("Your rule wasn't dropped. No match found.");
+                        }
+
+                    } else {
+
+                        print("\n Unspecified operation -- %s -- :( \n Try again! \n", operation);
+                        log.debug("\n Unspecified operation -- {} -- :( \n Try again! \n", operation);
+
+                    }
+                } else {
+                    log.error("Destination port {} is not enabled", dstPort);
+                }
+            } else {
+                log.error("Source port {} is not enabled", srcPort);
+            }
+        } else {
+            log.error("Device {} is not available", deviceId);
+        }
+
+    }
+
+
+    /**
+     * This method creates a XC on the device based on the parameters passed by the user.
+     * Takes as an input "global" parameters (passed by user through the console).
+     * @return - return the FlowId of the installed rule.
+     */
+    protected FlowId addRule() {
+
+        // Preparing parameters
+        DeviceId device = DeviceId.deviceId(deviceId);
+        PortNumber inPort = PortNumber.portNumber(srcPort);
+        PortNumber outPort = PortNumber.portNumber(dstPort);
+        OchSignal signal = createOchSignal(freq, sw, gridType, channelSpacing);
+
+        if (inPort == null) {
+            print("[addRule] Not able to find srcPort in the ONOS database");
+            log.debug("[addRule] Not able to find srcPort in the ONOS database");
+            return null;
+        }
+        if (outPort == null) {
+            print("[addRule] Not able to find dstPort in the ONOS database");
+            log.debug("[addRule] Not able to find dstPort in the ONOS database");
+            return null;
+        }
+
+        if (signal == null) {
+            print("[addRule] Not able to compose an OchSignal with passed parameters. Double check them");
+            log.debug("[addRule] Not able to compose an OchSignal with passed parameters. Double check them");
+            return null;
+        }
+
+        RoadmService manager = AbstractShellCommand.get(RoadmService.class);
+        print("Adding XC for the device %s between port %s and port %s on frequency %s with bandwidth %s",
+              deviceId, srcPort, dstPort, freq, sw);
+        log.info("[addRule] Adding XC for the device {} between port {} and port {} " +
+                         "on frequency {} with bandwidth {}", deviceId, srcPort, dstPort, freq, sw);
+        FlowId flow = manager.createConnection(device, 100, true, -1, inPort, outPort, signal);
+
+        if (flow != null) {
+            return flow;
+        } else {
+            return null;
+        }
+    }
+
+
+    /**
+     * This function drops XC installed on the device, which is matching parsed criteria.
+     * Takes as an input "global" parameters (passed by user through the console).
+     * @return - returns number of the rules that were dropped.
+     */
+    protected FlowId dropRule() {
+
+        // Preparing parameters
+        DeviceId device = DeviceId.deviceId(deviceId);
+        PortNumber inPort = PortNumber.portNumber(srcPort);
+        PortNumber outPort = PortNumber.portNumber(dstPort);
+
+        // Creating some variables
+        OchSignal ochSignal = null;
+        PortNumber inputPortNumber = null;
+        PortNumber outputPortNumber = null;
+
+        // Main idea: Go over all flow rules (read out from the storage) of current device and
+        // filter them based on input and output port with respect to OchSignal
+        FlowRuleService fr = AbstractShellCommand.get(FlowRuleService.class);
+        Iterable<FlowEntry> flowRules = fr.getFlowEntries(device);
+        FlowId flowId = null;
+        OchSignal referenceSignal = createOchSignal(freq, sw, gridType, channelSpacing);
+
+
+        for (FlowEntry flowRule : flowRules) {
+
+            // Taken from FlowRuleParser
+            for (Criterion c : flowRule.selector().criteria()) {
+                if (c instanceof OchSignalCriterion) {
+                    ochSignal = ((OchSignalCriterion) c).lambda();
+                }
+                if (c instanceof PortCriterion) {
+                    inputPortNumber = ((PortCriterion) c).port(); // obtain input port
+                }
+            }
+            for (Instruction i : flowRule.treatment().immediate()) {
+                if (i instanceof
+                        L0ModificationInstruction.ModOchSignalInstruction) {
+                    ochSignal =
+                            ((L0ModificationInstruction.ModOchSignalInstruction) i)
+                                    .lambda();
+                }
+                if (i instanceof Instructions.OutputInstruction) {
+                    outputPortNumber = ((Instructions.OutputInstruction) i).port(); // obtain output port
+                }
+            }
+
+            // If we found match, then let's delete this rule
+            if ((ochSignal.centralFrequency().equals(referenceSignal.centralFrequency()))
+                    & (ochSignal.slotWidth().equals(referenceSignal.slotWidth()))
+                    & (inputPortNumber.equals(inPort)) & (outputPortNumber.equals(outPort))) {
+                flowId = flowRule.id();
+
+                RoadmService manager = AbstractShellCommand.get(RoadmService.class);
+                manager.removeConnection(device, flowId);
+                print("Dropping existing XC from the device %s", deviceId);
+                return flowId;
+            }
+        }
+
+        return null;
+    }
+
+
+    /**
+     * This method forms parameters and creates and OchSignal instance from
+     * central frequency and the slot width of the channel.
+     * @param frequency - central frequency of the connection.
+     * @param sw - slot width of the optical channel.
+     * @param grid - frequency grid type.
+     * @param spacing - channel spacing.
+     * @return - returns created instance of OchSignal.
+     */
+    protected OchSignal createOchSignal(String frequency, String sw, String grid, String spacing) {
+
+        Frequency centralFreq = Frequency.ofGHz(Double.parseDouble(frequency));
+        Frequency slotWidth = Frequency.ofGHz(Double.parseDouble(sw));
+
+        GridType gridType = null;
+        try {
+            gridType = GridType.valueOf(grid.toUpperCase());
+        } catch (Exception e) {
+            gridType = GridType.DWDM;
+        }
+
+        ChannelSpacing channelSpacing = null;
+        // It requires passing channelSpacing in the following format CHL_6P25GHZ or similar
+        try {
+            channelSpacing = ChannelSpacing.valueOf(spacing);
+        } catch (Exception e) {
+            channelSpacing = ChannelSpacing.CHL_50GHZ;
+        }
+
+        return OpticalChannelUtility.createOchSignal(centralFreq, slotWidth, gridType, channelSpacing);
+    }
+
+}
diff --git a/apps/roadm/app/src/main/java/org/onosproject/roadm/cli/RoadmCrossConnectCommandCompleter.java b/apps/roadm/app/src/main/java/org/onosproject/roadm/cli/RoadmCrossConnectCommandCompleter.java
new file mode 100644
index 0000000..8590a11
--- /dev/null
+++ b/apps/roadm/app/src/main/java/org/onosproject/roadm/cli/RoadmCrossConnectCommandCompleter.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2015-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.roadm.cli;
+
+import com.google.common.collect.ImmutableList;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.onosproject.cli.AbstractChoicesCompleter;
+
+import java.util.List;
+
+import static org.onosproject.roadm.cli.RoadmCrossConnectCommand.CREATE;
+import static org.onosproject.roadm.cli.RoadmCrossConnectCommand.REMOVE;
+
+/**
+ * ROADM cross connect command completer.
+ */
+@Service
+public class RoadmCrossConnectCommandCompleter extends AbstractChoicesCompleter {
+    @Override
+    public List<String> choices() {
+        return ImmutableList.of(CREATE, REMOVE);
+    }
+
+}
diff --git a/apps/roadm/app/src/main/java/org/onosproject/roadm/cli/package-info.java b/apps/roadm/app/src/main/java/org/onosproject/roadm/cli/package-info.java
new file mode 100644
index 0000000..57c0293
--- /dev/null
+++ b/apps/roadm/app/src/main/java/org/onosproject/roadm/cli/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2019-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.
+ *
+ * This work was done in Nokia Bell Labs Paris
+ *
+ */
+
+/**
+ * Client command to add/drop cross-connections on ROADM devices.
+ */
+package org.onosproject.roadm.cli;