blob: 6b8fdf1073ee145f39b9ca03573e005d6e40f9bf [file] [log] [blame]
Alessio Giorgetti648b5382018-02-15 18:35:45 +01001/*
2 * Copyright 2017-present Open Networking Foundation
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package org.onosproject.drivers.lumentum;
18
19import com.google.common.collect.ImmutableList;
20import com.google.common.collect.Lists;
21
22import org.apache.commons.configuration.HierarchicalConfiguration;
23import org.apache.commons.configuration.XMLConfiguration;
24import org.apache.commons.lang3.StringUtils;
25
alessiod4a2b842019-04-30 18:43:17 +020026import org.onlab.packet.ChassisId;
27import org.onlab.util.Frequency;
Alessio Giorgetti648b5382018-02-15 18:35:45 +010028import org.onosproject.drivers.utilities.XmlConfigParser;
alessiod4a2b842019-04-30 18:43:17 +020029import org.onosproject.net.ChannelSpacing;
30import org.onosproject.net.SparseAnnotations;
31import org.onosproject.net.DefaultAnnotations;
32import org.onosproject.net.DeviceId;
33import org.onosproject.net.Device;
Alessio Giorgetti648b5382018-02-15 18:35:45 +010034import org.onosproject.net.Port;
35import org.onosproject.net.PortNumber;
Alessio Giorgetti648b5382018-02-15 18:35:45 +010036import org.onosproject.net.AnnotationKeys;
Alessio Giorgetti648b5382018-02-15 18:35:45 +010037import org.onosproject.net.device.DefaultDeviceDescription;
38import org.onosproject.net.device.DefaultPortDescription;
39import org.onosproject.net.device.DeviceDescription;
40import org.onosproject.net.device.DeviceDescriptionDiscovery;
41import org.onosproject.net.device.DeviceService;
42import org.onosproject.net.device.PortDescription;
43import org.onosproject.net.driver.AbstractHandlerBehaviour;
alessiod4a2b842019-04-30 18:43:17 +020044import org.onosproject.net.intent.OpticalPathIntent;
Alessio Giorgetti648b5382018-02-15 18:35:45 +010045import org.onosproject.netconf.NetconfController;
46import org.onosproject.netconf.NetconfException;
47import org.onosproject.netconf.NetconfSession;
Alessio Giorgetti648b5382018-02-15 18:35:45 +010048
49import org.slf4j.Logger;
50
51import java.io.ByteArrayInputStream;
52import java.util.List;
53
54import static com.google.common.base.Preconditions.checkNotNull;
alessiod4a2b842019-04-30 18:43:17 +020055import static org.onosproject.net.optical.device.OmsPortHelper.omsPortDescription;
Alessio Giorgetti648b5382018-02-15 18:35:45 +010056import static org.slf4j.LoggerFactory.getLogger;
57
58/**
59 * Device description behaviour for Lumentum ROADM-A Whitebox devices using NETCONF.
alessioa9bcacc2021-09-24 17:32:57 +020060 *
61 * Tested on device Lumentum:ROADM with Twin 1X20 WSS
62 * Software versions:
63 * - dcian_R3.1.2_057
64 * - dcian_R2.1.4_136
Alessio Giorgetti648b5382018-02-15 18:35:45 +010065 */
66public class LumentumNetconfRoadmDiscovery
67 extends AbstractHandlerBehaviour implements DeviceDescriptionDiscovery {
68
69 private static final String PHYSICAL_PORT = "data.physical-ports.physical-port";
70
71 private static final String DN = "dn";
72 private static final String DN_PORT = "port=";
73 private static final String PORT_EXTENSION = "port-extension";
74 protected static final String OPTICAL_INPUT = "port-optical-input";
75 protected static final String OPTICAL_OUTPUT = "port-optical-output";
76 private static final String PORT_PLUGGABLE = "port-pluggable";
77 private static final String PORT_ETHERNET = "port-ethernet";
78
79 private static final String MAINTENANCE_STATE = "config.maintenance-state";
80 private static final String PORT_SPEED = "config.loteeth:port-speed";
81 private static final String IN_SERVICE = "in-service";
82 private static final String PORT_NAME = "entity-description";
83
alessiod4a2b842019-04-30 18:43:17 +020084 public static final ChannelSpacing CHANNEL_SPACING_50 = ChannelSpacing.CHL_50GHZ;
85 public static final Frequency START_CENTER_FREQ_50 = Frequency.ofGHz(191_350);
86 public static final Frequency END_CENTER_FREQ_50 = Frequency.ofGHz(196_100);
87
88 private static final int MIN_MUX_PORT = 4101;
89 private static final int MAX_MUX_PORT = 4120;
90 private static final int MIN_DEM_PORT = 5201;
91 private static final int MAX_DEM_PORT = 5220;
92 private static final int DELTA_MUX_DEM_PORT = MIN_DEM_PORT - MIN_MUX_PORT;
93
alessio1bf2a632019-06-04 15:47:39 +020094 private static final String MUX_PORT_NAME = "Mux Input";
95 private static final String DEMUX_PORT_NAME = "Demux Output";
96 private static final String LINE_PORT_NAME = "Optical Line";
97
Alessio Giorgetti648b5382018-02-15 18:35:45 +010098 private final Logger log = getLogger(getClass());
99
100 @Override
101 public DeviceDescription discoverDeviceDetails() {
alessiod4a2b842019-04-30 18:43:17 +0200102 SparseAnnotations annotations = DefaultAnnotations.builder().build();
103
alessio1bf2a632019-06-04 15:47:39 +0200104 log.debug("Lumentum NETCONF - starting discoverDeviceDetails");
Alessio Giorgetti648b5382018-02-15 18:35:45 +0100105
106 // Some defaults values
107 String vendor = "Lumentum";
108 String hwVersion = "not loaded";
109 String swVersion = "not loaded";
110 String serialNumber = "not loaded";
alessiod4a2b842019-04-30 18:43:17 +0200111 String chassisData = "ne=1;chassis=10";
Alessio Giorgetti648b5382018-02-15 18:35:45 +0100112
alessiod4a2b842019-04-30 18:43:17 +0200113 ChassisId chassisId = null;
Alessio Giorgetti648b5382018-02-15 18:35:45 +0100114 DeviceId deviceId = handler().data().deviceId();
Alessio Giorgetti648b5382018-02-15 18:35:45 +0100115
116 NetconfSession session = getNetconfSession();
Alessio Giorgetti648b5382018-02-15 18:35:45 +0100117 if (session == null) {
118 log.error("Lumentum NETCONF - session not found for {}", deviceId);
119 return null;
120 }
121
122 //Retrieve system information from ietf-system
123 StringBuilder systemRequestBuilder = new StringBuilder();
124 systemRequestBuilder.append("<system-state xmlns=\"urn:ietf:params:xml:ns:yang:ietf-system\">");
125 systemRequestBuilder.append("</system-state>");
126
127 try {
128 String reply = session.get(systemRequestBuilder.toString(), null);
alessio1bf2a632019-06-04 15:47:39 +0200129 log.debug("Lumentum NETCONF - session.get reply {}", reply);
Alessio Giorgetti648b5382018-02-15 18:35:45 +0100130
131 XMLConfiguration xconf = (XMLConfiguration) XmlConfigParser.loadXmlString(reply);
132
133 vendor = xconf.getString("data.system-state.platform.machine", vendor);
134 swVersion = xconf.getString("data.system-state.platform.os-version", swVersion);
135 } catch (NetconfException e) {
136 log.error("Lumentum NETCONF error in session.get with filter <system-state>", e);
137 }
138
139 //Retrieve system information
140 StringBuilder chassisRequestBuilder = new StringBuilder();
141 chassisRequestBuilder.append("<chassis-list xmlns=\"http://www.lumentum.com/lumentum-ote-equipment\">");
142 chassisRequestBuilder.append("</chassis-list>");
143
144 try {
145 String reply = session.get(chassisRequestBuilder.toString(), null);
alessio1bf2a632019-06-04 15:47:39 +0200146 log.debug("Lumentum NETCONF - session.get reply {}", reply);
Alessio Giorgetti648b5382018-02-15 18:35:45 +0100147
148 XMLConfiguration xconf = (XMLConfiguration) XmlConfigParser.loadXmlString(reply);
149
150 hwVersion = xconf.getString("data.chassis-list.chassis.state.loteq:hardware-rev", hwVersion);
151 serialNumber = xconf.getString("data.chassis-list.chassis.state.loteq:serial-no", serialNumber);
alessiod4a2b842019-04-30 18:43:17 +0200152 chassisData = xconf.getString("data.chassis-list.chassis.dn", chassisData);
153
154 String[] parts = chassisData.split("chassis=");
155 chassisId = new ChassisId(Long.valueOf(parts[1], 10));
Alessio Giorgetti648b5382018-02-15 18:35:45 +0100156
157 } catch (NetconfException e) {
158 log.error("Lumentum NETCONF error in session.get", e);
159 }
160
161 //Upon connection of a new devices all pre-configured connections are removed
alessio1bf2a632019-06-04 15:47:39 +0200162 //TODO consider a way to keep "external" FlowRules
Alessio Giorgetti648b5382018-02-15 18:35:45 +0100163 rpcRemoveAllConnections("1");
164 rpcRemoveAllConnections("2");
165
alessio1bf2a632019-06-04 15:47:39 +0200166 log.info("Lumentum ROADM20 - discovered details:");
Alessio Giorgetti648b5382018-02-15 18:35:45 +0100167 log.info("TYPE {}", Device.Type.ROADM);
168 log.info("VENDOR {}", vendor);
169 log.info("HWVERSION {}", hwVersion);
170 log.info("SWVERSION {}", swVersion);
171 log.info("SERIAL {}", serialNumber);
172 log.info("CHASSISID {}", chassisId);
173
174 //Return the Device Description
175 return new DefaultDeviceDescription(deviceId.uri(), Device.Type.ROADM,
alessiod4a2b842019-04-30 18:43:17 +0200176 vendor, hwVersion, swVersion, serialNumber, chassisId, annotations);
Alessio Giorgetti648b5382018-02-15 18:35:45 +0100177 }
178
179 @Override
180 public List<PortDescription> discoverPortDetails() {
Alessio Giorgetti648b5382018-02-15 18:35:45 +0100181 DeviceId deviceId = handler().data().deviceId();
182 DeviceService deviceService = checkNotNull(handler().get(DeviceService.class));
183 Device device = deviceService.getDevice(deviceId);
184
185 //Get the configuration from the device
186 if (device == null) {
187 log.error("Lumentum NETCONF - device object not found for {}", deviceId);
188 return ImmutableList.of();
189 }
190
191 NetconfSession session = getNetconfSession();
Alessio Giorgetti648b5382018-02-15 18:35:45 +0100192 if (session == null) {
193 log.error("Lumentum NETCONF - session not found for {}", deviceId);
194 return ImmutableList.of();
195 }
196
197 StringBuilder requestBuilder = new StringBuilder();
198 requestBuilder.append("<physical-ports xmlns=\"http://www.lumentum.com/lumentum-ote-port\" ");
199 requestBuilder.append("xmlns:lotep=\"http://www.lumentum.com/lumentum-ote-port\" ");
200 requestBuilder.append("xmlns:lotepopt=\"http://www.lumentum.com/lumentum-ote-port-optical\" ");
201 requestBuilder.append("xmlns:loteeth=\"http://www.lumentum.com/lumentum-ote-port-ethernet\">");
202 requestBuilder.append("</physical-ports>");
203
alessio1bf2a632019-06-04 15:47:39 +0200204 String reply;
Alessio Giorgetti648b5382018-02-15 18:35:45 +0100205 try {
206 reply = session.get(requestBuilder.toString(), null);
207 } catch (NetconfException e) {
208 log.error("Lumentum NETCONF - " +
209 "discoverPortDetails failed to retrieve port details {}", handler().data().deviceId(), e);
210 return ImmutableList.of();
211 }
212
213 List<PortDescription> descriptions = parseLumentumRoadmPorts(XmlConfigParser.
214 loadXml(new ByteArrayInputStream(reply.getBytes())));
215
216 return ImmutableList.copyOf(descriptions);
217 }
218
219 /**
220 * Parses a configuration and returns a set of ports.
221 *
222 * @param cfg a hierarchical configuration
223 * @return a list of port descriptions
224 */
225 protected List<PortDescription> parseLumentumRoadmPorts(HierarchicalConfiguration cfg) {
226 List<PortDescription> portDescriptions = Lists.newArrayList();
227 List<HierarchicalConfiguration> ports = cfg.configurationsAt(PHYSICAL_PORT);
228
229 ports.stream().forEach(pcfg -> {
230
231 DefaultAnnotations.Builder annotations = DefaultAnnotations.builder();
232
233 //Load port number
234 PortNumber portNum = PortNumber.portNumber(
235 pcfg.getString(DN).substring(pcfg.getString(DN).lastIndexOf(DN_PORT) + 5));
236
237 //Load port state
238 String maintenanceState = pcfg.getString(MAINTENANCE_STATE);
239 boolean isEnabled = ((maintenanceState != null) && (maintenanceState).equals(IN_SERVICE));
240
241 //Load port type (FIBER/COPPER)
242 Port.Type type = null;
243 for (Object o : pcfg.getList(PORT_EXTENSION)) {
244 String s = (String) o;
alessioa9bcacc2021-09-24 17:32:57 +0200245 if (s.contains(OPTICAL_INPUT) || s.contains(OPTICAL_OUTPUT)) {
Alessio Giorgetti648b5382018-02-15 18:35:45 +0100246 type = Port.Type.FIBER;
alessioa9bcacc2021-09-24 17:32:57 +0200247 log.debug("Loaded OPTICAL port {}", portNum);
Alessio Giorgetti648b5382018-02-15 18:35:45 +0100248
alessioa9bcacc2021-09-24 17:32:57 +0200249 } else if (s.contains(PORT_ETHERNET) || s.contains(PORT_PLUGGABLE)) {
Alessio Giorgetti648b5382018-02-15 18:35:45 +0100250 type = Port.Type.COPPER;
alessioa9bcacc2021-09-24 17:32:57 +0200251 log.debug("Loaded PACKET port {}", portNum);
252
Alessio Giorgetti648b5382018-02-15 18:35:45 +0100253 }
254 }
255
256 //Load port speed of Ethernet interface, expressed in Mb/s
257 Long speed = 0L; //should be the speed of optical port
258 if (type != null) {
259 if (type.equals(Port.Type.COPPER)) {
260 String speedString = pcfg.getString(PORT_SPEED);
alessioa9bcacc2021-09-24 17:32:57 +0200261 log.debug("--- port {} loaded speed {}", portNum, speedString);
Alessio Giorgetti648b5382018-02-15 18:35:45 +0100262 if (speedString != null) {
alessioa9bcacc2021-09-24 17:32:57 +0200263 if (speedString.contains("Mb")) {
264 speed = Long.parseLong(speedString.substring(
265 speedString.lastIndexOf("speed_") + 6,
266 speedString.lastIndexOf("Mb")));
267 }
268 if (speedString.contains("Gb")) {
269 speed = 1000 * Long.parseLong(speedString.substring(
270 speedString.lastIndexOf("speed_") + 6,
271 speedString.lastIndexOf("Gb")));
272 }
Alessio Giorgetti648b5382018-02-15 18:35:45 +0100273 } else {
274 log.error("Lumentum NETCONF - Port speed of Ethernet port not correctly loaded");
275 }
276 }
277 } else {
278 log.error("Port Type not correctly loaded");
279 }
280
alessiod4a2b842019-04-30 18:43:17 +0200281 /**
282 * Setting the reverse port value for the unidirectional ports.
283 *
284 * In this device each port includes an input fiber and an output fiber.
285 * The 20 input fibers are numbered from MIN_MUX_PORT = 4101 to MAX_MUX_PORT = 4120.
286 * The 20 output fibers are numbered from MIN_DEM_PORT = 5201 to MAX_DEM_PORT = 5220.
287 *
288 * Where port 520x is always the reverse of 410x.
289 */
290 if ((portNum.toLong() >= MIN_MUX_PORT) && (portNum.toLong() <= MAX_MUX_PORT)) {
alessio1bf2a632019-06-04 15:47:39 +0200291 Long reversePortId = portNum.toLong() + DELTA_MUX_DEM_PORT;
alessiod4a2b842019-04-30 18:43:17 +0200292 annotations.set(OpticalPathIntent.REVERSE_PORT_ANNOTATION_KEY, reversePortId.toString());
293 }
294 if ((portNum.toLong() >= MIN_DEM_PORT) && (portNum.toLong() <= MAX_DEM_PORT)) {
alessio1bf2a632019-06-04 15:47:39 +0200295 Long reversePortId = portNum.toLong() - DELTA_MUX_DEM_PORT;
alessiod4a2b842019-04-30 18:43:17 +0200296 annotations.set(OpticalPathIntent.REVERSE_PORT_ANNOTATION_KEY, reversePortId.toString());
297 }
298
Alessio Giorgetti648b5382018-02-15 18:35:45 +0100299 //Load other information
300 pcfg.getKeys().forEachRemaining(k -> {
301 if (!k.contains(DN) && !k.contains(PORT_SPEED) && !k.contains(PORT_EXTENSION)
302 && !k.contains(MAINTENANCE_STATE)) {
303 String value = pcfg.getString(k);
304 if (!value.isEmpty()) {
305 k = StringUtils.replaceEach(k, new String[]{"loteeth:", "lotep:",
306 "lotepopt:", "config.", "=", ":",
307 "state."},
308 new String[]{"", "", "", "", "", "", ""});
309
310 annotations.set(k, value);
311
312 //To visualize port name in the ROADM app GUI
313 if (k.equals(PORT_NAME)) {
314 annotations.set(AnnotationKeys.PORT_NAME, value);
315 }
316
317 }
318 }
319 });
320
321 log.debug("Lumentum NETCONF - retrieved port {},{},{},{},{}",
322 portNum, isEnabled, type, speed, annotations.build());
323
alessio1bf2a632019-06-04 15:47:39 +0200324
325 if ((type == Port.Type.FIBER) &&
326 ((annotations.build().value(AnnotationKeys.PORT_NAME)).contains(MUX_PORT_NAME) ||
327 (annotations.build().value(AnnotationKeys.PORT_NAME)).contains(DEMUX_PORT_NAME) ||
328 (annotations.build().value(AnnotationKeys.PORT_NAME)).contains(LINE_PORT_NAME))) {
329
330 //These are the ports supporting OchSignals
alessiod4a2b842019-04-30 18:43:17 +0200331 portDescriptions.add(omsPortDescription(portNum,
332 isEnabled,
333 START_CENTER_FREQ_50,
334 END_CENTER_FREQ_50,
335 CHANNEL_SPACING_50.frequency(),
336 annotations.build()));
337 } else {
alessio1bf2a632019-06-04 15:47:39 +0200338 //These are COPPER ports, or FIBER ports not supporting OchSignals
alessiod4a2b842019-04-30 18:43:17 +0200339 DefaultPortDescription.Builder portDescriptionBuilder = DefaultPortDescription.builder();
340 portDescriptionBuilder.withPortNumber(portNum)
341 .isEnabled(isEnabled)
342 .type(type)
343 .portSpeed(speed)
344 .annotations(annotations.build());
Ray Milkeyf031e302018-09-07 10:07:24 -0700345
alessiod4a2b842019-04-30 18:43:17 +0200346 portDescriptions.add(portDescriptionBuilder.build());
347 }
Alessio Giorgetti648b5382018-02-15 18:35:45 +0100348 });
349
350 return portDescriptions;
351 }
352
353 //Following Lumentum documentation rpc operation to delete all connections
354 private boolean rpcRemoveAllConnections(String module) {
355 StringBuilder stringBuilder = new StringBuilder();
356 stringBuilder.append("<rpc xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">" + "\n");
357 stringBuilder.append(
358 "<remove-all-connections xmlns=\"http://www.lumentum.com/lumentum-ote-connection\">" + "\n");
359 stringBuilder.append("<dn>ne=1;chassis=1;card=1;module=" + module + "</dn>" + "\n");
360 stringBuilder.append("</remove-all-connections>" + "\n");
361 stringBuilder.append("</rpc>" + "\n");
362
363 return editCrossConnect(stringBuilder.toString());
364 }
365
366 private boolean editCrossConnect(String xcString) {
367 NetconfSession session = getNetconfSession();
Alessio Giorgetti648b5382018-02-15 18:35:45 +0100368 if (session == null) {
369 log.error("Lumentum NETCONF - session not found for {}", handler().data().deviceId());
370 return false;
371 }
372
373 try {
374 return session.editConfig(xcString);
375 } catch (NetconfException e) {
376 log.error("Failed to edit the CrossConnect edid-cfg for device {}",
377 handler().data().deviceId(), e);
378 log.debug("Failed configuration {}", xcString);
379 return false;
380 }
381 }
382
alessio1bf2a632019-06-04 15:47:39 +0200383 /**
384 * Helper method to get the Netconf session.
385 */
Alessio Giorgetti648b5382018-02-15 18:35:45 +0100386 private NetconfSession getNetconfSession() {
alessio1bf2a632019-06-04 15:47:39 +0200387 NetconfController controller =
388 checkNotNull(handler().get(NetconfController.class));
389 return controller.getNetconfDevice(did()).getSession();
390 }
Alessio Giorgetti648b5382018-02-15 18:35:45 +0100391
alessio1bf2a632019-06-04 15:47:39 +0200392 /**
393 * Helper method to get the device id.
394 */
395 private DeviceId did() {
396 return data().deviceId();
Alessio Giorgetti648b5382018-02-15 18:35:45 +0100397 }
398}