blob: 7a19fa94ee747c0b7a74c348185fb876e55c739f [file] [log] [blame]
Ramon Casellas99c16252019-05-31 14:29:00 +02001/*
2 * Copyright 2018-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 * This work was partially supported by EC H2020 project METRO-HAUL (761727).
17 */
18package org.onosproject.drivers.odtn.openroadm;
19
20import static com.google.common.base.Preconditions.checkNotNull;
21
22import com.google.common.collect.ImmutableList;
23import java.util.ArrayList;
24import java.util.HashMap;
25import java.util.List;
26import java.util.Map;
27import org.apache.commons.configuration.HierarchicalConfiguration;
28import org.apache.commons.configuration.XMLConfiguration;
29import org.apache.commons.configuration.tree.xpath.XPathExpressionEngine;
30import org.onlab.packet.ChassisId;
31import org.onlab.util.Frequency;
32import org.onosproject.drivers.utilities.XmlConfigParser;
33import org.onosproject.net.AnnotationKeys;
34import org.onosproject.net.ChannelSpacing;
35import org.onosproject.net.DefaultAnnotations;
36import org.onosproject.net.OchSignal;
37import org.onosproject.net.OduSignalType;
38import org.onosproject.net.PortNumber;
39import org.onosproject.net.device.DefaultDeviceDescription;
40import org.onosproject.net.device.DeviceDescription;
41import org.onosproject.net.device.DeviceDescriptionDiscovery;
42import org.onosproject.net.device.PortDescription;
43import org.onosproject.net.intent.OpticalPathIntent;
44import org.onosproject.net.optical.device.OchPortHelper;
45import org.onosproject.net.optical.device.OmsPortHelper;
46import org.onosproject.netconf.NetconfDevice;
47import org.onosproject.netconf.NetconfException;
48import org.onosproject.netconf.NetconfSession;
49
50import java.util.concurrent.ExecutionException;
51/**
52 * Driver Implementation of the DeviceDescrption discovery for OpenROADM.
53 */
54public class OpenRoadmDeviceDescription extends OpenRoadmNetconfHandlerBehaviour
55 implements DeviceDescriptionDiscovery {
56
57 // These annotations are added to the device and ports
58 public final class AnnotationKeys {
59 public static final String OPENROADM_NODEID = "openroadm-node-id";
60 public static final String OPENROADM_CIRCUIT_PACK_NAME =
61 "openroadm-circuit-pack-name";
62 public static final String OPENROADM_PORT_NAME = "openroadm-port-name";
63 public static final String OPENROADM_PARTNER_CIRCUIT_PACK_NAME =
64 "openroadm-partner-circuit-pack-name";
65 public static final String OPENROADM_PARTNER_PORT_NAME =
66 "openroadm-partner-port-name";
67 public static final String OPENROADM_LOGICAL_CONNECTION_POINT =
68 "openroadm-logical-connection-point";
69 private AnnotationKeys() {
70 // utility class
71 }
72 }
73
74 public static final ChannelSpacing CHANNEL_SPACING =
75 ChannelSpacing.CHL_50GHZ;
76
77
78 /*
79 * The following 2 values are not specified by the OpenROADM standard,
80 * but they are a reasonable default for a tunable C-band, defined from
81 * Channel C1 at 191.35 to C96 at 196.10 GHz (for a spacing at 50GHz)
82 */
83 public static final Frequency START_CENTER_FREQ = Frequency.ofGHz(191_350);
84 public static final Frequency STOP_CENTER_FREQ = Frequency.ofGHz(196_100);
85
86
87 public static final String OPENROADM_DEVICE_OPEN = //
88 "<org-openroadm-device xmlns=\"http://org/openroadm/device\">";
89 public static final String OPENROADM_DEVICE_CLOSE = //
90 "</org-openroadm-device>";
91
92
93 /**
94 * Builds a request to get OpenRoadm Device main node (within root).
95 *
96 * @param nodeTag the tag with the name to get e.g. <info/>
97 *
98 * @return A string with the Netconf RPC for a get with subtree info
99 */
100 private String getDeviceXmlNodeBuilder(final String nodeTag) {
101 StringBuilder filter = new StringBuilder();
102 filter.append(OPENROADM_DEVICE_OPEN);
103 filter.append(nodeTag);
104 filter.append(OPENROADM_DEVICE_CLOSE);
105 return filteredGetBuilder(filter.toString());
106 }
107
108 /**
109 * Builds a request to get Device details (<info>).
110 *
111 * @return A string with the Netconf RPC for a get with subtree info
112 */
113 private String getDeviceDetailsBuilder() {
114 return getDeviceXmlNodeBuilder("<info/>");
115 }
116
117 /**
118 * Builds a request to get Ports data (<circuit-packs>).
119 *
120 * @return A string with the Netconf RPC
121 */
122 private String getDeviceCircuitPacksBuilder() {
123 return getDeviceXmlNodeBuilder("<circuit-packs/>");
124 }
125
126 /**
127 * Builds a request to get External Links data (<external-link>).
128 *
129 * @return A string with the Netconf RPC
130 */
131 private String getDeviceExternalLinksBuilder() {
132 return getDeviceXmlNodeBuilder("<external-link/>");
133 }
134
135 /**
136 * Builds a request to get Device Degrees, config and operational data.
137 *
138 * @return A string with the Netconf RPC for a get with subtree rpcing based
139 * on /components/component/state/type being oc-platform-types:PORT
140 */
141 private String getDeviceDegreesBuilder() {
142 return getDeviceXmlNodeBuilder("<degree/>");
143 }
144
145 /**
146 * Builds a request to get Device SharedRiskGroups, config and operational
147 * data.
148 *
149 * @return A string with the Netconf RPC for a get with subtree
150 */
151 private String getDeviceSharedRiskGroupsBuilder() {
152 return getDeviceXmlNodeBuilder("<shared-risk-group/>");
153 }
154
155 /**
156 * Builds a request to get Ports data.
157 * Changed to XPath and added one based on classic filters since some agents
158 * do not support xpath filtering.
159 *
160 * @return A string with the Netconf RPC
161 */
162 private String getDeviceExternalPortsBuilderXPath() {
163 StringBuilder filter = new StringBuilder();
164 filter.append(
165 "/org-openroadm-device/circuit-packs/ports[port-qual='roadm-external']");
166 return xpathFilteredGetBuilder(filter.toString());
167 }
168
169 /**
170 * Builds a request to get Ports data.
171 *
172 * @return A string with the Netconf RPC
173 */
174 private String getDeviceExternalPortsBuilder() {
175 StringBuilder filter = new StringBuilder();
176 filter.append(OPENROADM_DEVICE_OPEN);
177 filter.append("<circuit-packs>");
178 filter.append(" <ports>");
179 filter.append(" <port-qual>roadm-external</port-qual>");
180 filter.append(" </ports>");
181 filter.append("</circuit-packs>");
182 filter.append(OPENROADM_DEVICE_CLOSE);
183 return filteredGetBuilder(filter.toString());
184 }
185
186 /**
187 * Builds a request to get External Links data.
188 *
189 * @return A string with the Netconf RPC
190 */
191 private String getDeviceExternalLinksBuilderXpath() {
192 StringBuilder filter = new StringBuilder();
193 filter.append("/org-openroadm-device/external-link");
194 return xpathFilteredGetBuilder(filter.toString());
195 }
196
197 /**
198 * Builds a request to get External Links data.
199 *
200 * @param nodeId OpenROADM node identifier.
201 * @param circuitPackName name of the circuit part of the port.
202 * @param portName name of the port.
203 * @return A string with the Netconf RPC
204 */
205 private String getDeviceExternalLinkForPortBuilderXPath(
206 String nodeId, String circuitPackName, String portName) {
207 StringBuilder filter = new StringBuilder();
208 filter.append("/org-openroadm-device/external-link[");
209 filter.append("./source/node-id='");
210 filter.append(nodeId);
211 filter.append("' and ");
212 filter.append("./source/circuit-pack-name='");
213 filter.append(circuitPackName);
214 filter.append("' and ");
215 filter.append("./source/port-name='");
216 filter.append(portName);
217 filter.append("']");
218 return xpathFilteredGetBuilder(filter.toString());
219 }
220
221 private String getDeviceExternalLinkForPortBuilder(String nodeId,
222 String circuitPackName,
223 String portName) {
224 StringBuilder filter = new StringBuilder();
225 filter.append(OPENROADM_DEVICE_OPEN);
226 filter.append("<external-link>");
227 filter.append(" <source>");
228 filter.append(" <node-id>");
229 filter.append(nodeId);
230 filter.append("</node-id>");
231 filter.append(" <circuit-pack-name>");
232 filter.append(circuitPackName);
233 filter.append("</circuit-pack-name>");
234 filter.append(" <port-name>");
235 filter.append(portName);
236 filter.append("</port-name>");
237 filter.append(" </source>");
238 filter.append("</external-link>");
239 filter.append(OPENROADM_DEVICE_CLOSE);
240 return xpathFilteredGetBuilder(filter.toString());
241 }
242
243
244 /**
245 * Returns a DeviceDescription with Device info.
246 *
247 * @return DeviceDescription or null
248 */
249 @Override
250 public DeviceDescription discoverDeviceDetails() {
251 boolean defaultAvailable = true;
252 NetconfDevice ncDevice = getNetconfDevice();
253 if (ncDevice == null) {
254 log.error("ONOS Error: Device reachable, deviceID {} is not in Map", did());
255 return null;
256 }
257 DefaultAnnotations.Builder annotationsBuilder =
258 DefaultAnnotations.builder();
259
260 // Some defaults
261 String vendor = "UNKNOWN";
262 String hwVersion = "2.2.0";
263 String swVersion = "2.2.0";
264 String serialNumber = "0x0000";
265 String chassisId = "0";
266 String nodeType = "rdm";
267
268 // Get the session, if null, at least we can use the defaults.
269 NetconfSession session = getNetconfSession(did());
270 if (session != null) {
271 try {
272 String reply = session.rpc(getDeviceDetailsBuilder()).get();
273 XMLConfiguration xconf =
274 (XMLConfiguration) XmlConfigParser.loadXmlString(reply);
275 String nodeId =
276 xconf.getString("data.org-openroadm-device.info.node-id", "");
277 if (nodeId.equals("")) {
278 log.error("[OPENROADM] {} org-openroadm-device node-id undefined, returning", did());
279 return null;
280 }
281 annotationsBuilder.set(AnnotationKeys.OPENROADM_NODEID, nodeId);
282 nodeType = xconf.getString("data.org-openroadm-device.info.node-type", "");
283 if (nodeType.equals("")) {
284 log.error("[OPENROADM] {} empty node-type", did());
285 return null;
286 }
287 vendor = xconf.getString(
288 "data.org-openroadm-device.info.vendor", vendor);
289 hwVersion = xconf.getString(
290 "data.org-openroadm-device.info.model", hwVersion);
291 swVersion = xconf.getString(
292 "data.org-openroadm-device.info.softwareVersion", swVersion);
293 serialNumber = xconf.getString(
294 "data.org-openroadm-device.info.serial-id", serialNumber);
295 chassisId = xconf.getString(
296 "data.org-openroadm-device.info.node-number", chassisId);
297
298 // GEOLOCATION
299 String longitudeStr = xconf.getString(
300 "data.org-openroadm-device.info.geoLocation.longitude");
301 String latitudeStr = xconf.getString(
302 "data.org-openroadm-device.info.geoLocation.latitude");
303 if (longitudeStr != null && latitudeStr != null) {
304 annotationsBuilder
305 .set(org.onosproject.net.AnnotationKeys.LONGITUDE,
306 longitudeStr)
307 .set(org.onosproject.net.AnnotationKeys.LATITUDE,
308 latitudeStr);
309 }
310 } catch (NetconfException | InterruptedException | ExecutionException e) {
311 log.error("[OPENROADM] {} exception", did());
312 return null;
313 }
314 } else {
315 log.debug("[OPENROADM] - No session {}", did());
316 }
317
318 log.debug("[OPENROADM] {} - VENDOR {} HWVERSION {} SWVERSION {} SERIAL {} CHASSIS {}",
319 did(), vendor, hwVersion, swVersion, serialNumber, chassisId);
320 ChassisId cid = new ChassisId(Long.valueOf(chassisId, 10));
321 /*
322 * OpenROADM defines multiple devices (node types). This driver has been tested with
323 * ROADMS, (node type, "rdm"). Other devices can also be discovered, and this code is here
324 * for future developments - untested - it is likely that the XML documents
325 * are model specific.
326 */
327 org.onosproject.net.Device.Type type;
328 if (nodeType.equals("rdm")) {
329 type = org.onosproject.net.Device.Type.ROADM;
330 } else if (nodeType.equals("ila")) {
331 type = org.onosproject.net.Device.Type.OPTICAL_AMPLIFIER;
332 } else if (nodeType.equals("xpdr")) {
333 type = org.onosproject.net.Device.Type.TERMINAL_DEVICE;
334 } else if (nodeType.equals("extplug")) {
335 type = org.onosproject.net.Device.Type.OTHER;
336 } else {
337 log.error("[OPENROADM] {} unsupported node-type", did());
338 return null;
339 }
340 DeviceDescription desc = new DefaultDeviceDescription(
341 did().uri(), type, vendor, hwVersion, swVersion, serialNumber, cid,
342 defaultAvailable, annotationsBuilder.build());
343 return desc;
344 }
345
346
347
348
349 /**
350 * Get the external links as a list of XML hieriarchical configs.
351 * @param session the NETConf session to the OpenROADM device.
352 * @return a list of hierarchical conf. each one external link.
353 */
354 List<HierarchicalConfiguration> getExternalLinks(NetconfSession session) {
355 try {
356 String reply = session.rpc(getDeviceExternalLinksBuilder()).get();
357 XMLConfiguration extLinksConf = //
358 (XMLConfiguration) XmlConfigParser.loadXmlString(reply);
359 extLinksConf.setExpressionEngine(new XPathExpressionEngine());
360 return extLinksConf.configurationsAt(
361 "/data/org-openroadm-device/external-link");
362 } catch (NetconfException | InterruptedException | ExecutionException e) {
363 log.error("[OPENROADM] {} exception getting external links", did());
364 return ImmutableList.of();
365 }
366 }
367
368
369 /**
370 * Get the circuit packs from the device as a list of XML hierarchical configs.
371 * @param session the NETConf session to the OpenROADM device.
372 * @return a list of hierarchical conf. each one circuit pack.
373 */
374 List<HierarchicalConfiguration> getCircuitPacks(NetconfSession session) {
375 try {
376 String reply = session.rpc(getDeviceCircuitPacksBuilder()).get();
377 XMLConfiguration cpConf = //
378 (XMLConfiguration) XmlConfigParser.loadXmlString(reply);
379 cpConf.setExpressionEngine(new XPathExpressionEngine());
380 return cpConf.configurationsAt(
381 "/data/org-openroadm-device/circuit-packs");
382 } catch (NetconfException | InterruptedException | ExecutionException e) {
383 log.error("[OPENROADM] {} exception getting circuit packs", did());
384 return ImmutableList.of();
385 }
386 }
387
388 /**
389 * Returns a list of PortDescriptions for the device.
390 *
391 * @return a list of descriptions.
392 */
393 @Override
394 public List<PortDescription> discoverPortDetails() {
395 NetconfSession session = getNetconfSession(did());
396 if (session == null) {
397 log.error("discoverPortDetails null session for {}", did());
398 return ImmutableList.of();
399 }
400 if (!getDevice().annotations().keys().contains("openroadm-node-id")) {
401 log.error("PortDiscovery before DeviceDiscovery, using netconf");
402 return ImmutableList.of();
403 }
404 String nodeId = getDevice().annotations().value("openroadm-node-id");
405 List<PortDescription> list = new ArrayList<PortDescription>();
406 List<HierarchicalConfiguration> circuitPacks = getCircuitPacks(session);
407 /*
408 * Iterate all the ports. We need to pass the whole circuitPacks list
409 * because some port data refers to ports in other circuit packs
410 * (reverse), in addition to pass the current circuit pack name, list of
411 * external ports etc
412 */
413 for (HierarchicalConfiguration c : circuitPacks) {
414 parsePorts(list, nodeId, // c contains the whole circuit pack
415 c.getString("circuit-pack-name"), //
416 c.configurationsAt(
417 "ports[port-qual='roadm-external']"), // ext ports
418 circuitPacks, getExternalLinks(session));
419 }
420 return list;
421 }
422
423 /**
424 * Parses port information.
425 *
426 * @param list List of port descriptions to append to.
427 * @param nodeId OpenROADM node identifier.
428 * @param circuitPackName Name of the circuit pack the ports belong to
429 * @param ports hierarchical conf containing all the ports for the circuit
430 * pack
431 * @param circuitPacks all the circuit packs (to correlate data).
432 * @param extLinks Hierarchical configuration containing all the ext.
433 * links.
434 */
435 protected void parsePorts(List<PortDescription> list, String nodeId,
436 String circuitPackName,
437 List<HierarchicalConfiguration> ports,
438 List<HierarchicalConfiguration> circuitPacks,
439 List<HierarchicalConfiguration> extLinks) {
440 checkNotNull(nodeId);
441 checkNotNull(circuitPackName);
442 for (HierarchicalConfiguration port : ports) {
443 try {
444 String portName = checkNotNull(port.getString("port-name"));
445 long portNum = Long.parseLong(port.getString("label"));
446 PortNumber pNum = PortNumber.portNumber(portNum);
447 PortNumber reversepNum = findReversePort(port, circuitPacks);
448 // To see if we have an external port
449 HierarchicalConfiguration eLink = null;
450 for (HierarchicalConfiguration extLink : extLinks) {
451 String eln =
452 checkNotNull(extLink.getString("external-link-name"));
453 String esnid =
454 checkNotNull(extLink.getString("source/node-id"));
455 String escpn =
456 checkNotNull(extLink.getString("source/circuit-pack-name"));
457 String espn =
458 checkNotNull(extLink.getString("source/port-name"));
459 if (nodeId.equals(esnid) && circuitPackName.equals(escpn) &&
460 portName.equals(espn)) {
461 eLink = extLink;
462 }
463 }
464 PortDescription pd = parsePortComponent(
465 nodeId, circuitPackName, pNum, reversepNum, port, eLink);
466 if (pd != null) {
467 list.add(pd);
468 }
469 } catch (NetconfException e) {
470 log.error("[OPENROADM] {} NetConf exception", did());
471 return;
472 }
473 }
474 }
475
476 /**
477 * Given a device port (external), return its patner/reverse port.
478 *
479 * @param thisPort the port for which we are looking for the reverse port.
480 * @param circuitPacks all the circuit packs (to correlate data).
481 * @return the port number for the reverse port.
482 * @throws NetconfException .
483 */
484 protected PortNumber
485 findReversePort(HierarchicalConfiguration thisPort,
486 List<HierarchicalConfiguration> circuitPacks)
487 throws NetconfException {
488 String partnerCircuitPackName =
489 checkNotNull(thisPort.getString("partner-port/circuit-pack-name"));
490 String partnerPortName =
491 checkNotNull(thisPort.getString("partner-port/port-name"));
492 for (HierarchicalConfiguration c : circuitPacks) {
493 if (!partnerCircuitPackName.equals(
494 c.getString("circuit-pack-name"))) {
495 continue;
496 }
497 for (HierarchicalConfiguration thatPort :
498 c.configurationsAt("ports[port-qual='roadm-external']")) {
499 String thatPortName = thatPort.getString("port-name");
500 if (partnerPortName.equals(thatPortName)) {
501 long thatPortNum =
502 Long.parseLong(thatPort.getString("label"));
503 return PortNumber.portNumber(thatPortNum);
504 }
505 }
506 }
507 // We should not reach here
508 throw new NetconfException("missing partner/reverse port info");
509 }
510
511 /**
512 * Parses a component XML doc into a PortDescription.
513 * An OMS port description is constructed from XML parsed data.
514 *
515 * @param port the port to parse
516 * @return PortDescription or null
517 */
518 private PortDescription
519 parsePortComponent(String nodeId, String circuitPackName, PortNumber pNum,
520 PortNumber reversepNum, HierarchicalConfiguration port,
521 HierarchicalConfiguration extLink) {
522 Map<String, String> annotations = new HashMap<>();
523 annotations.put(AnnotationKeys.OPENROADM_NODEID, nodeId);
524 annotations.put(AnnotationKeys.OPENROADM_CIRCUIT_PACK_NAME,
525 circuitPackName);
526 annotations.put(AnnotationKeys.OPENROADM_PORT_NAME,
527 port.getString("port-name"));
528 annotations.put(AnnotationKeys.OPENROADM_PARTNER_CIRCUIT_PACK_NAME,
529 port.getString("partner-port/circuit-pack-name", ""));
530 annotations.put(AnnotationKeys.OPENROADM_PARTNER_PORT_NAME,
531 port.getString("partner-port/port-name", ""));
532 annotations.put(AnnotationKeys.OPENROADM_LOGICAL_CONNECTION_POINT,
533 port.getString("logical-connection-point", ""));
534 // Annotate the reverse port, this is needed for bidir intents.
535 annotations.put(OpticalPathIntent.REVERSE_PORT_ANNOTATION_KEY,
536 Long.toString(reversepNum.toLong()));
537
538 // for backwards compatibility
539 annotations.put("logical-connection-point",
540 port.getString("logical-connection-point", ""));
541 // Annotate external link if we found one for this port
542 if (extLink != null) {
543 String ednid = extLink.getString("destination/node-id");
544 String edcpn = extLink.getString("destination/circuit-pack-name");
545 String edpn = extLink.getString("destination/port-name");
546 annotations.put("openroadm-external-node-id", ednid);
547 annotations.put("openroadm-external-circuit-pack-name", edcpn);
548 annotations.put("openroadm-external-port-name", edpn);
549 }
550
551 /*
552 * Declare the actual optical port:
553 * Assumptions: client ports are OCh, assumed to carry ODU4 (should be
554 * configurable)
555 */
556 if (port.getString("port-wavelength-type", "wavelength")
557 .equals("wavelength")) {
558 // OchSignal is needed for OchPortDescription constructor, but it's
559 // tunable
560 OchSignal signalId =
561 OchSignal.newDwdmSlot(ChannelSpacing.CHL_50GHZ, 3);
562 return OchPortHelper.ochPortDescription(
563 pNum, true /* enabled */, OduSignalType.ODU4, true /* tunable */,
564 signalId,
565 DefaultAnnotations.builder().putAll(annotations).build());
566 } else {
567 return OmsPortHelper.omsPortDescription(
568 pNum, true /* enabled */,
569 // Relationship : START and STOP Freq not being used (See
570 // LambdaQuery)
571 START_CENTER_FREQ, STOP_CENTER_FREQ, CHANNEL_SPACING.frequency(),
572 DefaultAnnotations.builder().putAll(annotations).build());
573 }
574 }
575}