blob: df656c94b1397e325ce90e67b19c3589ffe502ac [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.
60 */
61public class LumentumNetconfRoadmDiscovery
62 extends AbstractHandlerBehaviour implements DeviceDescriptionDiscovery {
63
64 private static final String PHYSICAL_PORT = "data.physical-ports.physical-port";
65
66 private static final String DN = "dn";
67 private static final String DN_PORT = "port=";
68 private static final String PORT_EXTENSION = "port-extension";
69 protected static final String OPTICAL_INPUT = "port-optical-input";
70 protected static final String OPTICAL_OUTPUT = "port-optical-output";
71 private static final String PORT_PLUGGABLE = "port-pluggable";
72 private static final String PORT_ETHERNET = "port-ethernet";
73
74 private static final String MAINTENANCE_STATE = "config.maintenance-state";
75 private static final String PORT_SPEED = "config.loteeth:port-speed";
76 private static final String IN_SERVICE = "in-service";
77 private static final String PORT_NAME = "entity-description";
78
alessiod4a2b842019-04-30 18:43:17 +020079 public static final ChannelSpacing CHANNEL_SPACING_50 = ChannelSpacing.CHL_50GHZ;
80 public static final Frequency START_CENTER_FREQ_50 = Frequency.ofGHz(191_350);
81 public static final Frequency END_CENTER_FREQ_50 = Frequency.ofGHz(196_100);
82
83 private static final int MIN_MUX_PORT = 4101;
84 private static final int MAX_MUX_PORT = 4120;
85 private static final int MIN_DEM_PORT = 5201;
86 private static final int MAX_DEM_PORT = 5220;
87 private static final int DELTA_MUX_DEM_PORT = MIN_DEM_PORT - MIN_MUX_PORT;
88
alessio1bf2a632019-06-04 15:47:39 +020089 private static final String MUX_PORT_NAME = "Mux Input";
90 private static final String DEMUX_PORT_NAME = "Demux Output";
91 private static final String LINE_PORT_NAME = "Optical Line";
92
Alessio Giorgetti648b5382018-02-15 18:35:45 +010093 private final Logger log = getLogger(getClass());
94
95 @Override
96 public DeviceDescription discoverDeviceDetails() {
alessiod4a2b842019-04-30 18:43:17 +020097 SparseAnnotations annotations = DefaultAnnotations.builder().build();
98
alessio1bf2a632019-06-04 15:47:39 +020099 log.debug("Lumentum NETCONF - starting discoverDeviceDetails");
Alessio Giorgetti648b5382018-02-15 18:35:45 +0100100
101 // Some defaults values
102 String vendor = "Lumentum";
103 String hwVersion = "not loaded";
104 String swVersion = "not loaded";
105 String serialNumber = "not loaded";
alessiod4a2b842019-04-30 18:43:17 +0200106 String chassisData = "ne=1;chassis=10";
Alessio Giorgetti648b5382018-02-15 18:35:45 +0100107
alessiod4a2b842019-04-30 18:43:17 +0200108 ChassisId chassisId = null;
Alessio Giorgetti648b5382018-02-15 18:35:45 +0100109 DeviceId deviceId = handler().data().deviceId();
Alessio Giorgetti648b5382018-02-15 18:35:45 +0100110
111 NetconfSession session = getNetconfSession();
Alessio Giorgetti648b5382018-02-15 18:35:45 +0100112 if (session == null) {
113 log.error("Lumentum NETCONF - session not found for {}", deviceId);
114 return null;
115 }
116
117 //Retrieve system information from ietf-system
118 StringBuilder systemRequestBuilder = new StringBuilder();
119 systemRequestBuilder.append("<system-state xmlns=\"urn:ietf:params:xml:ns:yang:ietf-system\">");
120 systemRequestBuilder.append("</system-state>");
121
122 try {
123 String reply = session.get(systemRequestBuilder.toString(), null);
alessio1bf2a632019-06-04 15:47:39 +0200124 log.debug("Lumentum NETCONF - session.get reply {}", reply);
Alessio Giorgetti648b5382018-02-15 18:35:45 +0100125
126 XMLConfiguration xconf = (XMLConfiguration) XmlConfigParser.loadXmlString(reply);
127
128 vendor = xconf.getString("data.system-state.platform.machine", vendor);
129 swVersion = xconf.getString("data.system-state.platform.os-version", swVersion);
130 } catch (NetconfException e) {
131 log.error("Lumentum NETCONF error in session.get with filter <system-state>", e);
132 }
133
134 //Retrieve system information
135 StringBuilder chassisRequestBuilder = new StringBuilder();
136 chassisRequestBuilder.append("<chassis-list xmlns=\"http://www.lumentum.com/lumentum-ote-equipment\">");
137 chassisRequestBuilder.append("</chassis-list>");
138
139 try {
140 String reply = session.get(chassisRequestBuilder.toString(), null);
alessio1bf2a632019-06-04 15:47:39 +0200141 log.debug("Lumentum NETCONF - session.get reply {}", reply);
Alessio Giorgetti648b5382018-02-15 18:35:45 +0100142
143 XMLConfiguration xconf = (XMLConfiguration) XmlConfigParser.loadXmlString(reply);
144
145 hwVersion = xconf.getString("data.chassis-list.chassis.state.loteq:hardware-rev", hwVersion);
146 serialNumber = xconf.getString("data.chassis-list.chassis.state.loteq:serial-no", serialNumber);
alessiod4a2b842019-04-30 18:43:17 +0200147 chassisData = xconf.getString("data.chassis-list.chassis.dn", chassisData);
148
149 String[] parts = chassisData.split("chassis=");
150 chassisId = new ChassisId(Long.valueOf(parts[1], 10));
Alessio Giorgetti648b5382018-02-15 18:35:45 +0100151
152 } catch (NetconfException e) {
153 log.error("Lumentum NETCONF error in session.get", e);
154 }
155
156 //Upon connection of a new devices all pre-configured connections are removed
alessio1bf2a632019-06-04 15:47:39 +0200157 //TODO consider a way to keep "external" FlowRules
Alessio Giorgetti648b5382018-02-15 18:35:45 +0100158 rpcRemoveAllConnections("1");
159 rpcRemoveAllConnections("2");
160
alessio1bf2a632019-06-04 15:47:39 +0200161 log.info("Lumentum ROADM20 - discovered details:");
Alessio Giorgetti648b5382018-02-15 18:35:45 +0100162 log.info("TYPE {}", Device.Type.ROADM);
163 log.info("VENDOR {}", vendor);
164 log.info("HWVERSION {}", hwVersion);
165 log.info("SWVERSION {}", swVersion);
166 log.info("SERIAL {}", serialNumber);
167 log.info("CHASSISID {}", chassisId);
168
169 //Return the Device Description
170 return new DefaultDeviceDescription(deviceId.uri(), Device.Type.ROADM,
alessiod4a2b842019-04-30 18:43:17 +0200171 vendor, hwVersion, swVersion, serialNumber, chassisId, annotations);
Alessio Giorgetti648b5382018-02-15 18:35:45 +0100172 }
173
174 @Override
175 public List<PortDescription> discoverPortDetails() {
Alessio Giorgetti648b5382018-02-15 18:35:45 +0100176 DeviceId deviceId = handler().data().deviceId();
177 DeviceService deviceService = checkNotNull(handler().get(DeviceService.class));
178 Device device = deviceService.getDevice(deviceId);
179
180 //Get the configuration from the device
181 if (device == null) {
182 log.error("Lumentum NETCONF - device object not found for {}", deviceId);
183 return ImmutableList.of();
184 }
185
186 NetconfSession session = getNetconfSession();
Alessio Giorgetti648b5382018-02-15 18:35:45 +0100187 if (session == null) {
188 log.error("Lumentum NETCONF - session not found for {}", deviceId);
189 return ImmutableList.of();
190 }
191
192 StringBuilder requestBuilder = new StringBuilder();
193 requestBuilder.append("<physical-ports xmlns=\"http://www.lumentum.com/lumentum-ote-port\" ");
194 requestBuilder.append("xmlns:lotep=\"http://www.lumentum.com/lumentum-ote-port\" ");
195 requestBuilder.append("xmlns:lotepopt=\"http://www.lumentum.com/lumentum-ote-port-optical\" ");
196 requestBuilder.append("xmlns:loteeth=\"http://www.lumentum.com/lumentum-ote-port-ethernet\">");
197 requestBuilder.append("</physical-ports>");
198
alessio1bf2a632019-06-04 15:47:39 +0200199 String reply;
Alessio Giorgetti648b5382018-02-15 18:35:45 +0100200 try {
201 reply = session.get(requestBuilder.toString(), null);
202 } catch (NetconfException e) {
203 log.error("Lumentum NETCONF - " +
204 "discoverPortDetails failed to retrieve port details {}", handler().data().deviceId(), e);
205 return ImmutableList.of();
206 }
207
208 List<PortDescription> descriptions = parseLumentumRoadmPorts(XmlConfigParser.
209 loadXml(new ByteArrayInputStream(reply.getBytes())));
210
211 return ImmutableList.copyOf(descriptions);
212 }
213
214 /**
215 * Parses a configuration and returns a set of ports.
216 *
217 * @param cfg a hierarchical configuration
218 * @return a list of port descriptions
219 */
220 protected List<PortDescription> parseLumentumRoadmPorts(HierarchicalConfiguration cfg) {
221 List<PortDescription> portDescriptions = Lists.newArrayList();
222 List<HierarchicalConfiguration> ports = cfg.configurationsAt(PHYSICAL_PORT);
223
224 ports.stream().forEach(pcfg -> {
225
226 DefaultAnnotations.Builder annotations = DefaultAnnotations.builder();
227
228 //Load port number
229 PortNumber portNum = PortNumber.portNumber(
230 pcfg.getString(DN).substring(pcfg.getString(DN).lastIndexOf(DN_PORT) + 5));
231
232 //Load port state
233 String maintenanceState = pcfg.getString(MAINTENANCE_STATE);
234 boolean isEnabled = ((maintenanceState != null) && (maintenanceState).equals(IN_SERVICE));
235
236 //Load port type (FIBER/COPPER)
237 Port.Type type = null;
238 for (Object o : pcfg.getList(PORT_EXTENSION)) {
239 String s = (String) o;
240 if (s.equals(OPTICAL_INPUT) || s.equals(OPTICAL_OUTPUT)) {
241 type = Port.Type.FIBER;
242
243 } else if (s.equals(PORT_ETHERNET) || s.equals(PORT_PLUGGABLE)) {
244 type = Port.Type.COPPER;
245 }
246 }
247
248 //Load port speed of Ethernet interface, expressed in Mb/s
249 Long speed = 0L; //should be the speed of optical port
250 if (type != null) {
251 if (type.equals(Port.Type.COPPER)) {
252 String speedString = pcfg.getString(PORT_SPEED);
253 if (speedString != null) {
254 speed = Long.parseLong(speedString.substring(speedString.lastIndexOf("speed_") + 6,
255 speedString.lastIndexOf("Mb")));
256 } else {
257 log.error("Lumentum NETCONF - Port speed of Ethernet port not correctly loaded");
258 }
259 }
260 } else {
261 log.error("Port Type not correctly loaded");
262 }
263
alessiod4a2b842019-04-30 18:43:17 +0200264 /**
265 * Setting the reverse port value for the unidirectional ports.
266 *
267 * In this device each port includes an input fiber and an output fiber.
268 * The 20 input fibers are numbered from MIN_MUX_PORT = 4101 to MAX_MUX_PORT = 4120.
269 * The 20 output fibers are numbered from MIN_DEM_PORT = 5201 to MAX_DEM_PORT = 5220.
270 *
271 * Where port 520x is always the reverse of 410x.
272 */
273 if ((portNum.toLong() >= MIN_MUX_PORT) && (portNum.toLong() <= MAX_MUX_PORT)) {
alessio1bf2a632019-06-04 15:47:39 +0200274 Long reversePortId = portNum.toLong() + DELTA_MUX_DEM_PORT;
alessiod4a2b842019-04-30 18:43:17 +0200275 annotations.set(OpticalPathIntent.REVERSE_PORT_ANNOTATION_KEY, reversePortId.toString());
276 }
277 if ((portNum.toLong() >= MIN_DEM_PORT) && (portNum.toLong() <= MAX_DEM_PORT)) {
alessio1bf2a632019-06-04 15:47:39 +0200278 Long reversePortId = portNum.toLong() - DELTA_MUX_DEM_PORT;
alessiod4a2b842019-04-30 18:43:17 +0200279 annotations.set(OpticalPathIntent.REVERSE_PORT_ANNOTATION_KEY, reversePortId.toString());
280 }
281
Alessio Giorgetti648b5382018-02-15 18:35:45 +0100282 //Load other information
283 pcfg.getKeys().forEachRemaining(k -> {
284 if (!k.contains(DN) && !k.contains(PORT_SPEED) && !k.contains(PORT_EXTENSION)
285 && !k.contains(MAINTENANCE_STATE)) {
286 String value = pcfg.getString(k);
287 if (!value.isEmpty()) {
288 k = StringUtils.replaceEach(k, new String[]{"loteeth:", "lotep:",
289 "lotepopt:", "config.", "=", ":",
290 "state."},
291 new String[]{"", "", "", "", "", "", ""});
292
293 annotations.set(k, value);
294
295 //To visualize port name in the ROADM app GUI
296 if (k.equals(PORT_NAME)) {
297 annotations.set(AnnotationKeys.PORT_NAME, value);
298 }
299
300 }
301 }
302 });
303
304 log.debug("Lumentum NETCONF - retrieved port {},{},{},{},{}",
305 portNum, isEnabled, type, speed, annotations.build());
306
alessio1bf2a632019-06-04 15:47:39 +0200307
308 if ((type == Port.Type.FIBER) &&
309 ((annotations.build().value(AnnotationKeys.PORT_NAME)).contains(MUX_PORT_NAME) ||
310 (annotations.build().value(AnnotationKeys.PORT_NAME)).contains(DEMUX_PORT_NAME) ||
311 (annotations.build().value(AnnotationKeys.PORT_NAME)).contains(LINE_PORT_NAME))) {
312
313 //These are the ports supporting OchSignals
alessiod4a2b842019-04-30 18:43:17 +0200314 portDescriptions.add(omsPortDescription(portNum,
315 isEnabled,
316 START_CENTER_FREQ_50,
317 END_CENTER_FREQ_50,
318 CHANNEL_SPACING_50.frequency(),
319 annotations.build()));
320 } else {
alessio1bf2a632019-06-04 15:47:39 +0200321 //These are COPPER ports, or FIBER ports not supporting OchSignals
alessiod4a2b842019-04-30 18:43:17 +0200322 DefaultPortDescription.Builder portDescriptionBuilder = DefaultPortDescription.builder();
323 portDescriptionBuilder.withPortNumber(portNum)
324 .isEnabled(isEnabled)
325 .type(type)
326 .portSpeed(speed)
327 .annotations(annotations.build());
Ray Milkeyf031e302018-09-07 10:07:24 -0700328
alessiod4a2b842019-04-30 18:43:17 +0200329 portDescriptions.add(portDescriptionBuilder.build());
330 }
Alessio Giorgetti648b5382018-02-15 18:35:45 +0100331 });
332
333 return portDescriptions;
334 }
335
336 //Following Lumentum documentation rpc operation to delete all connections
337 private boolean rpcRemoveAllConnections(String module) {
338 StringBuilder stringBuilder = new StringBuilder();
339 stringBuilder.append("<rpc xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">" + "\n");
340 stringBuilder.append(
341 "<remove-all-connections xmlns=\"http://www.lumentum.com/lumentum-ote-connection\">" + "\n");
342 stringBuilder.append("<dn>ne=1;chassis=1;card=1;module=" + module + "</dn>" + "\n");
343 stringBuilder.append("</remove-all-connections>" + "\n");
344 stringBuilder.append("</rpc>" + "\n");
345
346 return editCrossConnect(stringBuilder.toString());
347 }
348
349 private boolean editCrossConnect(String xcString) {
350 NetconfSession session = getNetconfSession();
Alessio Giorgetti648b5382018-02-15 18:35:45 +0100351 if (session == null) {
352 log.error("Lumentum NETCONF - session not found for {}", handler().data().deviceId());
353 return false;
354 }
355
356 try {
357 return session.editConfig(xcString);
358 } catch (NetconfException e) {
359 log.error("Failed to edit the CrossConnect edid-cfg for device {}",
360 handler().data().deviceId(), e);
361 log.debug("Failed configuration {}", xcString);
362 return false;
363 }
364 }
365
alessio1bf2a632019-06-04 15:47:39 +0200366 /**
367 * Helper method to get the Netconf session.
368 */
Alessio Giorgetti648b5382018-02-15 18:35:45 +0100369 private NetconfSession getNetconfSession() {
alessio1bf2a632019-06-04 15:47:39 +0200370 NetconfController controller =
371 checkNotNull(handler().get(NetconfController.class));
372 return controller.getNetconfDevice(did()).getSession();
373 }
Alessio Giorgetti648b5382018-02-15 18:35:45 +0100374
alessio1bf2a632019-06-04 15:47:39 +0200375 /**
376 * Helper method to get the device id.
377 */
378 private DeviceId did() {
379 return data().deviceId();
Alessio Giorgetti648b5382018-02-15 18:35:45 +0100380 }
381}