blob: 1ccbcad21686153261792849078425887c9976ab [file] [log] [blame]
Jan Kundrát981fe472019-10-15 22:44:19 +02001/*
2 * Copyright 2019-2020 Jan Kundrát, CESNET, <jan.kundrat@cesnet.cz> and 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.czechlight;
18
19import com.google.common.collect.Lists;
20
21import org.apache.commons.configuration.HierarchicalConfiguration;
22
23import org.onlab.packet.ChassisId;
24import org.onlab.util.Frequency;
25import org.onosproject.drivers.utilities.XmlConfigParser;
26import org.onosproject.net.ChannelSpacing;
27import org.onosproject.net.DefaultAnnotations;
28import org.onosproject.net.DeviceId;
29import org.onosproject.net.Device;
30import org.onosproject.net.PortNumber;
31import org.onosproject.net.AnnotationKeys;
32import org.onosproject.net.device.DefaultDeviceDescription;
33import org.onosproject.net.device.DeviceDescription;
34import org.onosproject.net.device.DeviceDescriptionDiscovery;
35import org.onosproject.net.device.DeviceService;
36import org.onosproject.net.device.PortDescription;
37import org.onosproject.net.driver.AbstractHandlerBehaviour;
38import org.onosproject.netconf.DatastoreId;
39import org.onosproject.netconf.NetconfController;
40import org.onosproject.netconf.NetconfException;
41import org.onosproject.netconf.NetconfSession;
42
43import org.onosproject.odtn.behaviour.OdtnDeviceDescriptionDiscovery;
44import org.slf4j.Logger;
45
46import java.util.Arrays;
47import java.util.List;
48
49import static com.google.common.base.Preconditions.checkNotNull;
50import static org.onosproject.net.optical.device.OmsPortHelper.omsPortDescription;
51import static org.slf4j.LoggerFactory.getLogger;
52
53/**
54 * Device description behaviour for CzechLight SDN ROADM devices using NETCONF.
55 */
56public class CzechLightDiscovery
57 extends AbstractHandlerBehaviour implements DeviceDescriptionDiscovery {
58
59 public enum DeviceType {
60 LINE_DEGREE,
61 ADD_DROP_FLEX,
62 COHERENT_ADD_DROP,
63 INLINE_AMP,
64 };
65 public static final String DEVICE_TYPE_ANNOTATION = "czechlight.model";
66
67 public static final int PORT_COMMON = 100;
68 public static final int PORT_INLINE_WEST = PORT_COMMON + 1;
69 public static final int PORT_INLINE_EAST = PORT_INLINE_WEST + 1;
70 public static final ChannelSpacing CHANNEL_SPACING_50 = ChannelSpacing.CHL_50GHZ;
71 public static final ChannelSpacing CHANNEL_SPACING_NONE = ChannelSpacing.CHL_0GHZ;
72 public static final Frequency START_CENTER_FREQ_50 = Frequency.ofGHz(191_350);
73 public static final Frequency END_CENTER_FREQ_50 = Frequency.ofGHz(196_100);
74
75 private static final String MOD_ROADM_DEVICE = "czechlight-roadm-device";
76 private static final String MOD_ROADM_DEVICE_DATE = "2019-09-30";
77 private static final String MOD_ROADM_FEATURE_LINE_DEGREE = "hw-line-9";
78 private static final String MOD_ROADM_FEATURE_FLEX_ADD_DROP = "hw-add-drop-20";
79 private static final String MOD_COHERENT_A_D = "czechlight-coherent-add-drop";
80 private static final String MOD_COHERENT_A_D_DATE = "2019-09-30";
81 private static final String MOD_INLINE_AMP = "czechlight-inline-amp";
82 private static final String MOD_INLINE_AMP_DATE = "2019-09-30";
83 private static final String NS_CZECHLIGHT_PREFIX = "http://czechlight.cesnet.cz/yang/";
84 public static final String NS_CZECHLIGHT_ROADM_DEVICE = NS_CZECHLIGHT_PREFIX + MOD_ROADM_DEVICE;
85 public static final String NS_CZECHLIGHT_COHERENT_A_D = NS_CZECHLIGHT_PREFIX + MOD_COHERENT_A_D;
86 public static final String NS_CZECHLIGHT_INLINE_AMP = NS_CZECHLIGHT_PREFIX + MOD_INLINE_AMP;
87
88 private static final String YANGLIB_KEY_REVISION = "data.modules-state.module.revision";
89 private static final String YANGLIB_KEY_MODULE_NAME = "data.modules-state.module.name";
90 private static final String YANGLIB_XMLNS = "urn:ietf:params:xml:ns:yang:ietf-yang-library";
91 private static final String YANGLIB_XML_PREFIX = "yanglib";
92 private static final String YANGLIB_XPATH_FILTER = "/" + YANGLIB_XML_PREFIX + ":modules-state/module[(name='"
93 + MOD_ROADM_DEVICE + "') or (name='" + MOD_COHERENT_A_D + "') or (name='" + MOD_INLINE_AMP + "')]";
94 private static final String YANGLIB_PATH_QUERY_FEATURES = "data.modules-state.module.feature";
95
96 public static final String CHANNEL_DEFS_FILTER =
97 "<channel-plan xmlns=\"http://czechlight.cesnet.cz/yang/czechlight-roadm-device\">" +
98 "<channel><lower-frequency/><upper-frequency/></channel>" +
99 "</channel-plan>";
100 private static final String UNIDIR_CFG_SUBSTR = "<port/><attenuation/><power/>";
101 public static final String XML_MC_OPEN = "<media-channels " +
102 "xmlns=\"http://czechlight.cesnet.cz/yang/czechlight-roadm-device\">";
103 public static final String XML_MC_CLOSE = "</media-channels>";
104 public static final String MC_ROUTING_FILTER =
105 XML_MC_OPEN +
106 "<add>" + UNIDIR_CFG_SUBSTR + "</add>" +
107 "<drop>" + UNIDIR_CFG_SUBSTR + "</drop>" +
108 XML_MC_CLOSE;
109
110 public static final String LINE_EXPRESS_PREFIX = "E";
111
112 private static final String DESC_PORT_LINE_WEST = "Line West";
113 private static final String DESC_PORT_LINE_EAST = "Line East";
114 private static final String DESC_PORT_LINE = "Line";
115 private static final String DESC_PORT_EXPRESS = "Express";
116
117
118 private static final Logger log = getLogger(CzechLightDiscovery.class);
119
120 @Override
121 public DeviceDescription discoverDeviceDetails() {
122 NetconfSession session = getNetconfSession();
123 if (session == null) {
124 log.error("Cannot request NETCONF session for {}", data().deviceId());
125 return null;
126 }
127
128 DefaultAnnotations.Builder annotations = DefaultAnnotations.builder();
129 final var noDevice = new DefaultDeviceDescription(handler().data().deviceId().uri(), Device.Type.OTHER,
130 null, null, null, null, null, annotations.build());
131
132 try {
133 Boolean isLineDegree = false, isAddDrop = false, isCoherentAddDrop = false, isInlineAmp = false;
134 var data = doGetXPath(getNetconfSession(), YANGLIB_XML_PREFIX, YANGLIB_XMLNS, YANGLIB_XPATH_FILTER);
135 if (!data.containsKey(YANGLIB_KEY_REVISION)) {
136 log.error("Not talking to a supported CzechLight device, is that a teapot?");
137 return noDevice;
138 }
139 final var revision = data.getString(YANGLIB_KEY_REVISION);
140 if (data.getString(YANGLIB_KEY_MODULE_NAME).equals(MOD_ROADM_DEVICE)) {
141 if (!revision.equals(MOD_ROADM_DEVICE_DATE)) {
142 log.error("Revision mismatch for YANG module {}: got {}", MOD_ROADM_DEVICE, revision);
143 return noDevice;
144 }
145 final var features = data.getStringArray(YANGLIB_PATH_QUERY_FEATURES);
146 isLineDegree = Arrays.stream(features)
147 .anyMatch(s -> s.equals(MOD_ROADM_FEATURE_LINE_DEGREE));
148 isAddDrop = Arrays.stream(features)
149 .anyMatch(s -> s.equals(MOD_ROADM_FEATURE_FLEX_ADD_DROP));
150 if (!isLineDegree && !isAddDrop) {
151 log.error("Device type not recognized, but {} YANG model is present. Reported YANG features: {}",
152 MOD_ROADM_DEVICE, String.join(", ", features));
153 return noDevice;
154 }
155 } else if (data.getString(YANGLIB_KEY_MODULE_NAME).equals(MOD_COHERENT_A_D)) {
156 if (!revision.equals(MOD_COHERENT_A_D_DATE)) {
157 log.error("Revision mismatch for YANG module {}: got {}", MOD_COHERENT_A_D, revision);
158 return noDevice;
159 }
160 isCoherentAddDrop = true;
161 } else if (data.getString(YANGLIB_KEY_MODULE_NAME).equals(MOD_INLINE_AMP)) {
162 if (!revision.equals(MOD_INLINE_AMP_DATE)) {
163 log.error("Revision mismatch for YANG module {}: got {}", MOD_INLINE_AMP, revision);
164 return noDevice;
165 }
166 isInlineAmp = true;
167 }
168
169 if (isLineDegree) {
170 log.info("Talking to a Line/Degree ROADM node");
171 annotations.set(DEVICE_TYPE_ANNOTATION, DeviceType.LINE_DEGREE.toString());
172 } else if (isAddDrop) {
173 log.info("Talking to an Add/Drop ROADM node");
174 annotations.set(DEVICE_TYPE_ANNOTATION, DeviceType.ADD_DROP_FLEX.toString());
175 } else if (isCoherentAddDrop) {
176 log.info("Talking to a Coherent Add/Drop ROADM node");
177 annotations.set(DEVICE_TYPE_ANNOTATION, DeviceType.COHERENT_ADD_DROP.toString());
178 } else if (isInlineAmp) {
179 log.info("Talking to an inline ampifier, not a ROADM, but we will fake it as a ROADM for now");
180 annotations.set(DEVICE_TYPE_ANNOTATION, DeviceType.INLINE_AMP.toString());
181 } else {
182 log.error("Device type not recognized");
183 return noDevice;
184 }
185 } catch (NetconfException e) {
186 log.error("Cannot request ietf-yang-library data", e);
187 return noDevice;
188 }
189
190 // FIXME: initialize these
191 String vendor = "CzechLight";
192 String hwVersion = "n/a";
193 String swVersion = "n/a";
194 String serialNumber = "n/a";
195 ChassisId chassisId = null;
196
197 return new DefaultDeviceDescription(handler().data().deviceId().uri(), Device.Type.ROADM,
198 vendor, hwVersion, swVersion, serialNumber, chassisId, annotations.build());
199 }
200
201 public static String leafPortName(final DeviceType deviceType, final long number) {
202 switch (deviceType) {
203 case LINE_DEGREE:
204 return LINE_EXPRESS_PREFIX + Long.toString(number);
205 default:
206 return Long.toString(number);
207 }
208 }
209
210 @Override
211 public List<PortDescription> discoverPortDetails() {
212 DeviceId deviceId = handler().data().deviceId();
213 DeviceService deviceService = checkNotNull(handler().get(DeviceService.class));
214 Device device = deviceService.getDevice(deviceId);
215 var deviceType = DeviceType.valueOf(device.annotations().value(DEVICE_TYPE_ANNOTATION));
216
217 List<PortDescription> portDescriptions = Lists.newArrayList();
218
219 if (deviceType == DeviceType.INLINE_AMP) {
220 DefaultAnnotations.Builder annotations = DefaultAnnotations.builder();
221 annotations.set(AnnotationKeys.PORT_NAME, DESC_PORT_LINE_WEST);
222 annotations.set(OdtnDeviceDescriptionDiscovery.PORT_TYPE,
223 OdtnDeviceDescriptionDiscovery.OdtnPortType.LINE.toString());
224 portDescriptions.add(omsPortDescription(PortNumber.portNumber(PORT_INLINE_WEST),
225 true,
226 START_CENTER_FREQ_50,
227 END_CENTER_FREQ_50,
228 CHANNEL_SPACING_50.frequency(),
229 annotations.build()));
230
231 annotations = DefaultAnnotations.builder();
232 annotations.set(AnnotationKeys.PORT_NAME, DESC_PORT_LINE_EAST);
233 annotations.set(OdtnDeviceDescriptionDiscovery.PORT_TYPE,
234 OdtnDeviceDescriptionDiscovery.OdtnPortType.LINE.toString());
235 portDescriptions.add(omsPortDescription(PortNumber.portNumber(PORT_INLINE_EAST),
236 true,
237 START_CENTER_FREQ_50,
238 END_CENTER_FREQ_50,
239 CHANNEL_SPACING_50.frequency(),
240 annotations.build()));
241
242 return portDescriptions;
243 }
244
245 DefaultAnnotations.Builder annotationsForCommon = DefaultAnnotations.builder();
246 switch (deviceType) {
247 case LINE_DEGREE:
248 annotationsForCommon.set(AnnotationKeys.PORT_NAME, DESC_PORT_LINE);
249 annotationsForCommon.set(OdtnDeviceDescriptionDiscovery.PORT_TYPE,
250 OdtnDeviceDescriptionDiscovery.OdtnPortType.LINE.toString());
251 break;
252 case ADD_DROP_FLEX:
253 case COHERENT_ADD_DROP:
254 annotationsForCommon.set(AnnotationKeys.PORT_NAME, DESC_PORT_EXPRESS);
255 break;
256 case INLINE_AMP:
257 assert false : "this cannot happen because it's handled above, but I have to type this here anyway";
258 default:
259 assert false : "unhandled device type";
260 }
261 portDescriptions.add(omsPortDescription(PortNumber.portNumber(PORT_COMMON),
262 true,
263 START_CENTER_FREQ_50,
264 END_CENTER_FREQ_50,
265 CHANNEL_SPACING_50.frequency(),
266 annotationsForCommon.build()));
267
268 final int leafPortCount;
269 switch (deviceType) {
270 case LINE_DEGREE:
271 leafPortCount = 9;
272 break;
273 case ADD_DROP_FLEX:
274 leafPortCount = 20;
275 break;
276 case COHERENT_ADD_DROP:
277 leafPortCount = 8;
278 break;
279 default:
280 log.error("Unsupported CzechLight device type");
281 return null;
282 }
283
284 for (var i = 1; i <= leafPortCount; ++i) {
285 DefaultAnnotations.Builder annotations = DefaultAnnotations.builder();
286 final Frequency channelSpacing;
287 annotations.set(AnnotationKeys.PORT_NAME, leafPortName(deviceType, i));
288 switch (deviceType) {
289 case LINE_DEGREE:
290 channelSpacing = CHANNEL_SPACING_50.frequency();
291 break;
292 case ADD_DROP_FLEX:
293 annotations.set(OdtnDeviceDescriptionDiscovery.PORT_TYPE,
294 OdtnDeviceDescriptionDiscovery.OdtnPortType.CLIENT.toString());
295 channelSpacing = CHANNEL_SPACING_50.frequency();
296 break;
297 case COHERENT_ADD_DROP:
298 annotations.set(OdtnDeviceDescriptionDiscovery.PORT_TYPE,
299 OdtnDeviceDescriptionDiscovery.OdtnPortType.CLIENT.toString());
300 channelSpacing = CHANNEL_SPACING_NONE.frequency();
301 break;
302 default:
303 log.error("Unsupported CzechLight device type");
304 return null;
305 }
306 portDescriptions.add(omsPortDescription(PortNumber.portNumber(i),
307 true,
308 START_CENTER_FREQ_50,
309 END_CENTER_FREQ_50,
310 channelSpacing,
311 annotations.build()));
312 }
313 return portDescriptions;
314 }
315
316 private NetconfSession getNetconfSession() {
317 NetconfController controller =
318 checkNotNull(handler().get(NetconfController.class));
319 return controller.getNetconfDevice(data().deviceId()).getSession();
320 }
321
322 public static double dbmToMilliwatts(final double dbm) {
323 return java.lang.Math.pow(10, dbm / 10);
324 }
325
326 public static double milliwattsToDbm(final double mw) {
327 return 10 * java.lang.Math.log10(mw);
328 }
329
330 /** Run a <get> NETCONF command with an XPath filter.
331 *
332 * @param session well, a NETCONF session
333 * @param prefix Name of a XML element prefix to use. Can be meaningless, such as "M"
334 * @param namespace Full URI of the XML namespace to use. This is the real meat.
335 * @param xpathFilter String with a relative XPath filter. This *MUST* start with "/" followed by 'prefix' and ":".
336 * @return Result of the <get/> operation via NETCONF as a XmlHierarchicalConfiguration
337 * @throws NetconfException exactly as session.doWrappedRpc() would do.
338 * */
339 public static HierarchicalConfiguration doGetXPath(final NetconfSession session, final String prefix,
340 final String namespace, final String xpathFilter)
341 throws NetconfException {
342 final var reply = session.doWrappedRpc("<get xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">"
343 + "<filter type=\"xpath\" xmlns:" + prefix + "=\"" + namespace + "\""
344 + " select=\"" + xpathFilter.replace("\"", "&quot;") + "\"/>"
345 + "</get>");
346 log.debug("GET RPC w/XPath {}", reply);
347 var data = XmlConfigParser.loadXmlString(reply);
348 if (!data.containsKey("data[@xmlns]")) {
349 log.error("NETCONF <get> w/XPath returned error: {}", reply);
350 return null;
351 }
352 return data;
353 }
354
355 public static HierarchicalConfiguration doGetSubtree(final NetconfSession session, final String subtreeXml)
356 throws NetconfException {
357 final var data = XmlConfigParser.loadXmlString(session.getConfig(DatastoreId.RUNNING, subtreeXml));
358 if (!data.containsKey("data[@xmlns]")) {
359 log.error("NETCONF <get> w/subtree returned error");
360 return null;
361 }
362 return data;
363 }
364
365 /** Massage an XPath fragment (without the "/module:" prefix) into a key suitable for XmlConfigParser.getString()
366 *
367 * This might or might not work properly for various corner cases. It will fail horribly when XML namespaces are not
368 * being done in exactly the same manner as the author of this code assumed was the case.
369 *
370 * @param xpath XPath subset without the "/module:" prefix, such as "container/another-container/leaf"
371 * @return a string to be passed to XmlConfigParser.getString(...) of the XmlHierarchicalConfiguration
372 * */
373 public static String xpathToXmlKey(final String xpath) {
374 return "data." // prefix added by RPC handling
375 // turn XPath level delimiters into XmlConfigParser's key format
376 + xpath.replace('/', '.')
377 // filter out XPath list keys/selectors, they are not visible in XmlConfigParser
378 .replaceAll("\\[[^]]*\\]", "");
379 }
380
381}