ONOS-7828 ODTN OpenConfig FlowRule and LambdaQuery.

Change-Id: I03212d2b8b544bae95f9f085eb1cadf883e5611d
diff --git a/apps/odtn/api/src/main/java/org/onosproject/odtn/utils/tapi/TapiSipHandler.java b/apps/odtn/api/src/main/java/org/onosproject/odtn/utils/tapi/TapiSipHandler.java
index 367122e..9f81739 100644
--- a/apps/odtn/api/src/main/java/org/onosproject/odtn/utils/tapi/TapiSipHandler.java
+++ b/apps/odtn/api/src/main/java/org/onosproject/odtn/utils/tapi/TapiSipHandler.java
@@ -33,12 +33,17 @@
 import static org.onosproject.yang.gen.v1.tapicommon.rev20181016.tapicommon.layerprotocolname.LayerProtocolNameEnum.DSR;
 import org.onosproject.yang.gen.v1.tapicommon.rev20181016.tapicommon.tapicontext.DefaultServiceInterfacePoint;
 import org.onosproject.yang.model.ModelObjectId;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * Utility class to deal with TAPI SIP with DCS.
  */
 public final class TapiSipHandler extends TapiObjectHandler<DefaultServiceInterfacePoint> {
 
+    private static final Logger log =
+            LoggerFactory.getLogger(TapiSipHandler.class);
+
     private TapiSipHandler() {
         obj = new DefaultServiceInterfacePoint();
         setId();
@@ -67,11 +72,25 @@
 
     /**
      * Check this handler dealing with port for SIP or not.
+     *
      * @param port onos port object
      * @return is this handler for SIP or not
      */
     public static boolean isSip(Port port) {
-        // FIXME modify this method to appropriate way
+        // RCAS Note: We may end up controlling more devices that do
+        // not have annotations or do not have the PORT_TYPE
+        // annotation. Let's accomodate other devices (e.g.
+        // OpenROADM) and be less strict. In short, if the
+        // annotation does not exist, simply return false.
+        //
+        // Note: for phase 1.5+ , we should also allow SIPs to
+        // be the transceiver LINE side, when we establish
+        // OpticalConnectivityIntent
+
+        if (!port.annotations().keys().contains(PORT_TYPE)) {
+            log.warn("No annotation of {} on port {}", PORT_TYPE, port);
+            return false;
+        }
         String portType = port.annotations().value(PORT_TYPE);
         OdtnDeviceDescriptionDiscovery.OdtnPortType odtnPortType
                 = OdtnDeviceDescriptionDiscovery.OdtnPortType.fromValue(portType);
diff --git a/apps/odtn/service/src/main/java/org/onosproject/odtn/impl/ServiceApplicationComponent.java b/apps/odtn/service/src/main/java/org/onosproject/odtn/impl/ServiceApplicationComponent.java
index a77b5a3..e296f1b 100644
--- a/apps/odtn/service/src/main/java/org/onosproject/odtn/impl/ServiceApplicationComponent.java
+++ b/apps/odtn/service/src/main/java/org/onosproject/odtn/impl/ServiceApplicationComponent.java
@@ -176,8 +176,9 @@
                 case PORT_REMOVED:
                     tapiTopologyManager.removePort(event.port());
                     break;
+                // TODO: Process device / port updated events
                 default:
-                    log.warn("Unknown Event", event.type());
+                    log.warn("Unprocessed Event {}", event.type());
                     break;
             }
 
@@ -206,7 +207,7 @@
                     tapiTopologyManager.removeLink(link);
                     break;
                 default:
-                    log.warn("Unknown Event", event.type());
+                    log.warn("Unknown Event {}", event.type());
                     break;
             }
         }
diff --git a/drivers/odtn-driver/BUILD b/drivers/odtn-driver/BUILD
index 2ca6cee..657d568 100644
--- a/drivers/odtn-driver/BUILD
+++ b/drivers/odtn-driver/BUILD
@@ -3,6 +3,8 @@
     "//drivers/utilities:onos-drivers-utilities",
     "//protocols/netconf/api:onos-protocols-netconf-api",
     "//apps/odtn/api:onos-apps-odtn-api",
+    "//apps/optical-model:onos-apps-optical-model",
+    "//drivers/optical:onos-drivers-optical",
 ]
 
 TEST_DEPS = TEST_ADAPTERS + [
@@ -31,6 +33,9 @@
         "org.onosproject.netconf",
         "org.onosproject.config",
         "org.onosproject.odtn-api",
+        "org.onosproject.drivers.netconf",
+        "org.onosproject.drivers.optical",
+        "org.onosproject.optical-model",
     ],
     title = "ODTN Driver",
     url = "https://wiki.onosproject.org/display/ODTN/ODTN",
