blob: 34655d47153f045ea044c3b98968eb5e6e1ac7d1 [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;
alessioa280f032024-01-30 02:52:01 +010037import org.onosproject.net.OduSignalType;
38import org.onosproject.net.OchSignal;
Alessio Giorgetti648b5382018-02-15 18:35:45 +010039import org.onosproject.net.device.DefaultDeviceDescription;
40import org.onosproject.net.device.DefaultPortDescription;
41import org.onosproject.net.device.DeviceDescription;
42import org.onosproject.net.device.DeviceDescriptionDiscovery;
43import org.onosproject.net.device.DeviceService;
44import org.onosproject.net.device.PortDescription;
45import org.onosproject.net.driver.AbstractHandlerBehaviour;
alessiod4a2b842019-04-30 18:43:17 +020046import org.onosproject.net.intent.OpticalPathIntent;
Alessio Giorgetti648b5382018-02-15 18:35:45 +010047import org.onosproject.netconf.NetconfController;
48import org.onosproject.netconf.NetconfException;
49import org.onosproject.netconf.NetconfSession;
Alessio Giorgetti648b5382018-02-15 18:35:45 +010050
51import org.slf4j.Logger;
52
53import java.io.ByteArrayInputStream;
54import java.util.List;
55
56import static com.google.common.base.Preconditions.checkNotNull;
alessiod4a2b842019-04-30 18:43:17 +020057import static org.onosproject.net.optical.device.OmsPortHelper.omsPortDescription;
alessioa280f032024-01-30 02:52:01 +010058import static org.onosproject.net.optical.device.OchPortHelper.ochPortDescription;
Alessio Giorgetti648b5382018-02-15 18:35:45 +010059import static org.slf4j.LoggerFactory.getLogger;
60
61/**
62 * Device description behaviour for Lumentum ROADM-A Whitebox devices using NETCONF.
alessioa9bcacc2021-09-24 17:32:57 +020063 *
64 * Tested on device Lumentum:ROADM with Twin 1X20 WSS
65 * Software versions:
66 * - dcian_R3.1.2_057
67 * - dcian_R2.1.4_136
Alessio Giorgetti648b5382018-02-15 18:35:45 +010068 */
69public class LumentumNetconfRoadmDiscovery
70 extends AbstractHandlerBehaviour implements DeviceDescriptionDiscovery {
71
72 private static final String PHYSICAL_PORT = "data.physical-ports.physical-port";
73
74 private static final String DN = "dn";
75 private static final String DN_PORT = "port=";
76 private static final String PORT_EXTENSION = "port-extension";
77 protected static final String OPTICAL_INPUT = "port-optical-input";
78 protected static final String OPTICAL_OUTPUT = "port-optical-output";
79 private static final String PORT_PLUGGABLE = "port-pluggable";
80 private static final String PORT_ETHERNET = "port-ethernet";
81
82 private static final String MAINTENANCE_STATE = "config.maintenance-state";
83 private static final String PORT_SPEED = "config.loteeth:port-speed";
84 private static final String IN_SERVICE = "in-service";
85 private static final String PORT_NAME = "entity-description";
86
alessiod4a2b842019-04-30 18:43:17 +020087 public static final ChannelSpacing CHANNEL_SPACING_50 = ChannelSpacing.CHL_50GHZ;
88 public static final Frequency START_CENTER_FREQ_50 = Frequency.ofGHz(191_350);
89 public static final Frequency END_CENTER_FREQ_50 = Frequency.ofGHz(196_100);
90
91 private static final int MIN_MUX_PORT = 4101;
92 private static final int MAX_MUX_PORT = 4120;
93 private static final int MIN_DEM_PORT = 5201;
94 private static final int MAX_DEM_PORT = 5220;
95 private static final int DELTA_MUX_DEM_PORT = MIN_DEM_PORT - MIN_MUX_PORT;
96
alessio1bf2a632019-06-04 15:47:39 +020097 private static final String MUX_PORT_NAME = "Mux Input";
98 private static final String DEMUX_PORT_NAME = "Demux Output";
99 private static final String LINE_PORT_NAME = "Optical Line";
100
Alessio Giorgetti648b5382018-02-15 18:35:45 +0100101 private final Logger log = getLogger(getClass());
102
103 @Override
104 public DeviceDescription discoverDeviceDetails() {
alessiod4a2b842019-04-30 18:43:17 +0200105 SparseAnnotations annotations = DefaultAnnotations.builder().build();
106
alessio1bf2a632019-06-04 15:47:39 +0200107 log.debug("Lumentum NETCONF - starting discoverDeviceDetails");
Alessio Giorgetti648b5382018-02-15 18:35:45 +0100108
109 // Some defaults values
110 String vendor = "Lumentum";
111 String hwVersion = "not loaded";
112 String swVersion = "not loaded";
113 String serialNumber = "not loaded";
alessiod4a2b842019-04-30 18:43:17 +0200114 String chassisData = "ne=1;chassis=10";
Alessio Giorgetti648b5382018-02-15 18:35:45 +0100115
alessiod4a2b842019-04-30 18:43:17 +0200116 ChassisId chassisId = null;
Alessio Giorgetti648b5382018-02-15 18:35:45 +0100117 DeviceId deviceId = handler().data().deviceId();
Alessio Giorgetti648b5382018-02-15 18:35:45 +0100118
119 NetconfSession session = getNetconfSession();
Alessio Giorgetti648b5382018-02-15 18:35:45 +0100120 if (session == null) {
121 log.error("Lumentum NETCONF - session not found for {}", deviceId);
122 return null;
123 }
124
125 //Retrieve system information from ietf-system
126 StringBuilder systemRequestBuilder = new StringBuilder();
127 systemRequestBuilder.append("<system-state xmlns=\"urn:ietf:params:xml:ns:yang:ietf-system\">");
128 systemRequestBuilder.append("</system-state>");
129
130 try {
131 String reply = session.get(systemRequestBuilder.toString(), null);
alessio1bf2a632019-06-04 15:47:39 +0200132 log.debug("Lumentum NETCONF - session.get reply {}", reply);
Alessio Giorgetti648b5382018-02-15 18:35:45 +0100133
134 XMLConfiguration xconf = (XMLConfiguration) XmlConfigParser.loadXmlString(reply);
135
136 vendor = xconf.getString("data.system-state.platform.machine", vendor);
137 swVersion = xconf.getString("data.system-state.platform.os-version", swVersion);
138 } catch (NetconfException e) {
139 log.error("Lumentum NETCONF error in session.get with filter <system-state>", e);
140 }
141
142 //Retrieve system information
143 StringBuilder chassisRequestBuilder = new StringBuilder();
144 chassisRequestBuilder.append("<chassis-list xmlns=\"http://www.lumentum.com/lumentum-ote-equipment\">");
145 chassisRequestBuilder.append("</chassis-list>");
146
147 try {
148 String reply = session.get(chassisRequestBuilder.toString(), null);
alessio1bf2a632019-06-04 15:47:39 +0200149 log.debug("Lumentum NETCONF - session.get reply {}", reply);
Alessio Giorgetti648b5382018-02-15 18:35:45 +0100150
151 XMLConfiguration xconf = (XMLConfiguration) XmlConfigParser.loadXmlString(reply);
152
153 hwVersion = xconf.getString("data.chassis-list.chassis.state.loteq:hardware-rev", hwVersion);
154 serialNumber = xconf.getString("data.chassis-list.chassis.state.loteq:serial-no", serialNumber);
alessiod4a2b842019-04-30 18:43:17 +0200155 chassisData = xconf.getString("data.chassis-list.chassis.dn", chassisData);
156
157 String[] parts = chassisData.split("chassis=");
158 chassisId = new ChassisId(Long.valueOf(parts[1], 10));
Alessio Giorgetti648b5382018-02-15 18:35:45 +0100159
160 } catch (NetconfException e) {
161 log.error("Lumentum NETCONF error in session.get", e);
162 }
163
164 //Upon connection of a new devices all pre-configured connections are removed
alessio1bf2a632019-06-04 15:47:39 +0200165 //TODO consider a way to keep "external" FlowRules
Alessio Giorgetti648b5382018-02-15 18:35:45 +0100166 rpcRemoveAllConnections("1");
167 rpcRemoveAllConnections("2");
168
alessio1bf2a632019-06-04 15:47:39 +0200169 log.info("Lumentum ROADM20 - discovered details:");
Alessio Giorgetti648b5382018-02-15 18:35:45 +0100170 log.info("TYPE {}", Device.Type.ROADM);
171 log.info("VENDOR {}", vendor);
172 log.info("HWVERSION {}", hwVersion);
173 log.info("SWVERSION {}", swVersion);
174 log.info("SERIAL {}", serialNumber);
175 log.info("CHASSISID {}", chassisId);
176
177 //Return the Device Description
178 return new DefaultDeviceDescription(deviceId.uri(), Device.Type.ROADM,
alessiod4a2b842019-04-30 18:43:17 +0200179 vendor, hwVersion, swVersion, serialNumber, chassisId, annotations);
Alessio Giorgetti648b5382018-02-15 18:35:45 +0100180 }
181
182 @Override
183 public List<PortDescription> discoverPortDetails() {
Alessio Giorgetti648b5382018-02-15 18:35:45 +0100184 DeviceId deviceId = handler().data().deviceId();
185 DeviceService deviceService = checkNotNull(handler().get(DeviceService.class));
186 Device device = deviceService.getDevice(deviceId);
187
188 //Get the configuration from the device
189 if (device == null) {
190 log.error("Lumentum NETCONF - device object not found for {}", deviceId);
191 return ImmutableList.of();
192 }
193
194 NetconfSession session = getNetconfSession();
Alessio Giorgetti648b5382018-02-15 18:35:45 +0100195 if (session == null) {
196 log.error("Lumentum NETCONF - session not found for {}", deviceId);
197 return ImmutableList.of();
198 }
199
200 StringBuilder requestBuilder = new StringBuilder();
201 requestBuilder.append("<physical-ports xmlns=\"http://www.lumentum.com/lumentum-ote-port\" ");
202 requestBuilder.append("xmlns:lotep=\"http://www.lumentum.com/lumentum-ote-port\" ");
203 requestBuilder.append("xmlns:lotepopt=\"http://www.lumentum.com/lumentum-ote-port-optical\" ");
204 requestBuilder.append("xmlns:loteeth=\"http://www.lumentum.com/lumentum-ote-port-ethernet\">");
205 requestBuilder.append("</physical-ports>");
206
alessio1bf2a632019-06-04 15:47:39 +0200207 String reply;
Alessio Giorgetti648b5382018-02-15 18:35:45 +0100208 try {
209 reply = session.get(requestBuilder.toString(), null);
210 } catch (NetconfException e) {
211 log.error("Lumentum NETCONF - " +
212 "discoverPortDetails failed to retrieve port details {}", handler().data().deviceId(), e);
213 return ImmutableList.of();
214 }
215
216 List<PortDescription> descriptions = parseLumentumRoadmPorts(XmlConfigParser.
217 loadXml(new ByteArrayInputStream(reply.getBytes())));
218
219 return ImmutableList.copyOf(descriptions);
220 }
221
222 /**
223 * Parses a configuration and returns a set of ports.
224 *
225 * @param cfg a hierarchical configuration
226 * @return a list of port descriptions
227 */
228 protected List<PortDescription> parseLumentumRoadmPorts(HierarchicalConfiguration cfg) {
229 List<PortDescription> portDescriptions = Lists.newArrayList();
230 List<HierarchicalConfiguration> ports = cfg.configurationsAt(PHYSICAL_PORT);
231
232 ports.stream().forEach(pcfg -> {
233
234 DefaultAnnotations.Builder annotations = DefaultAnnotations.builder();
235
236 //Load port number
237 PortNumber portNum = PortNumber.portNumber(
238 pcfg.getString(DN).substring(pcfg.getString(DN).lastIndexOf(DN_PORT) + 5));
239
240 //Load port state
241 String maintenanceState = pcfg.getString(MAINTENANCE_STATE);
242 boolean isEnabled = ((maintenanceState != null) && (maintenanceState).equals(IN_SERVICE));
243
244 //Load port type (FIBER/COPPER)
245 Port.Type type = null;
246 for (Object o : pcfg.getList(PORT_EXTENSION)) {
247 String s = (String) o;
alessioa9bcacc2021-09-24 17:32:57 +0200248 if (s.contains(OPTICAL_INPUT) || s.contains(OPTICAL_OUTPUT)) {
Alessio Giorgetti648b5382018-02-15 18:35:45 +0100249 type = Port.Type.FIBER;
alessioa9bcacc2021-09-24 17:32:57 +0200250 log.debug("Loaded OPTICAL port {}", portNum);
Alessio Giorgetti648b5382018-02-15 18:35:45 +0100251
alessioa9bcacc2021-09-24 17:32:57 +0200252 } else if (s.contains(PORT_ETHERNET) || s.contains(PORT_PLUGGABLE)) {
Alessio Giorgetti648b5382018-02-15 18:35:45 +0100253 type = Port.Type.COPPER;
alessioa9bcacc2021-09-24 17:32:57 +0200254 log.debug("Loaded PACKET port {}", portNum);
255
Alessio Giorgetti648b5382018-02-15 18:35:45 +0100256 }
257 }
258
259 //Load port speed of Ethernet interface, expressed in Mb/s
260 Long speed = 0L; //should be the speed of optical port
261 if (type != null) {
262 if (type.equals(Port.Type.COPPER)) {
263 String speedString = pcfg.getString(PORT_SPEED);
alessioa9bcacc2021-09-24 17:32:57 +0200264 log.debug("--- port {} loaded speed {}", portNum, speedString);
Alessio Giorgetti648b5382018-02-15 18:35:45 +0100265 if (speedString != null) {
alessioa9bcacc2021-09-24 17:32:57 +0200266 if (speedString.contains("Mb")) {
267 speed = Long.parseLong(speedString.substring(
268 speedString.lastIndexOf("speed_") + 6,
269 speedString.lastIndexOf("Mb")));
270 }
271 if (speedString.contains("Gb")) {
272 speed = 1000 * Long.parseLong(speedString.substring(
273 speedString.lastIndexOf("speed_") + 6,
274 speedString.lastIndexOf("Gb")));
275 }
Alessio Giorgetti648b5382018-02-15 18:35:45 +0100276 } else {
277 log.error("Lumentum NETCONF - Port speed of Ethernet port not correctly loaded");
278 }
279 }
280 } else {
281 log.error("Port Type not correctly loaded");
282 }
283
alessiod4a2b842019-04-30 18:43:17 +0200284 /**
285 * Setting the reverse port value for the unidirectional ports.
286 *
287 * In this device each port includes an input fiber and an output fiber.
288 * The 20 input fibers are numbered from MIN_MUX_PORT = 4101 to MAX_MUX_PORT = 4120.
289 * The 20 output fibers are numbered from MIN_DEM_PORT = 5201 to MAX_DEM_PORT = 5220.
290 *
291 * Where port 520x is always the reverse of 410x.
292 */
293 if ((portNum.toLong() >= MIN_MUX_PORT) && (portNum.toLong() <= MAX_MUX_PORT)) {
alessio1bf2a632019-06-04 15:47:39 +0200294 Long reversePortId = portNum.toLong() + DELTA_MUX_DEM_PORT;
alessiod4a2b842019-04-30 18:43:17 +0200295 annotations.set(OpticalPathIntent.REVERSE_PORT_ANNOTATION_KEY, reversePortId.toString());
296 }
297 if ((portNum.toLong() >= MIN_DEM_PORT) && (portNum.toLong() <= MAX_DEM_PORT)) {
alessio1bf2a632019-06-04 15:47:39 +0200298 Long reversePortId = portNum.toLong() - DELTA_MUX_DEM_PORT;
alessiod4a2b842019-04-30 18:43:17 +0200299 annotations.set(OpticalPathIntent.REVERSE_PORT_ANNOTATION_KEY, reversePortId.toString());
300 }
301
Alessio Giorgetti648b5382018-02-15 18:35:45 +0100302 //Load other information
303 pcfg.getKeys().forEachRemaining(k -> {
304 if (!k.contains(DN) && !k.contains(PORT_SPEED) && !k.contains(PORT_EXTENSION)
305 && !k.contains(MAINTENANCE_STATE)) {
306 String value = pcfg.getString(k);
307 if (!value.isEmpty()) {
308 k = StringUtils.replaceEach(k, new String[]{"loteeth:", "lotep:",
309 "lotepopt:", "config.", "=", ":",
310 "state."},
311 new String[]{"", "", "", "", "", "", ""});
312
313 annotations.set(k, value);
314
315 //To visualize port name in the ROADM app GUI
316 if (k.equals(PORT_NAME)) {
317 annotations.set(AnnotationKeys.PORT_NAME, value);
318 }
319
320 }
321 }
322 });
323
324 log.debug("Lumentum NETCONF - retrieved port {},{},{},{},{}",
325 portNum, isEnabled, type, speed, annotations.build());
326
alessio1bf2a632019-06-04 15:47:39 +0200327
328 if ((type == Port.Type.FIBER) &&
329 ((annotations.build().value(AnnotationKeys.PORT_NAME)).contains(MUX_PORT_NAME) ||
330 (annotations.build().value(AnnotationKeys.PORT_NAME)).contains(DEMUX_PORT_NAME) ||
331 (annotations.build().value(AnnotationKeys.PORT_NAME)).contains(LINE_PORT_NAME))) {
332
333 //These are the ports supporting OchSignals
alessioa280f032024-01-30 02:52:01 +0100334 if (portNum.toLong() != 3001) {
335 portDescriptions.add(ochPortDescription(portNum,
336 isEnabled,
337 OduSignalType.ODU4,
338 true,
339 OchSignal.newDwdmSlot(ChannelSpacing.CHL_50GHZ, 1),
340 annotations.build()));
341 } else {
342 portDescriptions.add(omsPortDescription(portNum,
343 isEnabled,
344 START_CENTER_FREQ_50,
345 END_CENTER_FREQ_50,
346 CHANNEL_SPACING_50.frequency(),
347 annotations.build()));
348 }
alessiod4a2b842019-04-30 18:43:17 +0200349 } else {
alessio1bf2a632019-06-04 15:47:39 +0200350 //These are COPPER ports, or FIBER ports not supporting OchSignals
alessiod4a2b842019-04-30 18:43:17 +0200351 DefaultPortDescription.Builder portDescriptionBuilder = DefaultPortDescription.builder();
352 portDescriptionBuilder.withPortNumber(portNum)
353 .isEnabled(isEnabled)
354 .type(type)
355 .portSpeed(speed)
356 .annotations(annotations.build());
Ray Milkeyf031e302018-09-07 10:07:24 -0700357
alessiod4a2b842019-04-30 18:43:17 +0200358 portDescriptions.add(portDescriptionBuilder.build());
359 }
Alessio Giorgetti648b5382018-02-15 18:35:45 +0100360 });
361
362 return portDescriptions;
363 }
364
365 //Following Lumentum documentation rpc operation to delete all connections
366 private boolean rpcRemoveAllConnections(String module) {
367 StringBuilder stringBuilder = new StringBuilder();
368 stringBuilder.append("<rpc xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">" + "\n");
369 stringBuilder.append(
370 "<remove-all-connections xmlns=\"http://www.lumentum.com/lumentum-ote-connection\">" + "\n");
371 stringBuilder.append("<dn>ne=1;chassis=1;card=1;module=" + module + "</dn>" + "\n");
372 stringBuilder.append("</remove-all-connections>" + "\n");
373 stringBuilder.append("</rpc>" + "\n");
374
375 return editCrossConnect(stringBuilder.toString());
376 }
377
378 private boolean editCrossConnect(String xcString) {
379 NetconfSession session = getNetconfSession();
Alessio Giorgetti648b5382018-02-15 18:35:45 +0100380 if (session == null) {
381 log.error("Lumentum NETCONF - session not found for {}", handler().data().deviceId());
382 return false;
383 }
384
385 try {
386 return session.editConfig(xcString);
387 } catch (NetconfException e) {
388 log.error("Failed to edit the CrossConnect edid-cfg for device {}",
389 handler().data().deviceId(), e);
390 log.debug("Failed configuration {}", xcString);
391 return false;
392 }
393 }
394
alessio1bf2a632019-06-04 15:47:39 +0200395 /**
396 * Helper method to get the Netconf session.
397 */
Alessio Giorgetti648b5382018-02-15 18:35:45 +0100398 private NetconfSession getNetconfSession() {
alessio1bf2a632019-06-04 15:47:39 +0200399 NetconfController controller =
400 checkNotNull(handler().get(NetconfController.class));
401 return controller.getNetconfDevice(did()).getSession();
402 }
Alessio Giorgetti648b5382018-02-15 18:35:45 +0100403
alessio1bf2a632019-06-04 15:47:39 +0200404 /**
405 * Helper method to get the device id.
406 */
407 private DeviceId did() {
408 return data().deviceId();
Alessio Giorgetti648b5382018-02-15 18:35:45 +0100409 }
410}