| /* |
| * 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()); |
| } |
| } |
| } |