diff --git a/drivers/odtn-driver/src/main/java/org/onosproject/drivers/odtn/impl/FlowRuleParser.java b/drivers/odtn-driver/src/main/java/org/onosproject/drivers/odtn/impl/FlowRuleParser.java
new file mode 100644
index 0000000..e557176
--- /dev/null
+++ b/drivers/odtn-driver/src/main/java/org/onosproject/drivers/odtn/impl/FlowRuleParser.java
@@ -0,0 +1,84 @@
+/*
+ * 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.
+ *
+ * This work was partially supported by EC H2020 project METRO-HAUL (761727).
+ */
+
+package org.onosproject.drivers.odtn.impl;
+
+import org.onlab.util.Frequency;
+import org.onosproject.net.OchSignal;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.flow.FlowRule;
+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;
+
+/**
+ * Class that parses a FlowRule as passed by ONOS and
+ * extracts info such as the OchSignal, Portnumber.
+ *
+ *
+ * We iterate on the Selector. A Selector based on OCh is Rx
+ */
+public class FlowRuleParser {
+
+    public FlowRuleParser(FlowRule r) {
+        for (Criterion c : r.selector().criteria()) {
+            if (c instanceof OchSignalCriterion) {
+                rx = true;
+                ochSignal = ((OchSignalCriterion) c).lambda();
+            }
+            if (c instanceof PortCriterion) {
+                portNumber = ((PortCriterion) c).port();
+            }
+        }
+
+        for (Instruction i : r.treatment().immediate()) {
+            if (i instanceof
+                    L0ModificationInstruction.ModOchSignalInstruction) {
+                ochSignal =
+                        ((L0ModificationInstruction.ModOchSignalInstruction) i)
+                                .lambda();
+            }
+            if (i instanceof Instructions.OutputInstruction) {
+                portNumber = ((Instructions.OutputInstruction) i).port();
+            }
+        }
+    }
+
+    public boolean isReceiver() {
+        return rx;
+    }
+
+    public OchSignal getOchsignal() {
+        return ochSignal;
+    }
+
+    public PortNumber getPortNumber() {
+        return portNumber;
+    }
+
+    public Frequency getCentralFrequency() {
+        return ochSignal.centralFrequency();
+    }
+
+    private boolean rx = false;
+    private OchSignal ochSignal = null;
+    private PortNumber portNumber = null;
+}
diff --git a/drivers/odtn-driver/src/main/java/org/onosproject/drivers/odtn/impl/OdtnDriversLoader.java b/drivers/odtn-driver/src/main/java/org/onosproject/drivers/odtn/impl/OdtnDriversLoader.java
index 598a450..b57ecf8 100644
--- a/drivers/odtn-driver/src/main/java/org/onosproject/drivers/odtn/impl/OdtnDriversLoader.java
+++ b/drivers/odtn-driver/src/main/java/org/onosproject/drivers/odtn/impl/OdtnDriversLoader.java
@@ -19,9 +19,16 @@
 import org.onosproject.net.driver.AbstractDriverLoader;
 import org.onosproject.odtn.behaviour.ConfigurableTransceiver;
 
