adding FlowRuleProgrammable for Nokia's transponders

Change-Id: I77e80bc5746af82b737a16051b914d58fca327fc
diff --git a/drivers/odtn-driver/src/main/java/org/onosproject/drivers/odtn/NokiaFlowRuleProgrammable.java b/drivers/odtn-driver/src/main/java/org/onosproject/drivers/odtn/NokiaFlowRuleProgrammable.java
new file mode 100644
index 0000000..0639884
--- /dev/null
+++ b/drivers/odtn-driver/src/main/java/org/onosproject/drivers/odtn/NokiaFlowRuleProgrammable.java
@@ -0,0 +1,248 @@
+/*
+ * Copyright 2018-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.odtn;
+
+import com.google.common.collect.ImmutableList;
+import org.onlab.util.Frequency;
+import org.onosproject.drivers.odtn.impl.DeviceConnectionCache;
+import org.onosproject.drivers.odtn.impl.FlowRuleParser;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Port;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.driver.AbstractHandlerBehaviour;
+import org.onosproject.net.flow.DefaultFlowEntry;
+import org.onosproject.net.flow.FlowEntry;
+import org.onosproject.net.flow.FlowRule;
+import org.onosproject.net.flow.FlowRuleProgrammable;
+import org.onosproject.netconf.DatastoreId;
+import org.onosproject.netconf.NetconfController;
+import org.onosproject.netconf.NetconfException;
+import org.onosproject.netconf.NetconfSession;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onosproject.odtn.behaviour.OdtnDeviceDescriptionDiscovery.OC_NAME;
+
+/**
+ * Implementation of FlowRuleProgrammable interface for
+ * OpenConfig terminal devices.
+ */
+public class NokiaFlowRuleProgrammable
+        extends AbstractHandlerBehaviour implements FlowRuleProgrammable {
+
+    private static final Logger log =
+            LoggerFactory.getLogger(NokiaFlowRuleProgrammable.class);
+
+    /**
+     * Apply the flow entries specified in the collection rules.
+     *
+     * @param rules A collection of Flow Rules to be applied
+     * @return The collection of added Flow Entries
+     */
+    @Override
+    public Collection<FlowRule> applyFlowRules(Collection<FlowRule> rules) {
+        NetconfSession session = getNetconfSession();
+        if (session == null) {
+            openConfigError("null session");
+            return ImmutableList.of();
+        }
+        List<FlowRule> added = new ArrayList<>();
+        for (FlowRule r : rules) {
+            try {
+                String connectionId = applyFlowRule(session, r);
+                getConnectionCache().add(did(), connectionId, r);
+                added.add(r);
+            } catch (Exception e) {
+                openConfigError("Error {}", e);
+                continue;
+            }
+        }
+        openConfigLog("applyFlowRules added {}", added.size());
+        return added;
+    }
+
+    /**
+     * Get the flow entries that are present on the device.
+     *
+     * @return A collection of Flow Entries
+     */
+    @Override
+    public Collection<FlowEntry> getFlowEntries() {
+        DeviceConnectionCache cache = getConnectionCache();
+        if (cache.get(did()) == null) {
+            return ImmutableList.of();
+        }
+
+        List<FlowEntry> entries = new ArrayList<>();
+        for (FlowRule r : cache.get(did())) {
+            entries.add(
+                    new DefaultFlowEntry(r, FlowEntry.FlowEntryState.ADDED, 0, 0, 0));
+        }
+        return entries;
+    }
+
+    /**
+     * Remove the specified flow rules.
+     *
+     * @param rules A collection of Flow Rules to be removed
+     * @return The collection of removed Flow Entries
+     */
+    @Override
+    public Collection<FlowRule> removeFlowRules(Collection<FlowRule> rules) {
+        NetconfSession session = getNetconfSession();
+        if (session == null) {
+            openConfigError("null session");
+            return ImmutableList.of();
+        }
+        List<FlowRule> removed = new ArrayList<>();
+        for (FlowRule r : rules) {
+            try {
+                String connectionId = removeFlowRule(session, r);
+                getConnectionCache().remove(did(), connectionId);
+                removed.add(r);
+            } catch (Exception e) {
+                openConfigError("Error {}", e);
+                continue;
+            }
+        }
+        openConfigLog("removedFlowRules removed {}", removed.size());
+        return removed;
+    }
+
+    private DeviceConnectionCache getConnectionCache() {
+        return DeviceConnectionCache.init();
+    }
+
+    /**
+     * Helper method to get the device id.
+     */
+    private DeviceId did() {
+        return data().deviceId();
+    }
+
+    /**
+     * Helper method to log from this class adding DeviceId.
+     */
+    private void openConfigLog(String format, Object... arguments) {
+        log.info("OPENCONFIG {}: " + format, did(), arguments);
+    }
+
+    /**
+     * Helper method to log an error from this class adding DeviceId.
+     */
+    private void openConfigError(String format, Object... arguments) {
+        log.error("OPENCONFIG {}: " + format, did(), arguments);
+    }
+
+
+    /**
+     * Helper method to get the Netconf Session.
+     */
+    private NetconfSession getNetconfSession() {
+        NetconfController controller =
+                checkNotNull(handler().get(NetconfController.class));
+        return controller.getNetconfDevice(did()).getSession();
+    }
+
+    private void setOpticalChannelFrequency(NetconfSession session,
+                                            String optChannel, Frequency freq)
+            throws NetconfException {
+        StringBuilder sb = new StringBuilder();
+        sb.append(
+                "<components xmlns='http://openconfig.net/yang/platform'>"
+                        + "<component operation='merge'>"
+                        + "<name>" + optChannel + "</name>"
+                        + "<oc-opt-term:optical-channel "
+                        +
+                        "    xmlns:oc-opt-term='http://openconfig.net/yang/terminal-device'>"
+                        + "  <oc-opt-term:config>"
+                        + "  <oc-opt-term:frequency>" + (long) freq.asMHz() +
+                        "</oc-opt-term:frequency>"
+                        + "    </oc-opt-term:config>"
+                        + " </oc-opt-term:optical-channel>"
+                        + "</component>"
+                        + "</components>");
+
+        boolean ok =
+                session.editConfig(DatastoreId.RUNNING, null, sb.toString());
+        if (!ok) {
+            throw new NetconfException("error writing channel frequency");
+        }
+    }
+
+    /**
+     * Get the OpenConfig component name for the OpticalChannel component.
+     *
+     * @param portNumber ONOS port number of the Line port ().
+     * @return the channel component name or null
+     */
+    private String getOpticalChannel(PortNumber portNumber) {
+        Port clientPort = handler().get(DeviceService.class).getPort(did(), portNumber);
+        return clientPort.annotations().value(OC_NAME);
+    }
+
+    /**
+     * Apply the flowrule.
+     *
+     * Note: only bidirectional are supported as of now,
+     * given OpenConfig note (below). In consequence, only the
+     * TX rules are actually mapped to netconf ops.
+     * <p>
+     * https://github.com/openconfig/public/blob/master/release/models
+     * /optical-transport/openconfig-terminal-device.yang
+     * <p>
+     * Directionality:
+     * To maintain simplicity in the model, the configuration is
+     * described from client-to-line direction.  The assumption is that
+     * equivalent reverse configuration is implicit, resulting in
+     * the same line-to-client configuration.
+     *
+     * @param session The Netconf session.
+     * @param r       Flow Rules to be applied.
+     * @return the optical channel + the frequency or just channel as identifier fo the config installed on the device
+     * @throws NetconfException if exchange goes wrong
+     */
+    protected String applyFlowRule(NetconfSession session, FlowRule r) throws NetconfException {
+        FlowRuleParser frp = new FlowRuleParser(r);
+        if (!frp.isReceiver()) {
+            String optChannel = getOpticalChannel(frp.getPortNumber());
+            setOpticalChannelFrequency(session, optChannel,
+                    frp.getCentralFrequency());
+            return optChannel + ":" + frp.getCentralFrequency().asGHz();
+        }
+        return String.valueOf(frp.getCentralFrequency().asGHz());
+    }
+
+
+    protected String removeFlowRule(NetconfSession session, FlowRule r)
+            throws NetconfException {
+        FlowRuleParser frp = new FlowRuleParser(r);
+        if (!frp.isReceiver()) {
+            String optChannel = getOpticalChannel(frp.getPortNumber());
+            setOpticalChannelFrequency(session, optChannel, Frequency.ofMHz(0));
+            return optChannel + ":" + frp.getCentralFrequency().asGHz();
+        }
+        return String.valueOf(frp.getCentralFrequency().asGHz());
+    }
+}
diff --git a/drivers/odtn-driver/src/main/java/org/onosproject/drivers/odtn/NokiaOpenConfigDeviceDiscovery.java b/drivers/odtn-driver/src/main/java/org/onosproject/drivers/odtn/NokiaOpenConfigDeviceDiscovery.java
index 2324614..2a454c5 100644
--- a/drivers/odtn-driver/src/main/java/org/onosproject/drivers/odtn/NokiaOpenConfigDeviceDiscovery.java
+++ b/drivers/odtn-driver/src/main/java/org/onosproject/drivers/odtn/NokiaOpenConfigDeviceDiscovery.java
@@ -40,6 +40,10 @@
 import org.onosproject.netconf.NetconfException;
 import org.onosproject.netconf.NetconfSession;
 import org.onosproject.odtn.behaviour.OdtnDeviceDescriptionDiscovery;
