blob: ca7fbc2791520b71fe5d0dd6db62a5eed2e5bce6 [file] [log] [blame]
hiroki9e1484d2018-12-07 09:36:49 -08001/*
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;
20
21import com.google.common.collect.ImmutableList;
22import org.apache.commons.configuration.HierarchicalConfiguration;
23import org.apache.commons.configuration.XMLConfiguration;
24import org.apache.commons.configuration.tree.xpath.XPathExpressionEngine;
25import org.onlab.packet.ChassisId;
26import org.onosproject.drivers.utilities.XmlConfigParser;
27import org.onosproject.net.DefaultAnnotations;
28import org.onosproject.net.Device;
29import org.onosproject.net.DeviceId;
30import org.onosproject.net.Port.Type;
31import org.onosproject.net.PortNumber;
32import org.onosproject.net.device.DefaultDeviceDescription;
33import org.onosproject.net.device.DefaultPortDescription;
34import org.onosproject.net.device.DefaultPortDescription.Builder;
35import org.onosproject.net.device.DeviceDescription;
36import org.onosproject.net.device.DeviceDescriptionDiscovery;
37import org.onosproject.net.device.PortDescription;
38import org.onosproject.net.driver.AbstractHandlerBehaviour;
39import org.onosproject.netconf.NetconfController;
40import org.onosproject.netconf.NetconfDevice;
41import org.onosproject.netconf.NetconfSession;
42import org.onosproject.odtn.behaviour.OdtnDeviceDescriptionDiscovery;
43import org.slf4j.Logger;
44
45import java.util.HashMap;
46import java.util.List;
47import java.util.Map;
48import java.util.Objects;
49import java.util.concurrent.CompletableFuture;
50import java.util.regex.Matcher;
51import java.util.regex.Pattern;
52import java.util.stream.Collectors;
53
54import static org.slf4j.LoggerFactory.getLogger;
55
56
57/**
58 * Driver Implementation of the DeviceDescrption discovery for OpenConfig
59 * terminal devices.
60 *
61 */
62public class CassiniTerminalDeviceDiscovery
63 extends AbstractHandlerBehaviour
64 implements OdtnDeviceDescriptionDiscovery, DeviceDescriptionDiscovery {
65
66 private static final String RPC_TAG_NETCONF_BASE =
67 "<rpc xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">";
68
69 private static final String RPC_CLOSE_TAG = "</rpc>";
70
71 private static final String OC_PLATFORM_TYPES_TRANSCEIVER =
72 "oc-platform-types:TRANSCEIVER";
73
74 private static final String OC_PLATFORM_TYPES_PORT =
75 "oc-platform-types:PORT";
76
77 private static final String OC_TRANSPORT_TYPES_OPTICAL_CHANNEL =
78 "oc-opt-types:OPTICAL_CHANNEL";
79
80 private static final Logger log = getLogger(CassiniTerminalDeviceDiscovery.class);
81
82
83 /**
84 * Returns the NetconfSession with the device for which the method was called.
85 *
86 * @param deviceId device indetifier
87 *
88 * @return The netconf session or null
89 */
90 private NetconfSession getNetconfSession(DeviceId deviceId) {
91 NetconfController controller = handler().get(NetconfController.class);
92 NetconfDevice ncdev = controller.getDevicesMap().get(deviceId);
93 if (ncdev == null) {
94 log.trace("No netconf device, returning null session");
95 return null;
96 }
97 return ncdev.getSession();
98 }
99
100
101 /**
102 * Get the deviceId for which the methods apply.
103 *
104 * @return The deviceId as contained in the handler data
105 */
106 private DeviceId did() {
107 return handler().data().deviceId();
108 }
109
110
111 /**
112 * Construct a String with a Netconf filtered get RPC Message.
113 *
114 * @param filter A valid XML tree with the filter to apply in the get
115 * @return a String containing the RPC XML Document
116 */
117 private String filteredGetBuilder(String filter) {
118 StringBuilder rpc = new StringBuilder(RPC_TAG_NETCONF_BASE);
119 rpc.append("<get>");
120 rpc.append("<filter type='subtree'>");
121 rpc.append(filter);
122 rpc.append("</filter>");
123 rpc.append("</get>");
124 rpc.append(RPC_CLOSE_TAG);
125 return rpc.toString();
126 }
127
128
129 /**
130 * Builds a request to get Device Components, config and operational data.
131 *
132 * @return A string with the Netconf RPC for a get with subtree rpcing based on
133 * /components/
134 */
135 private String getTerminalDeviceBuilder() {
136 return filteredGetBuilder("<terminal-device xmlns='http://openconfig.net/yang/terminal-device'/>");
137 }
138
139
140 @Override
141 public DeviceDescription discoverDeviceDetails() {
142 return new DefaultDeviceDescription(handler().data().deviceId().uri(),
143 Device.Type.OTN,
144 "EDGECORE",
145 "Cassini",
146 "OcNOS",
147 "",
148 new ChassisId("1"));
149 }
150
151
152
153 /**
154 * Returns a list of PortDescriptions for the device.
155 *
156 * @return a list of descriptions.
157 *
158 * The RPC reply follows the following pattern:
159 * //CHECKSTYLE:OFF
160 * <pre>{@code
161 * <?xml version="1.0" encoding="UTF-8"?>
162 * <rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="7">
163 * <data>
164 * <components xmlns="http://openconfig.net/yang/platform">
165 * <component>....
166 * </component>
167 * <component>....
168 * </component>
169 * </components>
170 * </data>
171 * </rpc-reply>
172 * }</pre>
173 * //CHECKSTYLE:ON
174 */
175 @Override
176 public List<PortDescription> discoverPortDetails() {
177 try {
178 NetconfSession session = getNetconfSession(did());
179 if (session == null) {
180 log.error("discoverPortDetails called with null session for {}", did());
181 return ImmutableList.of();
182 }
183
184 CompletableFuture<String> fut = session.rpc(getTerminalDeviceBuilder());
185 String rpcReply = fut.get();
186
187 XMLConfiguration xconf = (XMLConfiguration) XmlConfigParser.loadXmlString(rpcReply);
188 xconf.setExpressionEngine(new XPathExpressionEngine());
189
190 HierarchicalConfiguration logicalChannels = xconf.configurationAt("data/terminal-device/logical-channels");
191 return parseLogicalChannels(logicalChannels);
192 } catch (Exception e) {
193 log.error("Exception discoverPortDetails() {}", did(), e);
194 return ImmutableList.of();
195 }
196 }
197
198
199
200
201 /**
202 * Parses transceiver information from OpenConfig XML configuration.
203 *
204 * @param terminalDevice the XML document with components root.
205 * @return List of ports
206 *
207 * //CHECKSTYLE:OFF
208 * <pre>{@code
209 * <components xmlns="http://openconfig.net/yang/platform">
210 * <component>....
211 * </component>
212 * <component>....
213 * </component>
214 * </components>
215 * }</pre>
216 * //CHECKSTYLE:ON
217 */
218 protected List<PortDescription> parseLogicalChannels(HierarchicalConfiguration terminalDevice) {
219 return terminalDevice.configurationsAt("channel")
220 .stream()
221 .filter(channel -> !channel.getString("index", "unknown").equals("unknown"))
222 .map(channel -> {
223 try {
224 // Pass the root document for cross-reference
225 return parseLogicalChannel(channel);
226 } catch (Exception e) {
227 return null;
228 }
229 })
230 .filter(Objects::nonNull)
231 .collect(Collectors.toList());
232 }
233
234
235 /**
236 * Parses a component XML doc into a PortDescription.
237 *
238 * @param channel subtree to parse. It must be a component ot type PORT.
239 * case we need to check transceivers or optical channels.
240 *
241 * @return PortDescription or null if component does not have onos-index
242 */
243 private PortDescription parseLogicalChannel(
244 HierarchicalConfiguration channel) {
245
246 HierarchicalConfiguration config = channel.configurationAt("config");
247 String name = config.getString("index");
248 String portName = config.getString("description");
249 String rateClass = config.getString("rate-class");
250 log.info("Parsing Component {} type {} rate {}", name, portName, rateClass);
251
252 Map<String, String> annotations = new HashMap<>();
253 annotations.put(OdtnDeviceDescriptionDiscovery.OC_NAME, name);
254 annotations.put(OdtnDeviceDescriptionDiscovery.OC_TYPE, portName);
255
256 // Store all properties as port properties
257
258 Pattern clientPattern = Pattern.compile("ce(\\d*)/1"); // e.g. ce1/1
259 Pattern linePattern = Pattern.compile("oe(\\d*)"); // e.g. oe1
260 Matcher clientMatch = clientPattern.matcher(portName);
261 Matcher lineMatch = linePattern.matcher(portName);
262
263 Pattern portSpeedPattern = Pattern.compile("TRIB_RATE_([0-9.]*)G");
264 Matcher portSpeedMatch = portSpeedPattern.matcher(rateClass);
265
266
267 Builder builder = DefaultPortDescription.builder();
268
269 if (clientMatch.find()) {
270 Long num = Long.parseLong(clientMatch.group(1));
271 Long portNum = 100 + num;
272 String connectionId = "connection:" + num.toString();
273
274 annotations.putIfAbsent(PORT_TYPE, OdtnPortType.CLIENT.value());
275 annotations.putIfAbsent(ONOS_PORT_INDEX, portNum.toString());
276 annotations.putIfAbsent(CONNECTION_ID, connectionId);
277
278 builder.withPortNumber(PortNumber.portNumber(portNum, name));
279 builder.type(Type.PACKET);
280 } else if (lineMatch.find()) {
281 Long num = Long.parseLong(lineMatch.group(1));
282 Long portNum = 200 + num;
283 String connectionId = "connection:" + num.toString();
284
285 annotations.putIfAbsent(PORT_TYPE, OdtnPortType.LINE.value());
286 annotations.putIfAbsent(ONOS_PORT_INDEX, portNum.toString());
287 annotations.putIfAbsent(CONNECTION_ID, connectionId);
288
289 builder.withPortNumber(PortNumber.portNumber(portNum, name));
290 builder.type(Type.OCH);
291 }
292
293 if (portSpeedMatch.find()) {
294 Long speed = Long.parseLong(portSpeedMatch.group(1));
295 builder.portSpeed(speed * 1000);
296 }
297
298 builder.annotations(DefaultAnnotations.builder().putAll(annotations).build());
299 return builder.build();
300 }
301}