+
+import org.onosproject.net.optical.OpticalDevice;
+
 @Component(immediate = true)
 public class OdtnDriversLoader extends AbstractDriverLoader {
 
+    // OSGI: help bundle plugin discover runtime package dependency.
+    @SuppressWarnings("unused")
+    private OpticalDevice optical;
+
     // for injecting package dependencies for OSGi/BND
     ConfigurableTransceiver transceiver;
 
diff --git a/drivers/odtn-driver/src/main/java/org/onosproject/drivers/odtn/impl/OpenConfigConnectionCache.java b/drivers/odtn-driver/src/main/java/org/onosproject/drivers/odtn/impl/OpenConfigConnectionCache.java
new file mode 100644
index 0000000..5d2c7e7
--- /dev/null
+++ b/drivers/odtn-driver/src/main/java/org/onosproject/drivers/odtn/impl/OpenConfigConnectionCache.java
@@ -0,0 +1,147 @@
+/*
+ * 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.
+ *
+ * This work was partially supported by EC H2020 project METRO-HAUL (761727).
+ */
+
+package org.onosproject.drivers.odtn.impl;
+
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.flow.FlowId;
+import org.onosproject.net.flow.FlowRule;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Stores a set of Rules for given open config based devices in order to properly report them to the store.
+ */
+public final class OpenConfigConnectionCache {
+    private static final Logger log =
+            LoggerFactory.getLogger(OpenConfigConnectionCache.class);
+
+    private Map<DeviceId, Set<FlowRule>> mp = new HashMap<>();
+    private Map<DeviceId, Set<FlowRule>> smp = Collections.synchronizedMap(mp);
+
+    private static OpenConfigConnectionCache cache = null;
+
+    //banning public contraction
+    private OpenConfigConnectionCache() {
+    }
+
+    /**
+     * Initializes the cache if not already present.
+     * If present returns the existing one.
+     *
+     * @return single instance of cache
+     */
+    public static OpenConfigConnectionCache init() {
+        if (cache == null) {
+            cache = new OpenConfigConnectionCache();
+        }
+        return cache;
+    }
+
+    /**
+     * Returns the number of rules stored for a given device.
+     *
+     * @param did the device
+     * @return number of flows stored
+     */
+    public int size(DeviceId did) {
+        synchronized (smp) {
+            if (!smp.containsKey(did)) {
+                return 0;
+            }
+            return smp.get(did).size();
+        }
+    }
+
+    /**
+     * Returns the flow with given Id for the specific device.
+     *
+     * @param did    device id
+     * @param flowId flow id
+     * @return the flow rule
+     */
+    public FlowRule get(DeviceId did, FlowId flowId) {
+        synchronized (smp) {
+            if (!smp.containsKey(did)) {
+                return null;
+            }
+            Set<FlowRule> set = smp.get(did);
+            return set.stream()
+                    .filter(r -> r.id() == flowId)
+                    .findFirst()
+                    .orElse(null);
+        }
+    }
+
+    /**
+     * Returns all the flows for the specific device.
+     *
+     * @param did device id
+     * @return Set of flow rules
+     */
+    public Set<FlowRule> get(DeviceId did) {
+        synchronized (smp) {
+            if (!smp.containsKey(did)) {
+                return null;
+            }
+            return smp.get(did);
+        }
+    }
+
+    /**
+     * Add a flows for the specific device.
+     *
+     * @param did      device id
+     * @param flowRule the flow rule
+     */
+    public void add(DeviceId did, FlowRule flowRule) {
+        synchronized (smp) {
+            Set<FlowRule> set;
+            if (smp.containsKey(did)) {
+                set = smp.get(did);
+            } else {
+                set = new HashSet<FlowRule>();
+                log.warn("OpenConfigConnectionCache created for {}", did);
+                smp.put(did, set);
+            }
+            set.add(flowRule);
+        }
+    }
+
+    /**
+     * Add a flows for the specific device.
+     *
+     * @param did      device id
+     * @param flowRule the flow rule
+     */
+    public void remove(DeviceId did, FlowRule flowRule) {
+        synchronized (smp) {
+            if (!smp.containsKey(did)) {
+                return;
+            }
+            Set<FlowRule> set = smp.get(did);
+            set.removeIf(r2 -> r2.id() == flowRule.id());
+        }
+    }
+}
diff --git a/drivers/odtn-driver/src/main/java/org/onosproject/drivers/odtn/openconfig/TerminalDeviceDiscovery.java b/drivers/odtn-driver/src/main/java/org/onosproject/drivers/odtn/openconfig/TerminalDeviceDiscovery.java
index 26bfbbb..b65f15c 100644
--- a/drivers/odtn-driver/src/main/java/org/onosproject/drivers/odtn/openconfig/TerminalDeviceDiscovery.java
+++ b/drivers/odtn-driver/src/main/java/org/onosproject/drivers/odtn/openconfig/TerminalDeviceDiscovery.java
@@ -32,6 +32,7 @@
 
 import org.onlab.packet.ChassisId;
 
+
 import org.apache.commons.configuration.HierarchicalConfiguration;
 import org.apache.commons.configuration.XMLConfiguration;
 import org.apache.commons.configuration.tree.xpath.XPathExpressionEngine;
@@ -54,6 +55,10 @@
 import org.onosproject.net.SparseAnnotations;
 import org.onosproject.net.Port.Type;
 import org.onosproject.net.PortNumber;
+import org.onosproject.net.OchSignal;
+import org.onosproject.net.optical.device.OchPortHelper;
+import org.onosproject.net.OduSignalType;
+import org.onosproject.net.ChannelSpacing;
 
 import org.onosproject.netconf.NetconfController;
 import org.onosproject.netconf.NetconfDevice;
@@ -189,33 +194,6 @@
         filter.append(" </component>");
         filter.append("</components>");
         return filteredGetBuilder(filter.toString());
-        /* I am not sure the alternative method is more efficient
-           try {
-           DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
-           DocumentBuilder db = dbf.newDocumentBuilder();
-           Document doc = db.newDocument();
-           Element rpc = doc.createElementNS("urn:ietf:params:xml:ns:netconf:base:1.0", "rpc");
-           Element get = doc.createElement("get");
-           Element rpc = doc.createElement("rpc");
-           Element components = doc.createElementNS("http://openconfig.net/yang/platform", "components");
-           Element component = doc.createElement("component");
-           Element state = doc.createElement("state");
-           Element type  = doc.createElement("type");
-           type.setAttributeNS("http://www.w3.org/2000/xmlns/",
-           "xmlns:oc-platform-types", "http://openconfig.net/yang/platform-types");
-           type.appendChild(doc.createTextNode("oc-platform-types:OPERATING_SYSTEM"));
-           state.appendChild(type);
-           component.appendChild(state);
-           components.appendChild(component);
-           rpc.appendChild(components);
-           get.appendChild(rpc);
-           rpc.appendChild(get);
-           doc.appendChild(rpc);
-           return NetconfRpcParserUtil.toString(doc);
-           } catch (Exception e) {
-           throw new IllegalStateException(new NetconfException("Exception in getDeviceDetailsBuilder", e));
-           }
-         */
     }
 
 
