blob: 4762bf18d10918d8e4f231cbce1873ed588deaab [file] [log] [blame]
alessio0a0f3342019-10-28 16:58:01 +01001/*
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 */
18
19package org.onosproject.drivers.odtn.openconfig;
20
21import static com.google.common.base.Preconditions.checkNotNull;
22import static org.slf4j.LoggerFactory.getLogger;
23
24import org.onosproject.net.Device;
25import org.onosproject.net.DeviceId;
26import org.onosproject.net.DefaultAnnotations;
27import org.onosproject.net.SparseAnnotations;
28import org.onosproject.net.OchSignal;
29import org.onosproject.net.ChannelSpacing;
30import org.onosproject.net.PortNumber;
31import org.onosproject.net.CltSignalType;
32import org.onosproject.net.OduSignalType;
33import org.onosproject.net.optical.device.OduCltPortHelper;
34import org.slf4j.Logger;
35
36import java.util.HashMap;
37import java.util.List;
38import java.util.Map;
39import java.util.Objects;
40import java.util.stream.Collectors;
41import java.util.concurrent.CompletableFuture;
42
43import org.onlab.packet.ChassisId;
44
45import org.apache.commons.configuration.HierarchicalConfiguration;
46import org.apache.commons.configuration.XMLConfiguration;
47import org.apache.commons.configuration.tree.xpath.XPathExpressionEngine;
48
49import org.onosproject.drivers.utilities.XmlConfigParser;
50
51import org.onosproject.net.device.DeviceService;
52import org.onosproject.net.device.DeviceDescription;
53import org.onosproject.net.device.DeviceDescriptionDiscovery;
54import org.onosproject.net.device.DefaultDeviceDescription;
55import org.onosproject.net.device.PortDescription;
56
57import org.onosproject.net.driver.AbstractHandlerBehaviour;
58
59import org.onosproject.net.optical.device.OchPortHelper;
60
61import org.onosproject.netconf.NetconfController;
62import org.onosproject.netconf.NetconfDevice;
63import org.onosproject.netconf.NetconfException;
64import org.onosproject.netconf.NetconfSession;
65
66import com.google.common.collect.ImmutableList;
67
68import org.onosproject.odtn.behaviour.OdtnDeviceDescriptionDiscovery;
69
70
71/**
72 * Driver Implementation of the DeviceDescription discovery for OpenConfig terminal devices.
73 *
74 * As defined in OpenConfig each PORT component includes a subcomponent:
75 * --- client ports have a subcomponent of type oc-platform-types:TRANSCEIVER
76 * --- line ports have a subcomponent of type oc-opt-types:OPTICAL_CHANNEL
77 *
78 * Tested with a model in which each port includes the following two properties:
79 * --- odtn-port-type: can assume values "client" and "line"
80 * --- onos-index: integer value
81 *
82 * Other assumptions:
83 * --- The port name is in the format "port-xxx"
84 * --- The subcomponent of type TRANSCEIVER has a name in the format "transceiver-xxx"
85 * --- The subcomponent of type OPTICAL_CHANNEL has a name in the format "channel-xxx"
86 * --- In the section <terminal-device><logical-channels> the channel with index xxx is associated to transceiver-xxx
87 *
88 * Where xxx is the value of the onos-index property (e.g., port-11801, transceiver-11801, channel-11801)
89 *
90 * See simplified example of a port component:
91 *
92 * //CHECKSTYLE:OFF
93 * <component>
94 * <name>port-11801</name>
95 * <state>
96 * <name>port-11801</name>
97 * <type>oc-platform-types:PORT</type>
98 * </state>
99 * <properties>
100 * <property>
101 * <name>odtn-port-type</name>
102 * <state>
103 * <name>odtn-port-type</name>
104 * <value>client</value>
105 * </state>
106 * </property>
107 * <property>
108 * <name>onos-index</name>
109 * <state>
110 * <name>onos-index</name>
111 * <value>11801</value>
112 * </state>
113 * </property>
114 * </properties>
115 * <subcomponents>
116 * <subcomponent>
117 * <name>transceiver-11801</name>
118 * <state>
119 * <name>transceiver-11801</name>
120 * </state>
121 * </subcomponent>
122 * </subcomponents>
123 * </component>
124 * <terminal-device>
125 * <logical-channels>
126 * <channel>
127 * <index>11801</index>
128 * <state>
129 * <index>11801</index>
130 * <description>Logical channel 11801</description>
131 * <admin-state>DISABLED</admin-state>
132 * <rate-class>oc-opt-types:TRIB_RATE_10G</rate-class>
133 * <trib-protocol>oc-opt-types:PROT_10GE_LAN</trib-protocol>
134 * <logical-channel-type>oc-opt-types:PROT_ETHERNET</logical-channel-type>
135 * <loopback-mode>NONE</loopback-mode>
136 * <test-signal>false</test-signal>
137 * <link-state>UP</link-state>
138 * </state>
139 * <ingress>
140 * <state>
141 * <transceiver>transceiver-11801</transceiver>
142 * </state>
143 * </ingress>
144 * </channel>
145 * <logical-channels>
146 * <terminal-device>
147 * //CHECKSTYLE:ON
148 */
149public class ClientLineTerminalDeviceDiscovery
150 extends AbstractHandlerBehaviour
151 implements OdtnDeviceDescriptionDiscovery, DeviceDescriptionDiscovery {
152
153 private static final String RPC_TAG_NETCONF_BASE =
154 "<rpc xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">";
155
156 private static final String OC_PLATFORM_TYPES_OPERATING_SYSTEM =
157 "oc-platform-types:OPERATING_SYSTEM";
158
159 private static final String RPC_CLOSE_TAG = "</rpc>";
160
161 private static final String OC_PLATFORM_TYPES_TRANSCEIVER =
162 "oc-platform-types:TRANSCEIVER";
163
164 private static final String OC_PLATFORM_TYPES_PORT =
165 "oc-platform-types:PORT";
166
167 private static final String OC_TRANSPORT_TYPES_OPTICAL_CHANNEL =
168 "oc-opt-types:OPTICAL_CHANNEL";
169
170 private static final Logger log = getLogger(ClientLineTerminalDeviceDiscovery.class);
171
172
173 /**
174 * Returns the NetconfSession with the device for which the method was called.
175 *
176 * @param deviceId device indetifier
177 *
178 * @return The netconf session or null
179 */
180 private NetconfSession getNetconfSession(DeviceId deviceId) {
181 NetconfController controller = handler().get(NetconfController.class);
182 NetconfDevice ncdev = controller.getDevicesMap().get(deviceId);
183 if (ncdev == null) {
184 log.trace("No netconf device, returning null session");
185 return null;
186 }
187 return ncdev.getSession();
188 }
189
190
191 /**
192 * Get the deviceId for which the methods apply.
193 *
194 * @return The deviceId as contained in the handler data
195 */
196 private DeviceId did() {
197 return handler().data().deviceId();
198 }
199
200
201 /**
202 * Get the device instance for which the methods apply.
203 *
204 * @return The device instance
205 */
206 private Device getDevice() {
207 DeviceService deviceService = checkNotNull(handler().get(DeviceService.class));
208 Device device = deviceService.getDevice(did());
209 return device;
210 }
211
212
213 /**
214 * Construct a String with a Netconf filtered get RPC Message.
215 *
216 * @param filter A valid XML tree with the filter to apply in the get
217 * @return a String containing the RPC XML Document
218 */
219 private String filteredGetBuilder(String filter) {
220 StringBuilder rpc = new StringBuilder(RPC_TAG_NETCONF_BASE);
221 rpc.append("<get>");
222 rpc.append("<filter type='subtree'>");
223 rpc.append(filter);
224 rpc.append("</filter>");
225 rpc.append("</get>");
226 rpc.append(RPC_CLOSE_TAG);
227 return rpc.toString();
228 }
229
230
231 /**
232 * Construct a String with a Netconf filtered get RPC Message.
233 *
234 * @param filter A valid XPath Expression with the filter to apply in the get
235 * @return a String containing the RPC XML Document
236 *
237 * Note: server must support xpath capability.
238
239 * <select=" /components/component[name='PORT-A-In-1']/properties/...
240 * ...property[name='onos-index']/config/value" type="xpath"/>
241 */
242 private String xpathFilteredGetBuilder(String filter) {
243 StringBuilder rpc = new StringBuilder(RPC_TAG_NETCONF_BASE);
244 rpc.append("<get>");
245 rpc.append("<filter type='xpath' select=\"");
246 rpc.append(filter);
247 rpc.append("\"/>");
248 rpc.append("</get>");
249 rpc.append(RPC_CLOSE_TAG);
250 return rpc.toString();
251 }
252
253
254 /**
255 * Builds a request to get Device details, operational data.
256 *
257 * @return A string with the Netconf RPC for a get with subtree rpcing based on
258 * /components/component/state/type being oc-platform-types:OPERATING_SYSTEM
259 */
260 private String getDeviceDetailsBuilder() {
261 StringBuilder filter = new StringBuilder();
262 filter.append("<components xmlns='http://openconfig.net/yang/platform'>");
263 filter.append(" <component>");
264 filter.append(" <state>");
265 filter.append(" <type xmlns:oc-platform-types='http://openconfig.net/yang/platform-types'>");
266 filter.append(OC_PLATFORM_TYPES_OPERATING_SYSTEM);
267 filter.append(" </type>");
268 filter.append(" </state>");
269 filter.append(" </component>");
270 filter.append("</components>");
271 return filteredGetBuilder(filter.toString());
272 }
273
274
275 /**
276 * Builds a request to get Device Components, config and operational data.
277 *
278 * @return A string with the Netconf RPC for a get with subtree rpcing based on
279 * /components/
280 */
281 private String getDeviceComponentsBuilder() {
282 return filteredGetBuilder(
283 "<components xmlns='http://openconfig.net/yang/platform'/>");
284 }
285
286
287 /**
288 * Builds a request to get Device Ports, config and operational data.
289 *
290 * @return A string with the Netconf RPC for a get with subtree rpcing based on
291 * /components/component/state/type being oc-platform-types:PORT
292 */
293 private String getDevicePortsBuilder() {
294 StringBuilder rpc = new StringBuilder();
295 rpc.append("<components xmlns='http://openconfig.net/yang/platform'>");
296 rpc.append(" <component><state>");
297 rpc.append(" <type xmlns:oc-platform-types='http://openconfig.net/");
298 rpc.append("yang/platform-types'>oc-platform-types:PORT</type>");
299 rpc.append(" </state></component>");
300 rpc.append("</components>");
301 return filteredGetBuilder(rpc.toString());
302 }
303
304
305 /**
306 * Returns a DeviceDescription with Device info.
307 *
308 * @return DeviceDescription or null
309 *
310 * //CHECKSTYLE:OFF
311 * <pre>{@code
312 * <data>
313 * <components xmlns="http://openconfig.net/yang/platform">
314 * <component>
315 * <state>
316 * <name>FIRMWARE</name>
317 * <type>oc-platform-types:OPERATING_SYSTEM</type>
318 * <description>CTTC METRO-HAUL Emulated OpenConfig TerminalDevice</description>
319 * <version>0.0.1</version>
320 * </state>
321 * </component>
322 * </components>
323 * </data>
324 *}</pre>
325 * //CHECKSTYLE:ON
326 */
327 @Override
328 public DeviceDescription discoverDeviceDetails() {
329 boolean defaultAvailable = true;
330 SparseAnnotations annotations = DefaultAnnotations.builder().build();
331
332 log.debug("ClientLineTerminalDeviceDiscovery::discoverDeviceDetails device {}", did());
333
334 // Other option "OTN" or "OTHER", we use TERMINAL_DEVICE
335 org.onosproject.net.Device.Type type =
336 Device.Type.TERMINAL_DEVICE;
337
338 // Some defaults
339 String vendor = "NOVENDOR";
340 String serialNumber = "0xCAFEBEEF";
341 String hwVersion = "0.2.1";
342 String swVersion = "0.2.1";
343 String chassisId = "128";
344
345 // Get the session,
346 NetconfSession session = getNetconfSession(did());
347 try {
348 String reply = session.get(getDeviceDetailsBuilder());
349 log.debug("REPLY to DeviceDescription {}", reply);
350
351 // <rpc-reply> as root node, software hardare version requires openconfig >= 2018
352 XMLConfiguration xconf = (XMLConfiguration) XmlConfigParser.loadXmlString(reply);
353 vendor = xconf.getString("data.components.component.state.mfg-name", vendor);
354 serialNumber = xconf.getString("data.components.component.state.serial-no", serialNumber);
355 swVersion = xconf.getString("data.components.component.state.software-version", swVersion);
356 hwVersion = xconf.getString("data.components.component.state.hardware-version", hwVersion);
357 } catch (Exception e) {
358 log.error("ClientLineTerminalDeviceDiscovery::discoverDeviceDetails - Failed to retrieve session {}",
359 did());
360 throw new IllegalStateException(new NetconfException("Failed to retrieve version info.", e));
361 }
362
363 ChassisId cid = new ChassisId(Long.valueOf(chassisId, 10));
364
365 log.info("Device retrieved details");
366 log.info("VENDOR {}", vendor);
367 log.info("HWVERSION {}", hwVersion);
368 log.info("SWVERSION {}", swVersion);
369 log.info("SERIAL {}", serialNumber);
370 log.info("CHASSISID {}", chassisId);
371
372 return new DefaultDeviceDescription(did().uri(),
373 type, vendor, hwVersion, swVersion, serialNumber,
374 cid, defaultAvailable, annotations);
375 }
376
377
378
379 /**
380 * Returns a list of PortDescriptions for the device.
381 *
382 * @return a list of descriptions.
383 *
384 * The RPC reply follows the following pattern:
385 * //CHECKSTYLE:OFF
386 * <pre>{@code
387 * <?xml version="1.0" encoding="UTF-8"?>
388 * <rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="7">
389 * <data>
390 * <components xmlns="http://openconfig.net/yang/platform">
391 * <component>....
392 * </component>
393 * <component>....
394 * </component>
395 * </components>
396 * </data>
397 * </rpc-reply>
398 * }</pre>
399 * //CHECKSTYLE:ON
400 */
401 @Override
402 public List<PortDescription> discoverPortDetails() {
403 try {
404 XPathExpressionEngine xpe = new XPathExpressionEngine();
405 NetconfSession session = getNetconfSession(did());
406 if (session == null) {
407 log.error("discoverPortDetails called with null session for {}", did());
408 return ImmutableList.of();
409 }
410
411 CompletableFuture<String> fut = session.rpc(getDeviceComponentsBuilder());
412 String rpcReply = fut.get();
413
414 XMLConfiguration xconf = (XMLConfiguration) XmlConfigParser.loadXmlString(rpcReply);
415 xconf.setExpressionEngine(xpe);
416
417 log.debug("REPLY {}", rpcReply);
418 HierarchicalConfiguration components = xconf.configurationAt("data/components");
419 return parsePorts(components);
420 } catch (Exception e) {
421 log.error("Exception discoverPortDetails() {}", did(), e);
422 return ImmutableList.of();
423 }
424 }
425
426 /**
427 * Parses port information from OpenConfig XML configuration.
428 *
429 * @param components the XML document with components root.
430 * @return List of ports
431 *
432 * //CHECKSTYLE:OFF
433 * <pre>{@code
434 * <components xmlns="http://openconfig.net/yang/platform">
435 * <component>....
436 * </component>
437 * <component>....
438 * </component>
439 * </components>
440 * }</pre>
441 * //CHECKSTYLE:ON
442 */
443 protected List<PortDescription> parsePorts(HierarchicalConfiguration components) {
444 return components.configurationsAt("component").stream()
445 .filter(component -> {
446 return !component.getString("name", "unknown").equals("unknown") &&
447 component.getString("state/type", "unknown").equals(OC_PLATFORM_TYPES_PORT);
448 })
449 .map(component -> {
450 try {
451 // Pass the root document for cross-reference
452 return parsePortComponent(component, components);
453 } catch (Exception e) {
454 return null;
455 }
456 }
457 )
458 .filter(Objects::nonNull)
459 .collect(Collectors.toList());
460 }
461
462
463 /**
464 * Checks if a given component has a subcomponent of a given type.
465 *
466 * @param component subtree to parse looking for subcomponents.
467 * @param components the full components tree, to cross-ref in
468 * case we need to check (sub)components' types.
469 *
470 * @return true or false
471 */
472 private boolean hasSubComponentOfType(
473 HierarchicalConfiguration component,
474 HierarchicalConfiguration components,
475 String type) {
476 long count = component.configurationsAt("subcomponents/subcomponent")
477 .stream()
478 .filter(subcomponent -> {
479 String scName = subcomponent.getString("name");
480 StringBuilder sb = new StringBuilder("component[name='");
481 sb.append(scName);
482 sb.append("']/state/type");
483 String scType = components.getString(sb.toString(), "unknown");
484 return scType.equals(type);
485 })
486 .count();
487 return (count > 0);
488 }
489
490
491 /**
492 * Checks if a given component has a subcomponent of type OPTICAL_CHANNEL.
493 *
494 * @param component subtree to parse
495 * @param components the full components tree, to cross-ref in
496 * case we need to check transceivers or optical channels.
497 *
498 * @return true or false
499 */
500 private boolean hasOpticalChannelSubComponent(
501 HierarchicalConfiguration component,
502 HierarchicalConfiguration components) {
503 return hasSubComponentOfType(component, components, OC_TRANSPORT_TYPES_OPTICAL_CHANNEL);
504 }
505
506
507 /**
508 * Checks if a given component has a subcomponent of type TRANSCEIVER.
509 *
510 * @param component subtree to parse
511 * @param components the full components tree, to cross-ref in
512 * case we need to check transceivers or optical channels.
513 *
514 * @return true or false
515 */
516 private boolean hasTransceiverSubComponent(
517 HierarchicalConfiguration component,
518 HierarchicalConfiguration components) {
519 return hasSubComponentOfType(component, components, OC_PLATFORM_TYPES_TRANSCEIVER);
520 }
521
522
523 /**
524 * Parses a component XML doc into a PortDescription.
525 *
526 * @param component subtree to parse. It must be a component ot type PORT.
527 * @param components the full components tree, to cross-ref in
528 * case we need to check transceivers or optical channels.
529 *
530 * @return PortDescription or null if component does not have onos-index
531 */
532 private PortDescription parsePortComponent(
533 HierarchicalConfiguration component,
534 HierarchicalConfiguration components) {
535 Map<String, String> annotations = new HashMap<>();
536 String name = component.getString("name");
537 String type = component.getString("state/type");
538
539 log.info("Parsing Component {} type {}", name, type);
540
541 annotations.put(OdtnDeviceDescriptionDiscovery.OC_NAME, name);
542 annotations.put(OdtnDeviceDescriptionDiscovery.OC_TYPE, type);
543
544 // Store all properties as port properties
545 component.configurationsAt("properties/property")
546 .forEach(property -> {
547 String pn = property.getString("name");
548 String pv = property.getString("state/value");
549 annotations.put(pn, pv);
550 });
551
552 // Assing an ONOS port number
553 PortNumber portNum;
554 if (annotations.containsKey(ONOS_PORT_INDEX)) {
555 portNum = PortNumber.portNumber(Long.parseLong(annotations.get(ONOS_PORT_INDEX)));
556 } else {
557 log.warn("PORT {} does not include onos-index, hashing...", name);
558 portNum = PortNumber.portNumber(name.hashCode());
559 }
560 log.debug("PORT {} number {}", name, portNum);
561
562 // The heuristic to know if it is client or line side
563 if (!annotations.containsKey(PORT_TYPE)) {
564 if (hasTransceiverSubComponent(component, components)) {
565 annotations.put(PORT_TYPE, OdtnPortType.CLIENT.value());
566 } else if (hasOpticalChannelSubComponent(component, components)) {
567 annotations.put(PORT_TYPE, OdtnPortType.LINE.value());
568 }
569 }
570
571 // Build the port
572 // NOTE: using portNumber(id, name) breaks things. Intent parsing, port resorce management, etc. There seems
573 // to be an issue with resource mapping
574 if (annotations.get(PORT_TYPE).equals(OdtnPortType.CLIENT.value())) {
575 log.debug("PORT {} number {} added as CLIENT port", name, portNum);
576
577 return OduCltPortHelper.oduCltPortDescription(portNum,
578 true,
579 CltSignalType.CLT_10GBE,
580 DefaultAnnotations.builder().putAll(annotations).build());
581 }
582 if (annotations.get(PORT_TYPE).equals(OdtnPortType.LINE.value())) {
583 log.debug("PORT {} number {} added as LINE port", name, portNum);
584
585 // TODO: To be configured
586 OchSignal signalId = OchSignal.newDwdmSlot(ChannelSpacing.CHL_50GHZ, 1);
587
588 return OchPortHelper.ochPortDescription(
589 portNum, true,
590 OduSignalType.ODU4, // TODO Client signal to be discovered
591 true,
592 signalId,
593 DefaultAnnotations.builder().putAll(annotations).build());
594 }
595 log.error("PORT {} number {} is of UNKNOWN type", name, portNum);
596 return null;
597 }
598}