| /* |
| * Copyright 2017-present Open Networking Foundation |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package org.onosproject.drivers.lumentum; |
| |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.Lists; |
| |
| import org.apache.commons.configuration.HierarchicalConfiguration; |
| import org.apache.commons.configuration.XMLConfiguration; |
| import org.apache.commons.lang3.StringUtils; |
| |
| import org.onlab.packet.ChassisId; |
| import org.onlab.util.Frequency; |
| import org.onosproject.drivers.utilities.XmlConfigParser; |
| import org.onosproject.net.ChannelSpacing; |
| import org.onosproject.net.SparseAnnotations; |
| import org.onosproject.net.DefaultAnnotations; |
| import org.onosproject.net.DeviceId; |
| import org.onosproject.net.Device; |
| import org.onosproject.net.Port; |
| import org.onosproject.net.PortNumber; |
| import org.onosproject.net.AnnotationKeys; |
| import org.onosproject.net.device.DefaultDeviceDescription; |
| import org.onosproject.net.device.DefaultPortDescription; |
| import org.onosproject.net.device.DeviceDescription; |
| import org.onosproject.net.device.DeviceDescriptionDiscovery; |
| import org.onosproject.net.device.DeviceService; |
| import org.onosproject.net.device.PortDescription; |
| import org.onosproject.net.driver.AbstractHandlerBehaviour; |
| import org.onosproject.net.intent.OpticalPathIntent; |
| import org.onosproject.netconf.NetconfController; |
| import org.onosproject.netconf.NetconfException; |
| import org.onosproject.netconf.NetconfSession; |
| |
| import org.slf4j.Logger; |
| |
| import java.io.ByteArrayInputStream; |
| import java.util.List; |
| |
| import static com.google.common.base.Preconditions.checkNotNull; |
| import static org.onosproject.net.optical.device.OmsPortHelper.omsPortDescription; |
| import static org.slf4j.LoggerFactory.getLogger; |
| |
| /** |
| * Device description behaviour for Lumentum ROADM-A Whitebox devices using NETCONF. |
| */ |
| public class LumentumNetconfRoadmDiscovery |
| extends AbstractHandlerBehaviour implements DeviceDescriptionDiscovery { |
| |
| private static final String PHYSICAL_PORT = "data.physical-ports.physical-port"; |
| |
| private static final String DN = "dn"; |
| private static final String DN_PORT = "port="; |
| private static final String PORT_EXTENSION = "port-extension"; |
| protected static final String OPTICAL_INPUT = "port-optical-input"; |
| protected static final String OPTICAL_OUTPUT = "port-optical-output"; |
| private static final String PORT_PLUGGABLE = "port-pluggable"; |
| private static final String PORT_ETHERNET = "port-ethernet"; |
| |
| private static final String MAINTENANCE_STATE = "config.maintenance-state"; |
| private static final String PORT_SPEED = "config.loteeth:port-speed"; |
| private static final String IN_SERVICE = "in-service"; |
| private static final String PORT_NAME = "entity-description"; |
| |
| public static final ChannelSpacing CHANNEL_SPACING_50 = ChannelSpacing.CHL_50GHZ; |
| public static final Frequency START_CENTER_FREQ_50 = Frequency.ofGHz(191_350); |
| public static final Frequency END_CENTER_FREQ_50 = Frequency.ofGHz(196_100); |
| |
| private static final int MIN_MUX_PORT = 4101; |
| private static final int MAX_MUX_PORT = 4120; |
| private static final int MIN_DEM_PORT = 5201; |
| private static final int MAX_DEM_PORT = 5220; |
| private static final int DELTA_MUX_DEM_PORT = MIN_DEM_PORT - MIN_MUX_PORT; |
| |
| private static final String MUX_PORT_NAME = "Mux Input"; |
| private static final String DEMUX_PORT_NAME = "Demux Output"; |
| private static final String LINE_PORT_NAME = "Optical Line"; |
| |
| private final Logger log = getLogger(getClass()); |
| |
| @Override |
| public DeviceDescription discoverDeviceDetails() { |
| SparseAnnotations annotations = DefaultAnnotations.builder().build(); |
| |
| log.debug("Lumentum NETCONF - starting discoverDeviceDetails"); |
| |
| // Some defaults values |
| String vendor = "Lumentum"; |
| String hwVersion = "not loaded"; |
| String swVersion = "not loaded"; |
| String serialNumber = "not loaded"; |
| String chassisData = "ne=1;chassis=10"; |
| |
| ChassisId chassisId = null; |
| DeviceId deviceId = handler().data().deviceId(); |
| |
| NetconfSession session = getNetconfSession(); |
| if (session == null) { |
| log.error("Lumentum NETCONF - session not found for {}", deviceId); |
| return null; |
| } |
| |
| //Retrieve system information from ietf-system |
| StringBuilder systemRequestBuilder = new StringBuilder(); |
| systemRequestBuilder.append("<system-state xmlns=\"urn:ietf:params:xml:ns:yang:ietf-system\">"); |
| systemRequestBuilder.append("</system-state>"); |
| |
| try { |
| String reply = session.get(systemRequestBuilder.toString(), null); |
| log.debug("Lumentum NETCONF - session.get reply {}", reply); |
| |
| XMLConfiguration xconf = (XMLConfiguration) XmlConfigParser.loadXmlString(reply); |
| |
| vendor = xconf.getString("data.system-state.platform.machine", vendor); |
| swVersion = xconf.getString("data.system-state.platform.os-version", swVersion); |
| } catch (NetconfException e) { |
| log.error("Lumentum NETCONF error in session.get with filter <system-state>", e); |
| } |
| |
| //Retrieve system information |
| StringBuilder chassisRequestBuilder = new StringBuilder(); |
| chassisRequestBuilder.append("<chassis-list xmlns=\"http://www.lumentum.com/lumentum-ote-equipment\">"); |
| chassisRequestBuilder.append("</chassis-list>"); |
| |
| try { |
| String reply = session.get(chassisRequestBuilder.toString(), null); |
| log.debug("Lumentum NETCONF - session.get reply {}", reply); |
| |
| XMLConfiguration xconf = (XMLConfiguration) XmlConfigParser.loadXmlString(reply); |
| |
| hwVersion = xconf.getString("data.chassis-list.chassis.state.loteq:hardware-rev", hwVersion); |
| serialNumber = xconf.getString("data.chassis-list.chassis.state.loteq:serial-no", serialNumber); |
| chassisData = xconf.getString("data.chassis-list.chassis.dn", chassisData); |
| |
| String[] parts = chassisData.split("chassis="); |
| chassisId = new ChassisId(Long.valueOf(parts[1], 10)); |
| |
| } catch (NetconfException e) { |
| log.error("Lumentum NETCONF error in session.get", e); |
| } |
| |
| //Upon connection of a new devices all pre-configured connections are removed |
| //TODO consider a way to keep "external" FlowRules |
| rpcRemoveAllConnections("1"); |
| rpcRemoveAllConnections("2"); |
| |
| log.info("Lumentum ROADM20 - discovered details:"); |
| log.info("TYPE {}", Device.Type.ROADM); |
| log.info("VENDOR {}", vendor); |
| log.info("HWVERSION {}", hwVersion); |
| log.info("SWVERSION {}", swVersion); |
| log.info("SERIAL {}", serialNumber); |
| log.info("CHASSISID {}", chassisId); |
| |
| //Return the Device Description |
| return new DefaultDeviceDescription(deviceId.uri(), Device.Type.ROADM, |
| vendor, hwVersion, swVersion, serialNumber, chassisId, annotations); |
| } |
| |
| @Override |
| public List<PortDescription> discoverPortDetails() { |
| DeviceId deviceId = handler().data().deviceId(); |
| DeviceService deviceService = checkNotNull(handler().get(DeviceService.class)); |
| Device device = deviceService.getDevice(deviceId); |
| |
| //Get the configuration from the device |
| if (device == null) { |
| log.error("Lumentum NETCONF - device object not found for {}", deviceId); |
| return ImmutableList.of(); |
| } |
| |
| NetconfSession session = getNetconfSession(); |
| if (session == null) { |
| log.error("Lumentum NETCONF - session not found for {}", deviceId); |
| return ImmutableList.of(); |
| } |
| |
| StringBuilder requestBuilder = new StringBuilder(); |
| requestBuilder.append("<physical-ports xmlns=\"http://www.lumentum.com/lumentum-ote-port\" "); |
| requestBuilder.append("xmlns:lotep=\"http://www.lumentum.com/lumentum-ote-port\" "); |
| requestBuilder.append("xmlns:lotepopt=\"http://www.lumentum.com/lumentum-ote-port-optical\" "); |
| requestBuilder.append("xmlns:loteeth=\"http://www.lumentum.com/lumentum-ote-port-ethernet\">"); |
| requestBuilder.append("</physical-ports>"); |
| |
| String reply; |
| try { |
| reply = session.get(requestBuilder.toString(), null); |
| } catch (NetconfException e) { |
| log.error("Lumentum NETCONF - " + |
| "discoverPortDetails failed to retrieve port details {}", handler().data().deviceId(), e); |
| return ImmutableList.of(); |
| } |
| |
| List<PortDescription> descriptions = parseLumentumRoadmPorts(XmlConfigParser. |
| loadXml(new ByteArrayInputStream(reply.getBytes()))); |
| |
| return ImmutableList.copyOf(descriptions); |
| } |
| |
| /** |
| * Parses a configuration and returns a set of ports. |
| * |
| * @param cfg a hierarchical configuration |
| * @return a list of port descriptions |
| */ |
| protected List<PortDescription> parseLumentumRoadmPorts(HierarchicalConfiguration cfg) { |
| List<PortDescription> portDescriptions = Lists.newArrayList(); |
| List<HierarchicalConfiguration> ports = cfg.configurationsAt(PHYSICAL_PORT); |
| |
| ports.stream().forEach(pcfg -> { |
| |
| DefaultAnnotations.Builder annotations = DefaultAnnotations.builder(); |
| |
| //Load port number |
| PortNumber portNum = PortNumber.portNumber( |
| pcfg.getString(DN).substring(pcfg.getString(DN).lastIndexOf(DN_PORT) + 5)); |
| |
| //Load port state |
| String maintenanceState = pcfg.getString(MAINTENANCE_STATE); |
| boolean isEnabled = ((maintenanceState != null) && (maintenanceState).equals(IN_SERVICE)); |
| |
| //Load port type (FIBER/COPPER) |
| Port.Type type = null; |
| for (Object o : pcfg.getList(PORT_EXTENSION)) { |
| String s = (String) o; |
| if (s.equals(OPTICAL_INPUT) || s.equals(OPTICAL_OUTPUT)) { |
| type = Port.Type.FIBER; |
| |
| } else if (s.equals(PORT_ETHERNET) || s.equals(PORT_PLUGGABLE)) { |
| type = Port.Type.COPPER; |
| } |
| } |
| |
| //Load port speed of Ethernet interface, expressed in Mb/s |
| Long speed = 0L; //should be the speed of optical port |
| if (type != null) { |
| if (type.equals(Port.Type.COPPER)) { |
| String speedString = pcfg.getString(PORT_SPEED); |
| if (speedString != null) { |
| speed = Long.parseLong(speedString.substring(speedString.lastIndexOf("speed_") + 6, |
| speedString.lastIndexOf("Mb"))); |
| } else { |
| log.error("Lumentum NETCONF - Port speed of Ethernet port not correctly loaded"); |
| } |
| } |
| } else { |
| log.error("Port Type not correctly loaded"); |
| } |
| |
| /** |
| * Setting the reverse port value for the unidirectional ports. |
| * |
| * In this device each port includes an input fiber and an output fiber. |
| * The 20 input fibers are numbered from MIN_MUX_PORT = 4101 to MAX_MUX_PORT = 4120. |
| * The 20 output fibers are numbered from MIN_DEM_PORT = 5201 to MAX_DEM_PORT = 5220. |
| * |
| * Where port 520x is always the reverse of 410x. |
| */ |
| if ((portNum.toLong() >= MIN_MUX_PORT) && (portNum.toLong() <= MAX_MUX_PORT)) { |
| Long reversePortId = portNum.toLong() + DELTA_MUX_DEM_PORT; |
| annotations.set(OpticalPathIntent.REVERSE_PORT_ANNOTATION_KEY, reversePortId.toString()); |
| } |
| if ((portNum.toLong() >= MIN_DEM_PORT) && (portNum.toLong() <= MAX_DEM_PORT)) { |
| Long reversePortId = portNum.toLong() - DELTA_MUX_DEM_PORT; |
| annotations.set(OpticalPathIntent.REVERSE_PORT_ANNOTATION_KEY, reversePortId.toString()); |
| } |
| |
| //Load other information |
| pcfg.getKeys().forEachRemaining(k -> { |
| if (!k.contains(DN) && !k.contains(PORT_SPEED) && !k.contains(PORT_EXTENSION) |
| && !k.contains(MAINTENANCE_STATE)) { |
| String value = pcfg.getString(k); |
| if (!value.isEmpty()) { |
| k = StringUtils.replaceEach(k, new String[]{"loteeth:", "lotep:", |
| "lotepopt:", "config.", "=", ":", |
| "state."}, |
| new String[]{"", "", "", "", "", "", ""}); |
| |
| annotations.set(k, value); |
| |
| //To visualize port name in the ROADM app GUI |
| if (k.equals(PORT_NAME)) { |
| annotations.set(AnnotationKeys.PORT_NAME, value); |
| } |
| |
| } |
| } |
| }); |
| |
| log.debug("Lumentum NETCONF - retrieved port {},{},{},{},{}", |
| portNum, isEnabled, type, speed, annotations.build()); |
| |
| |
| if ((type == Port.Type.FIBER) && |
| ((annotations.build().value(AnnotationKeys.PORT_NAME)).contains(MUX_PORT_NAME) || |
| (annotations.build().value(AnnotationKeys.PORT_NAME)).contains(DEMUX_PORT_NAME) || |
| (annotations.build().value(AnnotationKeys.PORT_NAME)).contains(LINE_PORT_NAME))) { |
| |
| //These are the ports supporting OchSignals |
| portDescriptions.add(omsPortDescription(portNum, |
| isEnabled, |
| START_CENTER_FREQ_50, |
| END_CENTER_FREQ_50, |
| CHANNEL_SPACING_50.frequency(), |
| annotations.build())); |
| } else { |
| //These are COPPER ports, or FIBER ports not supporting OchSignals |
| DefaultPortDescription.Builder portDescriptionBuilder = DefaultPortDescription.builder(); |
| portDescriptionBuilder.withPortNumber(portNum) |
| .isEnabled(isEnabled) |
| .type(type) |
| .portSpeed(speed) |
| .annotations(annotations.build()); |
| |
| portDescriptions.add(portDescriptionBuilder.build()); |
| } |
| }); |
| |
| return portDescriptions; |
| } |
| |
| //Following Lumentum documentation rpc operation to delete all connections |
| private boolean rpcRemoveAllConnections(String module) { |
| StringBuilder stringBuilder = new StringBuilder(); |
| stringBuilder.append("<rpc xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">" + "\n"); |
| stringBuilder.append( |
| "<remove-all-connections xmlns=\"http://www.lumentum.com/lumentum-ote-connection\">" + "\n"); |
| stringBuilder.append("<dn>ne=1;chassis=1;card=1;module=" + module + "</dn>" + "\n"); |
| stringBuilder.append("</remove-all-connections>" + "\n"); |
| stringBuilder.append("</rpc>" + "\n"); |
| |
| return editCrossConnect(stringBuilder.toString()); |
| } |
| |
| private boolean editCrossConnect(String xcString) { |
| NetconfSession session = getNetconfSession(); |
| if (session == null) { |
| log.error("Lumentum NETCONF - session not found for {}", handler().data().deviceId()); |
| return false; |
| } |
| |
| try { |
| return session.editConfig(xcString); |
| } catch (NetconfException e) { |
| log.error("Failed to edit the CrossConnect edid-cfg for device {}", |
| handler().data().deviceId(), e); |
| log.debug("Failed configuration {}", xcString); |
| return false; |
| } |
| } |
| |
| /** |
| * Helper method to get the Netconf session. |
| */ |
| private NetconfSession getNetconfSession() { |
| NetconfController controller = |
| checkNotNull(handler().get(NetconfController.class)); |
| return controller.getNetconfDevice(did()).getSession(); |
| } |
| |
| /** |
| * Helper method to get the device id. |
| */ |
| private DeviceId did() { |
| return data().deviceId(); |
| } |
| } |