@@ -226,7 +204,8 @@
      *    /components/
      */
     private String getDeviceComponentsBuilder() {
-        return filteredGetBuilder("<components xmlns='http://openconfig.net/yang/platform'/>");
+        return filteredGetBuilder(
+            "<components xmlns='http://openconfig.net/yang/platform'/>");
     }
 
 
@@ -282,8 +261,8 @@
 
         // Some defaults
         String vendor       = "NOVENDOR";
-        String hwVersion    = "0.1.1";
-        String swVersion    = "0.1.1";
+        String hwVersion    = "0.2.1";
+        String swVersion    = "0.2.1";
         String serialNumber = "0xCAFEBEEF";
         String chassisId    = "128";
 
@@ -312,9 +291,9 @@
         log.info("CHASSISID {}", chassisId);
         ChassisId cid = new ChassisId(Long.valueOf(chassisId, 10));
         return new DefaultDeviceDescription(did().uri(),
-                type, vendor, hwVersion, swVersion, serialNumber,
-                cid, defaultAvailable, annotations);
-    }
+                    type, vendor, hwVersion, swVersion, serialNumber,
+                    cid, defaultAvailable, annotations);
+        }
 
 
 
@@ -343,22 +322,8 @@
     @Override
     public List<PortDescription> discoverPortDetails() {
         try {
+            XPathExpressionEngine xpe = new XPathExpressionEngine();
             NetconfSession session = getNetconfSession(did());
-            /*
-            Note: the method may get called before the netconf session is established
-            2018-05-24 14:01:43,607 | INFO
-            event NetworkConfigEvent{time=2018-05-24T14:01:43.602Z, type=CONFIG_ADDED, ....
-            configClass=class org.onosproject.netconf.config.NetconfDeviceConfig
-
-            2018-05-24 14:01:43,623 | INFO  | vice-installer-2 | TerminalDeviceDiscovery
-            TerminalDeviceDiscovery::discoverPortDetails netconf:127.0.0.1:830
-
-            2018-05-24 14:01:43,624 | ERROR | vice-installer-2 | TerminalDeviceDiscovery
-            org.onosproject.onos-drivers-metrohaul - 1.14.0.SNAPSHOT | Exception discoverPortDetails()
-
-            2018-05-24 14:01:43,631 | INFO  | vice-installer-1 | NetconfControllerImpl
-            Creating NETCONF session to netconf:127.0.0.1:830 with apache-mina
-             */
             if (session == null) {
                 log.error("discoverPortDetails called with null session for {}", did());
                 return ImmutableList.of();
@@ -368,7 +333,7 @@
             String rpcReply = fut.get();
 
             XMLConfiguration xconf = (XMLConfiguration) XmlConfigParser.loadXmlString(rpcReply);
-            xconf.setExpressionEngine(new XPathExpressionEngine());
+            xconf.setExpressionEngine(xpe);
 
             HierarchicalConfiguration components = xconf.configurationAt("data/components");
             return parsePorts(components);
@@ -402,10 +367,8 @@
         return components.configurationsAt("component")
             .stream()
             .filter(component -> {
-                    return !component.getString("name", "unknown")
-                            .equals("unknown") &&
-                        component.getString("state/type", "unknown")
-                            .equals(OC_PLATFORM_TYPES_PORT);
+                    return !component.getString("name", "unknown").equals("unknown") &&
+                    component.getString("state/type", "unknown").equals(OC_PLATFORM_TYPES_PORT);
                     })
             .map(component -> {
                 try {
@@ -414,7 +377,8 @@
                 } catch (Exception e) {
                     return null;
                 }
-                })
+            }
+            )
             .filter(Objects::nonNull)
             .collect(Collectors.toList());
     }
@@ -429,19 +393,19 @@
      *
      * @return true or false
      */