+import org.onosproject.net.OchSignal;
+import org.onosproject.net.optical.device.OchPortHelper;
+import org.onosproject.net.OduSignalType;
+import org.onosproject.net.ChannelSpacing;
 import org.slf4j.Logger;
 
 import java.util.HashMap;
@@ -146,28 +150,56 @@
      * @return PortDescription or null if component is not an ONOS Port
      */
     private PortDescription toPortDescriptionInternal(HierarchicalConfiguration component) {
-        Map<String, String> props = new HashMap<>();
+        Map<String, String> annotations = new HashMap<>();
         String name = component.getString("name");
         String type = component.getString("state/type");
         checkNotNull(name, "name not found");
         checkNotNull(type, "state/type not found");
-        props.put(OdtnDeviceDescriptionDiscovery.OC_NAME, name);
-        props.put(OdtnDeviceDescriptionDiscovery.OC_TYPE, type);
-        Builder builder = DefaultPortDescription.builder();
+        annotations.put(OdtnDeviceDescriptionDiscovery.OC_NAME, name);
+        annotations.put(OdtnDeviceDescriptionDiscovery.OC_TYPE, type);
+
+        component.configurationsAt("properties/property")
+                .forEach(property -> {
+                    String pn = property.getString("name");
+                    String pv = property.getString("state/value");
+                    annotations.put(pn, pv);
+                });
+
         if (type.equals("oc-platform-types:PORT")) {
+
             String subComponentName = component.getString("subcomponents/subcomponent/name");
             String[] textStr = subComponentName.split("-");
             String portComponentType = textStr[0];
             String portComponentIndex = textStr[textStr.length - 1];
-            builder.withPortNumber(PortNumber.portNumber(Long.parseLong(portComponentIndex), subComponentName));
-            if (portComponentType.equals(OPTICAL_CHANNEL)) {
-                builder.type(Type.OCH);
-                props.putIfAbsent(PORT_TYPE, OdtnPortType.LINE.value());
-                props.putIfAbsent(CONNECTION_ID, portComponentIndex);
-            } else if (portComponentType.equals(TRANSCEIVER)) {
-                builder.type(Type.PACKET);
-                props.putIfAbsent(PORT_TYPE, OdtnPortType.CLIENT.value());
-                props.putIfAbsent(CONNECTION_ID, portComponentIndex);
+
+             if (portComponentType.equals(OPTICAL_CHANNEL)) {
+
+                 annotations.putIfAbsent(PORT_TYPE, OdtnPortType.LINE.value());
+                 annotations.putIfAbsent(ONOS_PORT_INDEX, portComponentIndex.toString());
+                 annotations.putIfAbsent(CONNECTION_ID, "connection" + portComponentIndex.toString());
+
+                 OchSignal signalId = OchSignal.newDwdmSlot(ChannelSpacing.CHL_50GHZ, 1);
+                 return OchPortHelper.ochPortDescription(
+                        PortNumber.portNumber(Long.parseLong(portComponentIndex)),
+                        true,
+                        OduSignalType.ODU4, // TODO Client signal to be discovered
+                        true,
+                        signalId,
+                        DefaultAnnotations.builder().putAll(annotations).build());
+
+             } else if (portComponentType.equals(TRANSCEIVER)) {
+
+                 Builder builder = DefaultPortDescription.builder();
+                 annotations.putIfAbsent(PORT_TYPE, OdtnPortType.CLIENT.value());
+                 annotations.putIfAbsent(ONOS_PORT_INDEX, portComponentIndex.toString());
+                 annotations.putIfAbsent(CONNECTION_ID, "connection" + portComponentIndex.toString());
+
+                 builder.withPortNumber(PortNumber.portNumber(Long.parseLong(portComponentIndex), subComponentName));
+                 builder.type(Type.PACKET);
+
+                 builder.annotations(DefaultAnnotations.builder().putAll(annotations).build());
+                 return builder.build();
+
             } else {
                 log.debug("Unknown port component type {}", type);
                 return null;
@@ -176,9 +208,6 @@
             log.debug("Another component type {}", type);
             return null;
         }
-
-        builder.annotations(DefaultAnnotations.builder().putAll(props).build());
-        return builder.build();
     }
 
     /**
@@ -213,7 +242,7 @@
         } catch (NetconfException e) {
             log.error("can not login to device", e);
         }
-        return null;
+        return ns;
     }
 
     //crude way of removing rpc-reply envelope (copy from netconf session)
@@ -276,9 +305,9 @@
     private String buildLoginRpc(String userName, String passwd) {
         StringBuilder rpc = new StringBuilder(RPC_TAG_NETCONF_BASE);
         rpc.append("<login xmlns=\"http://nokia.com/yang/nokia-security\">");
-        rpc.append("<username>");
+        rpc.append("<user>");
         rpc.append(userName);
-        rpc.append("</username>");
+        rpc.append("</user>");
         rpc.append("<password>");
         rpc.append(passwd);
         rpc.append("</password>");
diff --git a/drivers/odtn-driver/src/main/resources/odtn-drivers.xml b/drivers/odtn-driver/src/main/resources/odtn-drivers.xml
index 510abb7..c4f2faa 100644
--- a/drivers/odtn-driver/src/main/resources/odtn-drivers.xml
+++ b/drivers/odtn-driver/src/main/resources/odtn-drivers.xml
@@ -72,6 +72,12 @@
                    impl="org.onosproject.drivers.odtn.NokiaOpenConfigDeviceDiscovery"/>
         <behaviour api="org.onosproject.odtn.behaviour.ConfigurableTransceiver"
                    impl="org.onosproject.odtn.behaviour.PlainTransceiver"/>
+        <behaviour api="org.onosproject.net.flow.FlowRuleProgrammable"
+                   impl="org.onosproject.drivers.odtn.NokiaFlowRuleProgrammable"/>
+        <behaviour api ="org.onosproject.net.optical.OpticalDevice"
+                   impl="org.onosproject.net.optical.DefaultOpticalDevice"/>
+        <behaviour api ="org.onosproject.net.behaviour.LambdaQuery"
+                   impl="org.onosproject.drivers.odtn.openconfig.TerminalDeviceLambdaQuery"/>
         <property name="netconfClientCapability">urn:ietf:params:netconf:base:1.0|
             urn:ietf:params:netconf:capability:writable-running:1.0|
             urn:ietf:params:netconf:capability:notification:1.0|