ONOS-7828 ODTN OpenConfig FlowRule and LambdaQuery.
Change-Id: I03212d2b8b544bae95f9f085eb1cadf883e5611d
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"/>