-     private boolean hasSubComponentOfType(
+    private boolean hasSubComponentOfType(
             HierarchicalConfiguration component,
             HierarchicalConfiguration components,
             String type) {
         long count = component.configurationsAt("subcomponents/subcomponent")
             .stream()
             .filter(subcomponent -> {
-                    String scName = subcomponent.getString("name");
-                    StringBuilder sb = new StringBuilder("component[name='");
-                    sb.append(scName);
-                    sb.append("']/state/type");
-                    String scType = components.getString(sb.toString(), "unknown");
-                    return scType.equals(type);
+                        String scName = subcomponent.getString("name");
+                        StringBuilder sb = new StringBuilder("component[name='");
+                        sb.append(scName);
+                        sb.append("']/state/type");
+                        String scType = components.getString(sb.toString(), "unknown");
+                        return scType.equals(type);
                     })
             .count();
         return (count > 0);
@@ -457,88 +421,101 @@
      *
      * @return true or false
      */
-     private boolean hasOpticalChannelSubComponent(
-             HierarchicalConfiguration component,
-             HierarchicalConfiguration components) {
-         return hasSubComponentOfType(component, components,
-                 OC_TRANSPORT_TYPES_OPTICAL_CHANNEL);
-     }
+    private boolean hasOpticalChannelSubComponent(
+            HierarchicalConfiguration component,
+            HierarchicalConfiguration components) {
+        return hasSubComponentOfType(component, components,
+                OC_TRANSPORT_TYPES_OPTICAL_CHANNEL);
+    }
 
 
-     /**
-      *  Checks if a given component has a subcomponent of type TRANSCEIVER.
-      *
-      * @param component subtree to parse
-      * @param components the full components tree, to cross-ref in
-      *  case we need to check transceivers or optical channels.
-      *
-      * @return true or false
-      */
-     private boolean hasTransceiverSubComponent(
-             HierarchicalConfiguration component,
-             HierarchicalConfiguration components) {
-         return hasSubComponentOfType(component, components,
-                 OC_PLATFORM_TYPES_TRANSCEIVER);
-     }
+    /**
+     *  Checks if a given component has a subcomponent of type TRANSCEIVER.
+     *
+     * @param component subtree to parse
+     * @param components the full components tree, to cross-ref in
+     *  case we need to check transceivers or optical channels.
+     *
+     * @return true or false
+     */
+    private boolean hasTransceiverSubComponent(
+            HierarchicalConfiguration component,
+            HierarchicalConfiguration components) {
+        return hasSubComponentOfType(component, components,
+                OC_PLATFORM_TYPES_TRANSCEIVER);
+    }
 
 
-     /**
-      * Parses a component XML doc into a PortDescription.
-      *
-      * @param component subtree to parse. It must be a component ot type PORT.
-      * @param components the full components tree, to cross-ref in
-      *  case we need to check transceivers or optical channels.
-      *
-      * @return PortDescription or null if component does not have onos-index
-      */
-     private PortDescription parsePortComponent(
-             HierarchicalConfiguration component,
-             HierarchicalConfiguration components) {
-         Map<String, String> annotations = new HashMap<>();
-         String name = component.getString("name");
-         String type = component.getString("state/type");
-         log.info("Parsing Component {} type {}", name, type);
-         annotations.put(OdtnDeviceDescriptionDiscovery.OC_NAME, name);
-         annotations.put(OdtnDeviceDescriptionDiscovery.OC_TYPE, type);
+    /**
+     * Parses a component XML doc into a PortDescription.
+     *
+     * @param component subtree to parse. It must be a component ot type PORT.
+     * @param components the full components tree, to cross-ref in
+     *  case we need to check transceivers or optical channels.
+     *
+     * @return PortDescription or null if component does not have onos-index
+     */
+    private PortDescription parsePortComponent(
+            HierarchicalConfiguration component,
+            HierarchicalConfiguration components) {
+        Map<String, String> annotations = new HashMap<>();
+        String name = component.getString("name");
+        String type = component.getString("state/type");
+        log.info("Parsing Component {} type {}", name, type);
+        annotations.put(OdtnDeviceDescriptionDiscovery.OC_NAME, name);
+        annotations.put(OdtnDeviceDescriptionDiscovery.OC_TYPE, type);
+        // Store all properties as port properties
+        component.configurationsAt("properties/property")
+            .forEach(property -> {
+                    String pn = property.getString("name");
+                    String pv = property.getString("state/value");
+                    annotations.put(pn, pv);
+                    });
 
-         // Store all properties as port properties
-         component.configurationsAt("properties/property")
-             .forEach(property -> {
-                     String pn = property.getString("name");
-                     String pv = property.getString("state/value");
-                     annotations.put(pn, pv);
-                     });
+        // Assing an ONOS port number
+        PortNumber portNum;
+        if (annotations.containsKey(ONOS_PORT_INDEX)) {
+            portNum = PortNumber.portNumber(Long.parseLong(annotations.get(ONOS_PORT_INDEX)));
+        } else {
+            log.warn("PORT {} does not include onos-index, hashing...", name);
+            portNum = PortNumber.portNumber(name.hashCode());
+        }
+        log.debug("PORT {} number {}", name, portNum);
 
-         if (!annotations.containsKey(ONOS_PORT_INDEX)) {
-             log.warn("DEBUG: PORT {} does not include onos-index, skipping", name);
-             return null;
-         }
+        // The heuristic to know if it is client or line side
+        if (!annotations.containsKey(PORT_TYPE)) {
+            if (hasTransceiverSubComponent(component, components)) {
+                annotations.put(PORT_TYPE, OdtnPortType.CLIENT.value());
+            } else if (hasOpticalChannelSubComponent(component, components)) {
+                annotations.put(PORT_TYPE, OdtnPortType.LINE.value());
+            }
+        }
 
-         // The heuristic to know if it is client or line side
-         if (!annotations.containsKey(PORT_TYPE)) {
-             if (hasTransceiverSubComponent(component, components)) {
-                 annotations.put(PORT_TYPE, OdtnPortType.CLIENT.value());
-             } else if (hasOpticalChannelSubComponent(component, components)) {
-                 annotations.put(PORT_TYPE, OdtnPortType.LINE.value());
-             }
-         }
-
-         // Build the port
-         Builder builder = DefaultPortDescription.builder();
-         builder.withPortNumber(PortNumber.portNumber(
-                     Long.parseLong(annotations.get(ONOS_PORT_INDEX)), name));
-         if (annotations.get(PORT_TYPE)
-                 .equals(OdtnPortType.CLIENT.value())) {
-             log.info("Adding CLIENT port");
-             builder.type(Type.PACKET);
-         } else if (annotations.get(PORT_TYPE)
-                 .equals(OdtnPortType.LINE.value())) {
-             log.info("Adding LINE port");
-             builder.type(Type.OCH);
-         } else {
-             log.info("Unknown port added as CLIENT port");
-         }
-         builder.annotations(DefaultAnnotations.builder().putAll(annotations).build());
-         return builder.build();
-     }
+        // Build the port
+        // NOTE: using portNumber(id, name) breaks things. Intent parsing, port resorce management, etc. There seems
+        // to be an issue with resource mapping
+        if (annotations.get(PORT_TYPE)
+                .equals(OdtnPortType.CLIENT.value())) {
+            log.debug("Adding CLIENT port");
+            Builder builder = DefaultPortDescription.builder();
+            builder.type(Type.PACKET);
+            builder.withPortNumber(portNum);
+            builder.annotations(DefaultAnnotations.builder().putAll(annotations).build());
+            return builder.build();
+        }
+        if (annotations.get(PORT_TYPE)
+                .equals(OdtnPortType.LINE.value())) {
+            log.debug("Adding LINE port");
+            // TODO: To be configured
+            OchSignal signalId = OchSignal.newDwdmSlot(ChannelSpacing.CHL_50GHZ, 1);
+            return OchPortHelper.ochPortDescription(
+                    portNum, true,
+                    OduSignalType.ODU4, // TODO Client signal to be discovered
+                    true,
+                    signalId,
+                    DefaultAnnotations.builder().putAll(annotations).build());
+        }
+        log.error("Unknown port type");
+        return null;
+    }
 }
diff --git a/drivers/odtn-driver/src/main/java/org/onosproject/drivers/odtn/openconfig/TerminalDeviceFlowRuleProgrammable.java b/drivers/odtn-driver/src/main/java/org/onosproject/drivers/odtn/openconfig/TerminalDeviceFlowRuleProgrammable.java
new file mode 100644
index 0000000..d6f8aa2
--- /dev/null
+++ b/drivers/odtn-driver/src/main/java/org/onosproject/drivers/odtn/openconfig/TerminalDeviceFlowRuleProgrammable.java
@@ -0,0 +1,385 @@
+/*
+ * 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.
+
+ * This work was partially supported by EC H2020 project METRO-HAUL (761727).
+ */
+
+package org.onosproject.drivers.odtn.openconfig;
+
+import com.google.common.collect.ImmutableList;
+import org.onlab.util.Frequency;
+import org.onosproject.drivers.odtn.impl.FlowRuleParser;
+import org.onosproject.drivers.odtn.impl.OpenConfigConnectionCache;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+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 org.w3c.dom.Document;
+import org.xml.sax.InputSource;
+
+import javax.xml.namespace.NamespaceContext;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathFactory;
+import java.io.StringReader;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Implementation of FlowRuleProgrammable interface for
+ * OpenConfig terminal devices.
+ */
+public class TerminalDeviceFlowRuleProgrammable
+        extends AbstractHandlerBehaviour implements FlowRuleProgrammable {
+
+    private static final Logger log =
+            LoggerFactory.getLogger(TerminalDeviceFlowRuleProgrammable.class);
+
+    private static final String RPC_TAG_NETCONF_BASE =
+            "<rpc xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">";
+
+    private static final String RPC_CLOSE_TAG = "</rpc>";
+
+
+    /**
+     * 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 {
+                applyFlowRule(session, r);
+            } catch (Exception e) {
+                openConfigError("Error {}", e);
+                continue;
+            }
+            getConnectionCache().add(did(), r);
+            added.add(r);
+        }
+        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() {
+        OpenConfigConnectionCache 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 {
+                removeFlowRule(session, r);
+            } catch (Exception e) {
+                openConfigError("Error {}", e);
+                continue;
+            }
+            getConnectionCache().add(did(), r);
+            removed.add(r);
+        }
+        openConfigLog("removedFlowRules removed {}", removed.size());
+        return removed;
+    }
+
+    private OpenConfigConnectionCache getConnectionCache() {
+        return OpenConfigConnectionCache.init();
+    }
+
+    // Context so XPath expressions are aware of XML namespaces
+    private static final NamespaceContext NS_CONTEXT = new NamespaceContext() {
+        @Override
+        public String getNamespaceURI(String prefix) {
+            if (prefix.equals("oc-platform-types")) {
+                return "http://openconfig.net/yang/platform-types";
+            }
+            if (prefix.equals("oc-opt-term")) {
+                return "http://openconfig.net/yang/terminal-device";
+            }
+            return null;
+        }
+
+        @Override
+        public Iterator getPrefixes(String val) {
+            return null;
+        }
+
+        @Override
+        public String getPrefix(String uri) {
+            return null;
+        }
+    };
+
+
+    /**
+     * 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();
+    }
+
+
+    /**
+     * Construct a String with a Netconf filtered get RPC Message.
+     *
+     * @param filter A valid XML tree with the filter to apply in the get
+     * @return a String containing the RPC XML Document
+     */
+    private String filteredGetBuilder(String filter) {
+        StringBuilder rpc = new StringBuilder(RPC_TAG_NETCONF_BASE);
+        rpc.append("<get>");
+        rpc.append("<filter type='subtree'>");
+        rpc.append(filter);
+        rpc.append("</filter>");
+        rpc.append("</get>");
+        rpc.append(RPC_CLOSE_TAG);
+        return rpc.toString();
+    }
+
+    /**
+     * Construct a get request to retrieve Components and their
+     * properties (for the ONOS port, index).
+     *
+     * @return The filt content to send to the device.
+     */
+    private String getComponents() {
+        StringBuilder filt = new StringBuilder();
+        filt.append("<components xmlns='http://openconfig.net/yang/platform'>");
+        filt.append(" <component>");
+        filt.append("  <name/>");
+        filt.append("  <properties/>");
+        filt.append(" </component>");
+        filt.append("</components>");
+        return filteredGetBuilder(filt.toString());
+    }
+
+
+    /**
+     * Construct a get request to retrieve Optical Channels and
+     * the line port they are using.
+     * <p>
+     * This method is used to query the device so we can find the
+     * OpticalChannel component name that used a given line port.
+     *
+     * @return The filt content to send to the device.
+     */
+    private String getOpticalChannels() {
+        StringBuilder filt = new StringBuilder();
+        filt.append("<components xmlns='http://openconfig.net/yang/platform'>");
+        filt.append(" <component>");
+        filt.append("  <name/>");
+        filt.append("  <state/>");
+        filt.append("  <oc-opt-term:optical-channel xmlns:oc-opt-term"
+                + " = 'http://openconfig.net/yang/terminal-device'>");
+        filt.append("    <oc-opt-term:config>");
+        filt.append("     <oc-opt-term:line-port/>");
+        filt.append("    </oc-opt-term:config>");
+        filt.append("  </oc-opt-term:optical-channel>");
+        filt.append(" </component>");
+        filt.append("</components>");
+        return filteredGetBuilder(filt.toString());
+    }
+
+
+    /**
+     * Get the OpenConfig component name for the OpticalChannel component
+     * associated to the passed port number (typically a line side port, already
+     * mapped to ONOS port).
+     *
+     * @param session    The netconf session to the device.
+     * @param portNumber ONOS port number of the Line port ().
+     * @return the channel component name or null
+     */
+    private String getOpticalChannel(NetconfSession session,
+                                     PortNumber portNumber) {
+        try {
+            checkNotNull(session);
+            checkNotNull(portNumber);
+            XPath xp = XPathFactory.newInstance().newXPath();
+            xp.setNamespaceContext(NS_CONTEXT);
+
+            // Get the port name for a given port number
+            // We could iterate the port annotations too, no need to
+            // interact with device.
+            String xpGetPortName =
+                    "/rpc-reply/data/components/"
+                            +
+                            "component[./properties/property[name='onos-index']/config/value ='" +
+                            portNumber.toLong() + "']/"
+                            + "name/text()";
+
+            // Get all the components and their properties
+            String compReply = session.rpc(getComponents()).get();
+            DocumentBuilderFactory builderFactory =
+                    DocumentBuilderFactory.newInstance();
+            DocumentBuilder builder = builderFactory.newDocumentBuilder();
+            Document document =
+                    builder.parse(new InputSource(new StringReader(compReply)));
+            String portName = xp.evaluate(xpGetPortName, document);
+            String xpGetOptChannelName =
+                    "/rpc-reply/data/components/"
+                            + "component[./optical-channel/config/line-port='" + portName +
+                            "']/name/text()";
+
+            String optChannelReply = session.rpc(getOpticalChannels()).get();
+            document =
+                    builder.parse(new InputSource(new StringReader(optChannelReply)));
+            return xp.evaluate(xpGetOptChannelName, document);
+        } catch (Exception e) {
+            openConfigError("Exception {}", e);
+            return null;
+        }
+    }
+
+
+    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");
+        }
+    }
+
+
+    /**
+     * 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.
+     * @throws NetconfException if exchange goes wrong
+     */
+    protected void applyFlowRule(NetconfSession session, FlowRule r) throws NetconfException {
+        FlowRuleParser frp = new FlowRuleParser(r);
+        if (!frp.isReceiver()) {
+            String optChannel = getOpticalChannel(session, frp.getPortNumber());
+            setOpticalChannelFrequency(session, optChannel,
+                    frp.getCentralFrequency());
+        }
+    }
+
+
+    protected void removeFlowRule(NetconfSession session, FlowRule r)
+            throws NetconfException {
+        FlowRuleParser frp = new FlowRuleParser(r);
+        if (!frp.isReceiver()) {
+            String optChannel = getOpticalChannel(session, frp.getPortNumber());
+            setOpticalChannelFrequency(session, optChannel, Frequency.ofMHz(0));
+        }
+    }
+}
diff --git a/drivers/odtn-driver/src/main/java/org/onosproject/drivers/odtn/openconfig/TerminalDeviceLambdaQuery.java b/drivers/odtn-driver/src/main/java/org/onosproject/drivers/odtn/openconfig/TerminalDeviceLambdaQuery.java
new file mode 100644
index 0000000..acbe453
--- /dev/null
+++ b/drivers/odtn-driver/src/main/java/org/onosproject/drivers/odtn/openconfig/TerminalDeviceLambdaQuery.java
@@ -0,0 +1,56 @@
+/*
+ * 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.
+ *
+ * This work was partially supported by EC H2020 project METRO-HAUL (761727).
+ */
+
+package org.onosproject.drivers.odtn.openconfig;
+
+import org.onosproject.driver.optical.query.CBandLambdaQuery;
+import org.onosproject.net.ChannelSpacing;
+import org.onosproject.net.OchSignal;
+import org.onosproject.net.PortNumber;
+import org.slf4j.Logger;
+
+import java.util.Set;
+
+import static org.slf4j.LoggerFactory.getLogger;
+
+
+/**
+ * Lambda Query for OpenConfig based terminal devices.
+ */
+public class TerminalDeviceLambdaQuery extends CBandLambdaQuery {
+
+    protected static final Logger log = getLogger(TerminalDeviceLambdaQuery.class);
+
+    @Override
+    public Set<OchSignal> queryLambdas(PortNumber port) {
+        log.debug("OPENCONFIG: queried lambdas for port {}", port);
+
+        // Profile 1
+        channelSpacing = ChannelSpacing.CHL_50GHZ;
+        lambdaCount = 96;
+        slotGranularity = 4;
+        return super.queryLambdas(port);
+
+        // Another option, commented until best option for transceiver tunability deduction is chosen Profile 2
+        // short lambdaCount = 96;
+        // // fixed grid lambdas of 50GHz width
+        // return IntStream.rangeClosed(1, lambdaCount)
+        //         .mapToObj(x -> OchSignal.newDwdmSlot(ChannelSpacing.CHL_50GHZ, x))
+        //         .collect(Collectors.toSet());
+    }
+}
diff --git a/drivers/odtn-driver/src/main/resources/odtn-drivers.xml b/drivers/odtn-driver/src/main/resources/odtn-drivers.xml
index 1657d65..618419b 100644
--- a/drivers/odtn-driver/src/main/resources/odtn-drivers.xml
+++ b/drivers/odtn-driver/src/main/resources/odtn-drivers.xml
@@ -23,10 +23,18 @@
         <behaviour api="org.onosproject.odtn.behaviour.ConfigurableTransceiver"
                   impl="org.onosproject.odtn.behaviour.PlainTransceiver"/>
     </driver>
-    <driver name="terminal-device" manufacturer="CTTC" hwVersion="" swVersion="">
+
+    <driver name="terminal-device"  manufacturer="CTTC" hwVersion="" swVersion="">
         <behaviour api="org.onosproject.net.device.DeviceDescriptionDiscovery"
                   impl="org.onosproject.drivers.odtn.openconfig.TerminalDeviceDiscovery"/>
+        <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"/>
+        <behaviour api="org.onosproject.net.flow.FlowRuleProgrammable"
+                   impl="org.onosproject.drivers.odtn.openconfig.TerminalDeviceFlowRuleProgrammable"/>
     </driver>
+
     <driver name="infinera-xt3300" manufacturer="infinera" hwVersion="xt3300" swVersion="18.0">
         <behaviour api="org.onosproject.net.device.DeviceDescriptionDiscovery"
                    impl="org.onosproject.drivers.odtn.InfineraOpenConfigDeviceDiscovery"/>