ONOS-7975 ODTN OpenROADM v2.2 NetConf drivers.
Change-Id: I7b4115146bfda7b0061e095ca189ea4d2e9d993d
diff --git a/drivers/odtn-driver/src/main/java/org/onosproject/drivers/odtn/openroadm/OpenRoadmDeviceDescription.java b/drivers/odtn-driver/src/main/java/org/onosproject/drivers/odtn/openroadm/OpenRoadmDeviceDescription.java
new file mode 100644
index 0000000..7a19fa9
--- /dev/null
+++ b/drivers/odtn-driver/src/main/java/org/onosproject/drivers/odtn/openroadm/OpenRoadmDeviceDescription.java
@@ -0,0 +1,575 @@
+/*
+ * 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.openroadm;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.collect.ImmutableList;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.apache.commons.configuration.HierarchicalConfiguration;
+import org.apache.commons.configuration.XMLConfiguration;
+import org.apache.commons.configuration.tree.xpath.XPathExpressionEngine;
+import org.onlab.packet.ChassisId;
+import org.onlab.util.Frequency;
+import org.onosproject.drivers.utilities.XmlConfigParser;
+import org.onosproject.net.AnnotationKeys;
+import org.onosproject.net.ChannelSpacing;
+import org.onosproject.net.DefaultAnnotations;
+import org.onosproject.net.OchSignal;
+import org.onosproject.net.OduSignalType;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.device.DefaultDeviceDescription;
+import org.onosproject.net.device.DeviceDescription;
+import org.onosproject.net.device.DeviceDescriptionDiscovery;
+import org.onosproject.net.device.PortDescription;
+import org.onosproject.net.intent.OpticalPathIntent;
+import org.onosproject.net.optical.device.OchPortHelper;
+import org.onosproject.net.optical.device.OmsPortHelper;
+import org.onosproject.netconf.NetconfDevice;
+import org.onosproject.netconf.NetconfException;
+import org.onosproject.netconf.NetconfSession;
+
+import java.util.concurrent.ExecutionException;
+/**
+ * Driver Implementation of the DeviceDescrption discovery for OpenROADM.
+ */
+public class OpenRoadmDeviceDescription extends OpenRoadmNetconfHandlerBehaviour
+ implements DeviceDescriptionDiscovery {
+
+ // These annotations are added to the device and ports
+ public final class AnnotationKeys {
+ public static final String OPENROADM_NODEID = "openroadm-node-id";
+ public static final String OPENROADM_CIRCUIT_PACK_NAME =
+ "openroadm-circuit-pack-name";
+ public static final String OPENROADM_PORT_NAME = "openroadm-port-name";
+ public static final String OPENROADM_PARTNER_CIRCUIT_PACK_NAME =
+ "openroadm-partner-circuit-pack-name";
+ public static final String OPENROADM_PARTNER_PORT_NAME =
+ "openroadm-partner-port-name";
+ public static final String OPENROADM_LOGICAL_CONNECTION_POINT =
+ "openroadm-logical-connection-point";
+ private AnnotationKeys() {
+ // utility class
+ }
+ }
+
+ public static final ChannelSpacing CHANNEL_SPACING =
+ ChannelSpacing.CHL_50GHZ;
+
+
+ /*
+ * The following 2 values are not specified by the OpenROADM standard,
+ * but they are a reasonable default for a tunable C-band, defined from
+ * Channel C1 at 191.35 to C96 at 196.10 GHz (for a spacing at 50GHz)
+ */
+ public static final Frequency START_CENTER_FREQ = Frequency.ofGHz(191_350);
+ public static final Frequency STOP_CENTER_FREQ = Frequency.ofGHz(196_100);
+
+
+ public static final String OPENROADM_DEVICE_OPEN = //
+ "<org-openroadm-device xmlns=\"http://org/openroadm/device\">";
+ public static final String OPENROADM_DEVICE_CLOSE = //
+ "</org-openroadm-device>";
+
+
+ /**
+ * Builds a request to get OpenRoadm Device main node (within root).
+ *
+ * @param nodeTag the tag with the name to get e.g. <info/>
+ *
+ * @return A string with the Netconf RPC for a get with subtree info
+ */
+ private String getDeviceXmlNodeBuilder(final String nodeTag) {
+ StringBuilder filter = new StringBuilder();
+ filter.append(OPENROADM_DEVICE_OPEN);
+ filter.append(nodeTag);
+ filter.append(OPENROADM_DEVICE_CLOSE);
+ return filteredGetBuilder(filter.toString());
+ }
+
+ /**
+ * Builds a request to get Device details (<info>).
+ *
+ * @return A string with the Netconf RPC for a get with subtree info
+ */
+ private String getDeviceDetailsBuilder() {
+ return getDeviceXmlNodeBuilder("<info/>");
+ }
+
+ /**
+ * Builds a request to get Ports data (<circuit-packs>).
+ *
+ * @return A string with the Netconf RPC
+ */
+ private String getDeviceCircuitPacksBuilder() {
+ return getDeviceXmlNodeBuilder("<circuit-packs/>");
+ }
+
+ /**
+ * Builds a request to get External Links data (<external-link>).
+ *
+ * @return A string with the Netconf RPC
+ */
+ private String getDeviceExternalLinksBuilder() {
+ return getDeviceXmlNodeBuilder("<external-link/>");
+ }
+
+ /**
+ * Builds a request to get Device Degrees, config and operational data.
+ *
+ * @return A string with the Netconf RPC for a get with subtree rpcing based
+ * on /components/component/state/type being oc-platform-types:PORT
+ */
+ private String getDeviceDegreesBuilder() {
+ return getDeviceXmlNodeBuilder("<degree/>");
+ }
+
+ /**
+ * Builds a request to get Device SharedRiskGroups, config and operational
+ * data.
+ *
+ * @return A string with the Netconf RPC for a get with subtree
+ */
+ private String getDeviceSharedRiskGroupsBuilder() {
+ return getDeviceXmlNodeBuilder("<shared-risk-group/>");
+ }
+
+ /**
+ * Builds a request to get Ports data.
+ * Changed to XPath and added one based on classic filters since some agents
+ * do not support xpath filtering.
+ *
+ * @return A string with the Netconf RPC
+ */
+ private String getDeviceExternalPortsBuilderXPath() {
+ StringBuilder filter = new StringBuilder();
+ filter.append(
+ "/org-openroadm-device/circuit-packs/ports[port-qual='roadm-external']");
+ return xpathFilteredGetBuilder(filter.toString());
+ }
+
+ /**
+ * Builds a request to get Ports data.
+ *
+ * @return A string with the Netconf RPC
+ */
+ private String getDeviceExternalPortsBuilder() {
+ StringBuilder filter = new StringBuilder();
+ filter.append(OPENROADM_DEVICE_OPEN);
+ filter.append("<circuit-packs>");
+ filter.append(" <ports>");
+ filter.append(" <port-qual>roadm-external</port-qual>");
+ filter.append(" </ports>");
+ filter.append("</circuit-packs>");
+ filter.append(OPENROADM_DEVICE_CLOSE);
+ return filteredGetBuilder(filter.toString());
+ }
+
+ /**
+ * Builds a request to get External Links data.
+ *
+ * @return A string with the Netconf RPC
+ */
+ private String getDeviceExternalLinksBuilderXpath() {
+ StringBuilder filter = new StringBuilder();
+ filter.append("/org-openroadm-device/external-link");
+ return xpathFilteredGetBuilder(filter.toString());
+ }
+
+ /**
+ * Builds a request to get External Links data.
+ *
+ * @param nodeId OpenROADM node identifier.
+ * @param circuitPackName name of the circuit part of the port.
+ * @param portName name of the port.
+ * @return A string with the Netconf RPC
+ */
+ private String getDeviceExternalLinkForPortBuilderXPath(
+ String nodeId, String circuitPackName, String portName) {
+ StringBuilder filter = new StringBuilder();
+ filter.append("/org-openroadm-device/external-link[");
+ filter.append("./source/node-id='");
+ filter.append(nodeId);
+ filter.append("' and ");
+ filter.append("./source/circuit-pack-name='");
+ filter.append(circuitPackName);
+ filter.append("' and ");
+ filter.append("./source/port-name='");
+ filter.append(portName);
+ filter.append("']");
+ return xpathFilteredGetBuilder(filter.toString());
+ }
+
+ private String getDeviceExternalLinkForPortBuilder(String nodeId,
+ String circuitPackName,
+ String portName) {
+ StringBuilder filter = new StringBuilder();
+ filter.append(OPENROADM_DEVICE_OPEN);
+ filter.append("<external-link>");
+ filter.append(" <source>");
+ filter.append(" <node-id>");
+ filter.append(nodeId);
+ filter.append("</node-id>");
+ filter.append(" <circuit-pack-name>");
+ filter.append(circuitPackName);
+ filter.append("</circuit-pack-name>");
+ filter.append(" <port-name>");
+ filter.append(portName);
+ filter.append("</port-name>");
+ filter.append(" </source>");
+ filter.append("</external-link>");
+ filter.append(OPENROADM_DEVICE_CLOSE);
+ return xpathFilteredGetBuilder(filter.toString());
+ }
+
+
+ /**
+ * Returns a DeviceDescription with Device info.
+ *
+ * @return DeviceDescription or null
+ */
+ @Override
+ public DeviceDescription discoverDeviceDetails() {
+ boolean defaultAvailable = true;
+ NetconfDevice ncDevice = getNetconfDevice();
+ if (ncDevice == null) {
+ log.error("ONOS Error: Device reachable, deviceID {} is not in Map", did());
+ return null;
+ }
+ DefaultAnnotations.Builder annotationsBuilder =
+ DefaultAnnotations.builder();
+
+ // Some defaults
+ String vendor = "UNKNOWN";
+ String hwVersion = "2.2.0";
+ String swVersion = "2.2.0";
+ String serialNumber = "0x0000";
+ String chassisId = "0";
+ String nodeType = "rdm";
+
+ // Get the session, if null, at least we can use the defaults.
+ NetconfSession session = getNetconfSession(did());
+ if (session != null) {
+ try {
+ String reply = session.rpc(getDeviceDetailsBuilder()).get();
+ XMLConfiguration xconf =
+ (XMLConfiguration) XmlConfigParser.loadXmlString(reply);
+ String nodeId =
+ xconf.getString("data.org-openroadm-device.info.node-id", "");
+ if (nodeId.equals("")) {
+ log.error("[OPENROADM] {} org-openroadm-device node-id undefined, returning", did());
+ return null;
+ }
+ annotationsBuilder.set(AnnotationKeys.OPENROADM_NODEID, nodeId);
+ nodeType = xconf.getString("data.org-openroadm-device.info.node-type", "");
+ if (nodeType.equals("")) {
+ log.error("[OPENROADM] {} empty node-type", did());
+ return null;
+ }
+ vendor = xconf.getString(
+ "data.org-openroadm-device.info.vendor", vendor);
+ hwVersion = xconf.getString(
+ "data.org-openroadm-device.info.model", hwVersion);
+ swVersion = xconf.getString(
+ "data.org-openroadm-device.info.softwareVersion", swVersion);
+ serialNumber = xconf.getString(
+ "data.org-openroadm-device.info.serial-id", serialNumber);
+ chassisId = xconf.getString(
+ "data.org-openroadm-device.info.node-number", chassisId);
+
+ // GEOLOCATION
+ String longitudeStr = xconf.getString(
+ "data.org-openroadm-device.info.geoLocation.longitude");
+ String latitudeStr = xconf.getString(
+ "data.org-openroadm-device.info.geoLocation.latitude");
+ if (longitudeStr != null && latitudeStr != null) {
+ annotationsBuilder
+ .set(org.onosproject.net.AnnotationKeys.LONGITUDE,
+ longitudeStr)
+ .set(org.onosproject.net.AnnotationKeys.LATITUDE,
+ latitudeStr);
+ }
+ } catch (NetconfException | InterruptedException | ExecutionException e) {
+ log.error("[OPENROADM] {} exception", did());
+ return null;
+ }
+ } else {
+ log.debug("[OPENROADM] - No session {}", did());
+ }
+
+ log.debug("[OPENROADM] {} - VENDOR {} HWVERSION {} SWVERSION {} SERIAL {} CHASSIS {}",
+ did(), vendor, hwVersion, swVersion, serialNumber, chassisId);
+ ChassisId cid = new ChassisId(Long.valueOf(chassisId, 10));
+ /*
+ * OpenROADM defines multiple devices (node types). This driver has been tested with
+ * ROADMS, (node type, "rdm"). Other devices can also be discovered, and this code is here
+ * for future developments - untested - it is likely that the XML documents
+ * are model specific.
+ */
+ org.onosproject.net.Device.Type type;
+ if (nodeType.equals("rdm")) {
+ type = org.onosproject.net.Device.Type.ROADM;
+ } else if (nodeType.equals("ila")) {
+ type = org.onosproject.net.Device.Type.OPTICAL_AMPLIFIER;
+ } else if (nodeType.equals("xpdr")) {
+ type = org.onosproject.net.Device.Type.TERMINAL_DEVICE;
+ } else if (nodeType.equals("extplug")) {
+ type = org.onosproject.net.Device.Type.OTHER;
+ } else {
+ log.error("[OPENROADM] {} unsupported node-type", did());
+ return null;
+ }
+ DeviceDescription desc = new DefaultDeviceDescription(
+ did().uri(), type, vendor, hwVersion, swVersion, serialNumber, cid,
+ defaultAvailable, annotationsBuilder.build());
+ return desc;
+ }
+
+
+
+
+ /**
+ * Get the external links as a list of XML hieriarchical configs.
+ * @param session the NETConf session to the OpenROADM device.
+ * @return a list of hierarchical conf. each one external link.
+ */
+ List<HierarchicalConfiguration> getExternalLinks(NetconfSession session) {
+ try {
+ String reply = session.rpc(getDeviceExternalLinksBuilder()).get();
+ XMLConfiguration extLinksConf = //
+ (XMLConfiguration) XmlConfigParser.loadXmlString(reply);
+ extLinksConf.setExpressionEngine(new XPathExpressionEngine());
+ return extLinksConf.configurationsAt(
+ "/data/org-openroadm-device/external-link");
+ } catch (NetconfException | InterruptedException | ExecutionException e) {
+ log.error("[OPENROADM] {} exception getting external links", did());
+ return ImmutableList.of();
+ }
+ }
+
+
+ /**
+ * Get the circuit packs from the device as a list of XML hierarchical configs.
+ * @param session the NETConf session to the OpenROADM device.
+ * @return a list of hierarchical conf. each one circuit pack.
+ */
+ List<HierarchicalConfiguration> getCircuitPacks(NetconfSession session) {
+ try {
+ String reply = session.rpc(getDeviceCircuitPacksBuilder()).get();
+ XMLConfiguration cpConf = //
+ (XMLConfiguration) XmlConfigParser.loadXmlString(reply);
+ cpConf.setExpressionEngine(new XPathExpressionEngine());
+ return cpConf.configurationsAt(
+ "/data/org-openroadm-device/circuit-packs");
+ } catch (NetconfException | InterruptedException | ExecutionException e) {
+ log.error("[OPENROADM] {} exception getting circuit packs", did());
+ return ImmutableList.of();
+ }
+ }
+
+ /**
+ * Returns a list of PortDescriptions for the device.
+ *
+ * @return a list of descriptions.
+ */
+ @Override
+ public List<PortDescription> discoverPortDetails() {
+ NetconfSession session = getNetconfSession(did());
+ if (session == null) {
+ log.error("discoverPortDetails null session for {}", did());
+ return ImmutableList.of();
+ }
+ if (!getDevice().annotations().keys().contains("openroadm-node-id")) {
+ log.error("PortDiscovery before DeviceDiscovery, using netconf");
+ return ImmutableList.of();
+ }
+ String nodeId = getDevice().annotations().value("openroadm-node-id");
+ List<PortDescription> list = new ArrayList<PortDescription>();
+ List<HierarchicalConfiguration> circuitPacks = getCircuitPacks(session);
+ /*
+ * Iterate all the ports. We need to pass the whole circuitPacks list
+ * because some port data refers to ports in other circuit packs
+ * (reverse), in addition to pass the current circuit pack name, list of
+ * external ports etc
+ */
+ for (HierarchicalConfiguration c : circuitPacks) {
+ parsePorts(list, nodeId, // c contains the whole circuit pack
+ c.getString("circuit-pack-name"), //
+ c.configurationsAt(
+ "ports[port-qual='roadm-external']"), // ext ports
+ circuitPacks, getExternalLinks(session));
+ }
+ return list;
+ }
+
+ /**
+ * Parses port information.
+ *
+ * @param list List of port descriptions to append to.
+ * @param nodeId OpenROADM node identifier.
+ * @param circuitPackName Name of the circuit pack the ports belong to
+ * @param ports hierarchical conf containing all the ports for the circuit
+ * pack
+ * @param circuitPacks all the circuit packs (to correlate data).
+ * @param extLinks Hierarchical configuration containing all the ext.
+ * links.
+ */
+ protected void parsePorts(List<PortDescription> list, String nodeId,
+ String circuitPackName,
+ List<HierarchicalConfiguration> ports,
+ List<HierarchicalConfiguration> circuitPacks,
+ List<HierarchicalConfiguration> extLinks) {
+ checkNotNull(nodeId);
+ checkNotNull(circuitPackName);
+ for (HierarchicalConfiguration port : ports) {
+ try {
+ String portName = checkNotNull(port.getString("port-name"));
+ long portNum = Long.parseLong(port.getString("label"));
+ PortNumber pNum = PortNumber.portNumber(portNum);
+ PortNumber reversepNum = findReversePort(port, circuitPacks);
+ // To see if we have an external port
+ HierarchicalConfiguration eLink = null;
+ for (HierarchicalConfiguration extLink : extLinks) {
+ String eln =
+ checkNotNull(extLink.getString("external-link-name"));
+ String esnid =
+ checkNotNull(extLink.getString("source/node-id"));
+ String escpn =
+ checkNotNull(extLink.getString("source/circuit-pack-name"));
+ String espn =
+ checkNotNull(extLink.getString("source/port-name"));
+ if (nodeId.equals(esnid) && circuitPackName.equals(escpn) &&
+ portName.equals(espn)) {
+ eLink = extLink;
+ }
+ }
+ PortDescription pd = parsePortComponent(
+ nodeId, circuitPackName, pNum, reversepNum, port, eLink);
+ if (pd != null) {
+ list.add(pd);
+ }
+ } catch (NetconfException e) {
+ log.error("[OPENROADM] {} NetConf exception", did());
+ return;
+ }
+ }
+ }
+
+ /**
+ * Given a device port (external), return its patner/reverse port.
+ *
+ * @param thisPort the port for which we are looking for the reverse port.
+ * @param circuitPacks all the circuit packs (to correlate data).
+ * @return the port number for the reverse port.
+ * @throws NetconfException .
+ */
+ protected PortNumber
+ findReversePort(HierarchicalConfiguration thisPort,
+ List<HierarchicalConfiguration> circuitPacks)
+ throws NetconfException {
+ String partnerCircuitPackName =
+ checkNotNull(thisPort.getString("partner-port/circuit-pack-name"));
+ String partnerPortName =
+ checkNotNull(thisPort.getString("partner-port/port-name"));
+ for (HierarchicalConfiguration c : circuitPacks) {
+ if (!partnerCircuitPackName.equals(
+ c.getString("circuit-pack-name"))) {
+ continue;
+ }
+ for (HierarchicalConfiguration thatPort :
+ c.configurationsAt("ports[port-qual='roadm-external']")) {
+ String thatPortName = thatPort.getString("port-name");
+ if (partnerPortName.equals(thatPortName)) {
+ long thatPortNum =
+ Long.parseLong(thatPort.getString("label"));
+ return PortNumber.portNumber(thatPortNum);
+ }
+ }
+ }
+ // We should not reach here
+ throw new NetconfException("missing partner/reverse port info");
+ }
+
+ /**
+ * Parses a component XML doc into a PortDescription.
+ * An OMS port description is constructed from XML parsed data.
+ *
+ * @param port the port to parse
+ * @return PortDescription or null
+ */
+ private PortDescription
+ parsePortComponent(String nodeId, String circuitPackName, PortNumber pNum,
+ PortNumber reversepNum, HierarchicalConfiguration port,
+ HierarchicalConfiguration extLink) {
+ Map<String, String> annotations = new HashMap<>();
+ annotations.put(AnnotationKeys.OPENROADM_NODEID, nodeId);
+ annotations.put(AnnotationKeys.OPENROADM_CIRCUIT_PACK_NAME,
+ circuitPackName);
+ annotations.put(AnnotationKeys.OPENROADM_PORT_NAME,
+ port.getString("port-name"));
+ annotations.put(AnnotationKeys.OPENROADM_PARTNER_CIRCUIT_PACK_NAME,
+ port.getString("partner-port/circuit-pack-name", ""));
+ annotations.put(AnnotationKeys.OPENROADM_PARTNER_PORT_NAME,
+ port.getString("partner-port/port-name", ""));
+ annotations.put(AnnotationKeys.OPENROADM_LOGICAL_CONNECTION_POINT,
+ port.getString("logical-connection-point", ""));
+ // Annotate the reverse port, this is needed for bidir intents.
+ annotations.put(OpticalPathIntent.REVERSE_PORT_ANNOTATION_KEY,
+ Long.toString(reversepNum.toLong()));
+
+ // for backwards compatibility
+ annotations.put("logical-connection-point",
+ port.getString("logical-connection-point", ""));
+ // Annotate external link if we found one for this port
+ if (extLink != null) {
+ String ednid = extLink.getString("destination/node-id");
+ String edcpn = extLink.getString("destination/circuit-pack-name");
+ String edpn = extLink.getString("destination/port-name");
+ annotations.put("openroadm-external-node-id", ednid);
+ annotations.put("openroadm-external-circuit-pack-name", edcpn);
+ annotations.put("openroadm-external-port-name", edpn);
+ }
+
+ /*
+ * Declare the actual optical port:
+ * Assumptions: client ports are OCh, assumed to carry ODU4 (should be
+ * configurable)
+ */
+ if (port.getString("port-wavelength-type", "wavelength")
+ .equals("wavelength")) {
+ // OchSignal is needed for OchPortDescription constructor, but it's
+ // tunable
+ OchSignal signalId =
+ OchSignal.newDwdmSlot(ChannelSpacing.CHL_50GHZ, 3);
+ return OchPortHelper.ochPortDescription(
+ pNum, true /* enabled */, OduSignalType.ODU4, true /* tunable */,
+ signalId,
+ DefaultAnnotations.builder().putAll(annotations).build());
+ } else {
+ return OmsPortHelper.omsPortDescription(
+ pNum, true /* enabled */,
+ // Relationship : START and STOP Freq not being used (See
+ // LambdaQuery)
+ START_CENTER_FREQ, STOP_CENTER_FREQ, CHANNEL_SPACING.frequency(),
+ DefaultAnnotations.builder().putAll(annotations).build());
+ }
+ }
+}