blob: e76df4e91d2f7e9ec5edd663dc41eca84e92ebdf [file] [log] [blame]
harjak47ff0db2019-10-08 10:30:47 +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 */
18
19package org.onosproject.drivers.odtn;
20
21import com.google.common.collect.Range;
22import org.apache.commons.configuration.ConfigurationException;
23import org.apache.commons.configuration.HierarchicalConfiguration;
24import org.apache.commons.configuration.XMLConfiguration;
25import org.apache.commons.configuration.tree.xpath.XPathExpressionEngine;
26import org.onlab.osgi.DefaultServiceDirectory;
27import org.onosproject.drivers.utilities.XmlConfigParser;
28import org.onosproject.net.DeviceId;
29import org.onosproject.net.PortNumber;
30import org.onosproject.net.behaviour.PowerConfig;
31import org.onosproject.net.device.DeviceService;
32import org.onosproject.net.driver.AbstractHandlerBehaviour;
33import org.onosproject.netconf.DatastoreId;
34import org.onosproject.netconf.NetconfController;
35import org.onosproject.netconf.NetconfDevice;
36import org.onosproject.netconf.NetconfException;
37import org.onosproject.netconf.NetconfSession;
38import org.slf4j.Logger;
39
40import java.io.StringWriter;
41import java.util.ArrayList;
42import java.util.List;
43import java.util.Optional;
44import java.util.concurrent.CompletableFuture;
45import java.util.concurrent.ExecutionException;
46
47import static com.google.common.base.Preconditions.checkNotNull;
48import static org.slf4j.LoggerFactory.getLogger;
49
50/**
51 * Driver Implementation of the PowerConfig for OpenConfig terminal devices.
52 */
53public class AdvaTerminalDevicePowerConfig<T>
54 extends AbstractHandlerBehaviour implements PowerConfig<T> {
55
56 private static final String RPC_TAG_NETCONF_BASE =
57 "<rpc xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">";
58
59 private static final String RPC_CLOSE_TAG = "</rpc>";
60
61 private static final Logger log = getLogger(AdvaTerminalDevicePowerConfig.class);
62
63 private ComponentType state = ComponentType.DIRECTION;
64
65 /**
66 * Returns the NetconfSession with the device for which the method was called.
67 *
68 * @param deviceId device indetifier
69 * @return The netconf session or null
70 */
71 private NetconfSession getNetconfSession(DeviceId deviceId) {
72 NetconfController controller = handler().get(NetconfController.class);
73 NetconfDevice ncdev = controller.getDevicesMap().get(deviceId);
74 if (ncdev == null) {
75 log.trace("No netconf device, returning null session");
76 return null;
77 }
78 return ncdev.getSession();
79 }
80
81 /**
82 * Get the deviceId for which the methods apply.
83 *
84 * @return The deviceId as contained in the handler data
85 */
86 private DeviceId did() {
87 return handler().data().deviceId();
88 }
89
90 /**
91 * Execute RPC request.
92 *
93 * @param session Netconf session
94 * @param message Netconf message in XML format
95 * @return XMLConfiguration object
96 */
97
98 private XMLConfiguration executeRpc(NetconfSession session, String message) {
99 try {
100 if (log.isDebugEnabled()) {
101 try {
102 StringWriter stringWriter = new StringWriter();
103 XMLConfiguration xconf = (XMLConfiguration) XmlConfigParser.loadXmlString(message);
104 xconf.setExpressionEngine(new XPathExpressionEngine());
105 xconf.save(stringWriter);
106 log.debug("Request {}", stringWriter.toString());
107 } catch (ConfigurationException e) {
108 log.error("XML Config Exception ", e);
109 }
110 }
111 CompletableFuture<String> fut = session.rpc(message);
112 String rpcReply = fut.get();
113 XMLConfiguration xconf = (XMLConfiguration) XmlConfigParser.loadXmlString(rpcReply);
114 xconf.setExpressionEngine(new XPathExpressionEngine());
115 if (log.isDebugEnabled()) {
116 try {
117 StringWriter stringWriter = new StringWriter();
118 xconf.save(stringWriter);
119 log.debug("Response {}", stringWriter.toString());
120 } catch (ConfigurationException e) {
121 log.error("XML Config Exception ", e);
122 }
123 }
124 return xconf;
125 } catch (NetconfException ne) {
126 log.error("Exception on Netconf protocol: {}.", ne);
127 } catch (InterruptedException ie) {
128 log.error("Interrupted Exception: {}.", ie);
129 } catch (ExecutionException ee) {
130 log.error("Concurrent Exception while executing Netconf operation: {}.", ee);
131 }
132 return null;
133 }
134
135 /**
136 * Get the target-output-power value on specific optical-channel.
137 *
138 * @param port the port
139 * @param component the port component. It should be 'oc-name' in the Annotations of Port.
140 * 'oc-name' could be mapped to '/component/name' in openconfig yang.
141 * @return target power value
142 */
143 @Override
144 public Optional<Double> getTargetPower(PortNumber port, T component) {
145 checkState(component);
146 return state.getTargetPower(port, component);
147 }
148
149 @Override
150 public void setTargetPower(PortNumber port, T component, double power) {
151 checkState(component);
152 state.setTargetPower(port, component, power);
153 }
154
155 @Override
156 public Optional<Double> currentPower(PortNumber port, T component) {
157 checkState(component);
158 return state.currentPower(port, component);
159 }
160
161 @Override
162 public Optional<Double> currentInputPower(PortNumber port, T component) {
163 checkState(component);
164 return state.currentInputPower(port, component);
165 }
166
167 @Override
168 public Optional<Range<Double>> getTargetPowerRange(PortNumber port, T component) {
169 checkState(component);
170 return state.getTargetPowerRange(port, component);
171 }
172
173 @Override
174 public Optional<Range<Double>> getInputPowerRange(PortNumber port, T component) {
175 checkState(component);
176 return state.getInputPowerRange(port, component);
177 }
178
179 @Override
180 public List<PortNumber> getPorts(T component) {
181 checkState(component);
182 return state.getPorts(component);
183 }
184
185
186 /**
187 * Set the ComponentType to invoke proper methods for different template T.
188 *
189 * @param component the component.
190 */
191 void checkState(Object component) {
192 String clsName = component.getClass().getName();
193 switch (clsName) {
194 case "org.onosproject.net.Direction":
195 state = ComponentType.DIRECTION;
196 break;
197 case "org.onosproject.net.OchSignal":
198 state = ComponentType.OCHSIGNAL;
199 break;
200 default:
201 log.error("Cannot parse the component type {}.", clsName);
202 log.info("The component content is {}.", component.toString());
203 }
204 state.adva = this;
205 }
206
207 /**
208 * Component type.
209 */
210 enum ComponentType {
211
212 /**
213 * Direction.
214 */
215 DIRECTION() {
216 @Override
217 public Optional<Double> getTargetPower(PortNumber port, Object component) {
218 return super.getTargetPower(port, component);
219 }
220
221 @Override
222 public void setTargetPower(PortNumber port, Object component, double power) {
223 super.setTargetPower(port, component, power);
224 }
225 },
226
227 /**
228 * OchSignal.
229 */
230 OCHSIGNAL() {
231 @Override
232 public Optional<Double> getTargetPower(PortNumber port, Object component) {
233 return super.getTargetPower(port, component);
234 }
235
236 @Override
237 public void setTargetPower(PortNumber port, Object component, double power) {
238 super.setTargetPower(port, component, power);
239 }
240 };
241
242
243 AdvaTerminalDevicePowerConfig adva;
244
245 /**
246 * mirror method in the internal class.
247 *
248 * @param port port
249 * @param component component
250 * @return target power
251 */
252 Optional<Double> getTargetPower(PortNumber port, Object component) {
253 NetconfSession session = adva.getNetconfSession(adva.did());
254 checkNotNull(session);
255 String filter = parsePort(adva, port, null, null);
256 StringBuilder rpcReq = new StringBuilder();
257 rpcReq.append(RPC_TAG_NETCONF_BASE)
258 .append("<get-config>")
259 .append("<source>")
260 .append("<" + DatastoreId.RUNNING + "/>")
261 .append("</source>")
262 .append("<filter type='subtree'>")
263 .append(filter)
264 .append("</filter>")
265 .append("</get-config>")
266 .append(RPC_CLOSE_TAG);
267 XMLConfiguration xconf = adva.executeRpc(session, rpcReq.toString());
268 if (xconf == null) {
269 log.error("Error in executingRpc");
270 return Optional.empty();
271 }
272 try {
273 HierarchicalConfiguration config =
274 xconf.configurationAt("data/components/component/optical-channel/config");
275 if (config == null || config.getString("target-output-power") == null) {
276 return Optional.empty();
277 }
278 double power = Float.valueOf(config.getString("target-output-power")).doubleValue();
279 return Optional.of(power);
280 } catch (IllegalArgumentException e) {
281 return Optional.empty();
282 }
283 }
284
285 /**
286 * mirror method in the internal class.
287 *
288 * @param port port
289 * @param component component
290 * @param power target value
291 */
292 void setTargetPower(PortNumber port, Object component, double power) {
293 NetconfSession session = adva.getNetconfSession(adva.did());
294 checkNotNull(session);
295 String editConfig = parsePort(adva, port, null, power);
296 StringBuilder rpcReq = new StringBuilder();
297 rpcReq.append(RPC_TAG_NETCONF_BASE)
298 .append("<edit-config>")
299 .append("<target><" + DatastoreId.CANDIDATE + "/></target>")
300 .append("<config>")
301 .append(editConfig)
302 .append("</config>")
303 .append("</edit-config>")
304 .append(RPC_CLOSE_TAG);
305 log.info("Setting power {}", rpcReq.toString());
306 XMLConfiguration xconf = adva.executeRpc(session, rpcReq.toString());
307 // The successful reply should be "<rpc-reply ...><ok /></rpc-reply>"
308 if (!xconf.getRoot().getChild(0).getName().equals("ok")) {
309 log.error("The <edit-config> operation to set target-output-power of Port({}:{}) is failed.",
310 port.toString(), component.toString());
311 }
312 try {
313 session.commit();
314 } catch (NetconfException e) {
315 log.error("error committing channel power", e);
316 }
317 }
318
319 /**
320 * mirror method in the internal class.
321 *
322 * @param port port
323 * @param component the component.
324 * @return current output power.
325 */
326 Optional<Double> currentPower(PortNumber port, Object component) {
327 XMLConfiguration xconf = getOpticalChannelState(
328 adva, port, "<output-power><instant/></output-power>");
329 try {
330 HierarchicalConfiguration config =
331 xconf.configurationAt("data/components/component/optical-channel/state/output-power");
332 if (config == null || config.getString("instant") == null) {
333 return Optional.empty();
334 }
335 double currentPower = Float.valueOf(config.getString("instant")).doubleValue();
336 return Optional.of(currentPower);
337 } catch (IllegalArgumentException e) {
338 return Optional.empty();
339 }
340 }
341
342 /**
343 * mirror method in the internal class.
344 *
345 * @param port port
346 * @param component the component
347 * @return current input power
348 */
349 Optional<Double> currentInputPower(PortNumber port, Object component) {
350 XMLConfiguration xconf = getOpticalChannelState(
351 adva, port, "<input-power><instant/></input-power>");
352 try {
353 HierarchicalConfiguration config =
354 xconf.configurationAt("data/components/component/optical-channel/state/input-power");
355 if (config == null || config.getString("instant") == null) {
356 return Optional.empty();
357 }
358 double currentPower = Float.valueOf(config.getString("instant")).doubleValue();
359 return Optional.of(currentPower);
360 } catch (IllegalArgumentException e) {
361 return Optional.empty();
362 }
363 }
364
365 Optional<Range<Double>> getTargetPowerRange(PortNumber port, Object component) {
366 double targetMin = -30;
367 double targetMax = 1;
368 return Optional.of(Range.open(targetMin, targetMax));
369 }
370
371 Optional<Range<Double>> getInputPowerRange(PortNumber port, Object component) {
372 double targetMin = -30;
373 double targetMax = 1;
374 return Optional.of(Range.open(targetMin, targetMax));
375 }
376
377 List<PortNumber> getPorts(Object component) {
378 // FIXME
379 log.warn("Not Implemented Yet!");
380 return new ArrayList<PortNumber>();
381 }
382
383 /**
384 * Get filtered content under <optical-channel><state>.
385 *
386 * @param pc power config instance
387 * @param port the port number
388 * @param underState the filter condition
389 * @return RPC reply
390 */
391 private static XMLConfiguration getOpticalChannelState(AdvaTerminalDevicePowerConfig pc,
392 PortNumber port, String underState) {
393 NetconfSession session = pc.getNetconfSession(pc.did());
394 checkNotNull(session);
395 String name = ocName(pc, port);
396 StringBuilder rpcReq = new StringBuilder(RPC_TAG_NETCONF_BASE);
397 rpcReq.append("<get><filter><components xmlns=\"http://openconfig.net/yang/platform\"><component>")
398 .append("<name>").append(name).append("</name>")
399 .append("<optical-channel xmlns=\"http://openconfig.net/yang/terminal-device\">")
400 .append("<state>")
401 .append(underState)
402 .append("</state></optical-channel></component></components></filter></get>")
403 .append(RPC_CLOSE_TAG);
404 XMLConfiguration xconf = pc.executeRpc(session, rpcReq.toString());
405 return xconf;
406 }
407
408
409 /**
410 * Extract component name from portNumber's annotations.
411 *
412 * @param pc power config instance
413 * @param portNumber the port number
414 * @return the component name
415 */
416 private static String ocName(AdvaTerminalDevicePowerConfig pc, PortNumber portNumber) {
417 DeviceService deviceService = DefaultServiceDirectory.getService(DeviceService.class);
418 DeviceId deviceId = pc.handler().data().deviceId();
419 return deviceService.getPort(deviceId, portNumber).annotations().value("oc-name");
420 }
421
422
423 /**
424 * Parse filtering string from port and component.
425 *
426 * @param portNumber Port Number
427 * @param component port component (optical-channel)
428 * @param power power value set.
429 * @return filtering string in xml format
430 */
431 private static String parsePort(AdvaTerminalDevicePowerConfig pc, PortNumber portNumber,
432 Object component, Double power) {
433 if (component == null) {
434 String name = ocName(pc, portNumber);
435 StringBuilder sb = new StringBuilder("<components xmlns=\"http://openconfig.net/yang/platform\">");
436 sb.append("<component>");
437 if (power != null) {
438 // This is an edit-config operation.
439 sb.append("<config>")
440 .append("<name>")
441 .append(name)
442 .append("</name>")
443 .append("</config>")
444 .append("<optical-channel xmlns=\"http://openconfig.net/yang/terminal-device\">")
445 .append("<config>")
446 .append("<target-output-power>")
447 .append(power)
448 .append("</target-output-power>")
449 .append("</config>")
450 .append("</optical-channel>");
451 } else {
452 sb.append("<name>")
453 .append(name)
454 .append("</name>");
455 }
456
457 sb.append("</component>").append("</components>");
458 return sb.toString();
459 } else {
460 log.error("Cannot process the component {}.", component.getClass());
461 return null;
462 }
463 }
464 }
465}