blob: f0f5d3ae27d492bf315e21f4e035889e946d6d3d [file] [log] [blame]
Boyuan Yana00e3d02019-04-10 23:43:12 -07001/*
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;
Andrea Campanella3ccee482019-08-02 19:15:18 +020022import org.apache.commons.configuration.ConfigurationException;
Boyuan Yana00e3d02019-04-10 23:43:12 -070023import 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;
Boyuan Yana00e3d02019-04-10 23:43:12 -070029import org.onosproject.net.PortNumber;
30import org.onosproject.net.behaviour.PowerConfig;
31import org.onosproject.net.device.DeviceService;
32import org.onosproject.net.driver.AbstractHandlerBehaviour;
Andrea Campanella3a361452019-08-02 10:17:53 +020033import org.onosproject.netconf.DatastoreId;
Boyuan Yana00e3d02019-04-10 23:43:12 -070034import org.onosproject.netconf.NetconfController;
35import org.onosproject.netconf.NetconfDevice;
36import org.onosproject.netconf.NetconfException;
37import org.onosproject.netconf.NetconfSession;
38import org.slf4j.Logger;
39
Andrea Campanella3ccee482019-08-02 19:15:18 +020040import java.io.StringWriter;
Boyuan Yana00e3d02019-04-10 23:43:12 -070041import java.util.ArrayList;
42import java.util.List;
43import java.util.Optional;
44import java.util.concurrent.CompletableFuture;
45import java.util.concurrent.ExecutionException;
46
Boyuan Yan7bee2482019-05-22 17:15:53 -070047import static com.google.common.base.Preconditions.checkNotNull;
Boyuan Yana00e3d02019-04-10 23:43:12 -070048import static org.slf4j.LoggerFactory.getLogger;
49
50/**
51 * Driver Implementation of the PowerConfig for OpenConfig terminal devices.
Boyuan Yana00e3d02019-04-10 23:43:12 -070052 */
Boyuan Yan7bee2482019-05-22 17:15:53 -070053public class CassiniTerminalDevicePowerConfig<T>
54 extends AbstractHandlerBehaviour implements PowerConfig<T> {
Boyuan Yana00e3d02019-04-10 23:43:12 -070055
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
Boyuan Yana00e3d02019-04-10 23:43:12 -070061 private static final Logger log = getLogger(CassiniTerminalDevicePowerConfig.class);
62
Boyuan Yan7bee2482019-05-22 17:15:53 -070063 private ComponentType state = ComponentType.DIRECTION;
64
Boyuan Yana00e3d02019-04-10 23:43:12 -070065 /**
66 * Returns the NetconfSession with the device for which the method was called.
67 *
68 * @param deviceId device indetifier
Boyuan Yana00e3d02019-04-10 23:43:12 -070069 * @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 /**
Boyuan Yana00e3d02019-04-10 23:43:12 -070091 * Execute RPC request.
Andrea Campanella3ccee482019-08-02 19:15:18 +020092 *
Boyuan Yana00e3d02019-04-10 23:43:12 -070093 * @param session Netconf session
94 * @param message Netconf message in XML format
95 * @return XMLConfiguration object
96 */
Andrea Campanella7ebfe322019-08-29 11:46:57 -070097
Boyuan Yana00e3d02019-04-10 23:43:12 -070098 private XMLConfiguration executeRpc(NetconfSession session, String message) {
99 try {
Andrea Campanella7ebfe322019-08-29 11:46:57 -0700100 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 }
Boyuan Yana00e3d02019-04-10 23:43:12 -0700111 CompletableFuture<String> fut = session.rpc(message);
112 String rpcReply = fut.get();
113 XMLConfiguration xconf = (XMLConfiguration) XmlConfigParser.loadXmlString(rpcReply);
114 xconf.setExpressionEngine(new XPathExpressionEngine());
Andrea Campanella7ebfe322019-08-29 11:46:57 -0700115 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 }
Boyuan Yana00e3d02019-04-10 23:43:12 -0700124 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.
Andrea Campanella3ccee482019-08-02 19:15:18 +0200137 *
138 * @param port the port
Boyuan Yana00e3d02019-04-10 23:43:12 -0700139 * @param component the port component. It should be 'oc-name' in the Annotations of Port.
Boyuan Yan6b23b382019-06-04 11:59:35 -0700140 * 'oc-name' could be mapped to '/component/name' in openconfig yang.
Boyuan Yana00e3d02019-04-10 23:43:12 -0700141 * @return target power value
142 */
143 @Override
Andrea Campanelladadf6402019-08-07 15:24:11 +0200144 public Optional<Double> getTargetPower(PortNumber port, T component) {
Boyuan Yan7bee2482019-05-22 17:15:53 -0700145 checkState(component);
146 return state.getTargetPower(port, component);
147 }
148
149 @Override
Andrea Campanelladadf6402019-08-07 15:24:11 +0200150 public void setTargetPower(PortNumber port, T component, double power) {
Boyuan Yan7bee2482019-05-22 17:15:53 -0700151 checkState(component);
152 state.setTargetPower(port, component, power);
153 }
154
155 @Override
Andrea Campanelladadf6402019-08-07 15:24:11 +0200156 public Optional<Double> currentPower(PortNumber port, T component) {
Boyuan Yan7bee2482019-05-22 17:15:53 -0700157 checkState(component);
158 return state.currentPower(port, component);
159 }
160
161 @Override
Andrea Campanelladadf6402019-08-07 15:24:11 +0200162 public Optional<Double> currentInputPower(PortNumber port, T component) {
Boyuan Yan7bee2482019-05-22 17:15:53 -0700163 checkState(component);
164 return state.currentInputPower(port, component);
165 }
166
167 @Override
Andrea Campanelladadf6402019-08-07 15:24:11 +0200168 public Optional<Range<Double>> getTargetPowerRange(PortNumber port, T component) {
Boyuan Yan7bee2482019-05-22 17:15:53 -0700169 checkState(component);
170 return state.getTargetPowerRange(port, component);
171 }
172
173 @Override
Andrea Campanelladadf6402019-08-07 15:24:11 +0200174 public Optional<Range<Double>> getInputPowerRange(PortNumber port, T component) {
Boyuan Yan7bee2482019-05-22 17:15:53 -0700175 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.
Andrea Campanella3ccee482019-08-02 19:15:18 +0200188 *
Boyuan Yan7bee2482019-05-22 17:15:53 -0700189 * @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.cassini = this;
205 }
206
207 /**
208 * Component type.
209 */
210 enum ComponentType {
211
212 /**
213 * Direction.
214 */
215 DIRECTION() {
216 @Override
Andrea Campanelladadf6402019-08-07 15:24:11 +0200217 public Optional<Double> getTargetPower(PortNumber port, Object component) {
Boyuan Yan7bee2482019-05-22 17:15:53 -0700218 return super.getTargetPower(port, component);
219 }
Andrea Campanella3ccee482019-08-02 19:15:18 +0200220
Boyuan Yan7bee2482019-05-22 17:15:53 -0700221 @Override
Andrea Campanelladadf6402019-08-07 15:24:11 +0200222 public void setTargetPower(PortNumber port, Object component, double power) {
Boyuan Yan7bee2482019-05-22 17:15:53 -0700223 super.setTargetPower(port, component, power);
224 }
225 },
226
227 /**
228 * OchSignal.
229 */
230 OCHSIGNAL() {
231 @Override
Andrea Campanelladadf6402019-08-07 15:24:11 +0200232 public Optional<Double> getTargetPower(PortNumber port, Object component) {
Boyuan Yan7bee2482019-05-22 17:15:53 -0700233 return super.getTargetPower(port, component);
234 }
235
236 @Override
Andrea Campanelladadf6402019-08-07 15:24:11 +0200237 public void setTargetPower(PortNumber port, Object component, double power) {
Boyuan Yan7bee2482019-05-22 17:15:53 -0700238 super.setTargetPower(port, component, power);
239 }
240 };
241
242
Boyuan Yan7bee2482019-05-22 17:15:53 -0700243 CassiniTerminalDevicePowerConfig cassini;
244
245 /**
246 * mirror method in the internal class.
Andrea Campanella3ccee482019-08-02 19:15:18 +0200247 *
248 * @param port port
Boyuan Yan7bee2482019-05-22 17:15:53 -0700249 * @param component component
250 * @return target power
251 */
Andrea Campanelladadf6402019-08-07 15:24:11 +0200252 Optional<Double> getTargetPower(PortNumber port, Object component) {
Boyuan Yan7bee2482019-05-22 17:15:53 -0700253 NetconfSession session = cassini.getNetconfSession(cassini.did());
254 checkNotNull(session);
255 String filter = parsePort(cassini, port, null, null);
256 StringBuilder rpcReq = new StringBuilder();
257 rpcReq.append(RPC_TAG_NETCONF_BASE)
Andrea Campanella3ccee482019-08-02 19:15:18 +0200258 .append("<get-config>")
259 .append("<source>")
260 .append("<" + DatastoreId.RUNNING + "/>")
261 .append("</source>")
Boyuan Yan7bee2482019-05-22 17:15:53 -0700262 .append("<filter type='subtree'>")
263 .append(filter)
264 .append("</filter>")
Andrea Campanella3ccee482019-08-02 19:15:18 +0200265 .append("</get-config>")
Boyuan Yan7bee2482019-05-22 17:15:53 -0700266 .append(RPC_CLOSE_TAG);
267 XMLConfiguration xconf = cassini.executeRpc(session, rpcReq.toString());
Andrea Campanella3ccee482019-08-02 19:15:18 +0200268 if (xconf == null) {
269 log.error("Error in executingRpc");
270 return Optional.empty();
271 }
Boyuan Yan7bee2482019-05-22 17:15:53 -0700272 try {
273 HierarchicalConfiguration config =
Andrea Campanella3ccee482019-08-02 19:15:18 +0200274 xconf.configurationAt("data/components/component/optical-channel/config");
Andrea Campanelladadf6402019-08-07 15:24:11 +0200275 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();
Boyuan Yan7bee2482019-05-22 17:15:53 -0700279 return Optional.of(power);
280 } catch (IllegalArgumentException e) {
281 return Optional.empty();
282 }
Boyuan Yana00e3d02019-04-10 23:43:12 -0700283 }
284
Boyuan Yan7bee2482019-05-22 17:15:53 -0700285 /**
286 * mirror method in the internal class.
Andrea Campanella3ccee482019-08-02 19:15:18 +0200287 *
288 * @param port port
Boyuan Yan7bee2482019-05-22 17:15:53 -0700289 * @param component component
Andrea Campanella3ccee482019-08-02 19:15:18 +0200290 * @param power target value
Boyuan Yan7bee2482019-05-22 17:15:53 -0700291 */
Andrea Campanelladadf6402019-08-07 15:24:11 +0200292 void setTargetPower(PortNumber port, Object component, double power) {
Boyuan Yan7bee2482019-05-22 17:15:53 -0700293 NetconfSession session = cassini.getNetconfSession(cassini.did());
294 checkNotNull(session);
295 String editConfig = parsePort(cassini, port, null, power);
296 StringBuilder rpcReq = new StringBuilder();
297 rpcReq.append(RPC_TAG_NETCONF_BASE)
298 .append("<edit-config>")
Andrea Campanella3a361452019-08-02 10:17:53 +0200299 .append("<target><" + DatastoreId.CANDIDATE + "/></target>")
Boyuan Yan7bee2482019-05-22 17:15:53 -0700300 .append("<config>")
301 .append(editConfig)
302 .append("</config>")
303 .append("</edit-config>")
304 .append(RPC_CLOSE_TAG);
Andrea Campanella3a361452019-08-02 10:17:53 +0200305 log.info("Setting power {}", rpcReq.toString());
Boyuan Yan7bee2482019-05-22 17:15:53 -0700306 XMLConfiguration xconf = cassini.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 }
Andrea Campanella3a361452019-08-02 10:17:53 +0200312 try {
313 session.commit();
314 } catch (NetconfException e) {
Andrea Campanella3ccee482019-08-02 19:15:18 +0200315 log.error("error committing channel power", e);
Andrea Campanella3a361452019-08-02 10:17:53 +0200316 }
Boyuan Yana00e3d02019-04-10 23:43:12 -0700317 }
Boyuan Yan7bee2482019-05-22 17:15:53 -0700318
319 /**
320 * mirror method in the internal class.
Andrea Campanella3ccee482019-08-02 19:15:18 +0200321 *
322 * @param port port
Boyuan Yan7bee2482019-05-22 17:15:53 -0700323 * @param component the component.
324 * @return current output power.
325 */
Andrea Campanelladadf6402019-08-07 15:24:11 +0200326 Optional<Double> currentPower(PortNumber port, Object component) {
Boyuan Yan7bee2482019-05-22 17:15:53 -0700327 XMLConfiguration xconf = getOpticalChannelState(
Andrea Campanella3ccee482019-08-02 19:15:18 +0200328 cassini, port, "<output-power><instant/></output-power>");
Boyuan Yan7bee2482019-05-22 17:15:53 -0700329 try {
330 HierarchicalConfiguration config =
Andrea Campanella3ccee482019-08-02 19:15:18 +0200331 xconf.configurationAt("data/components/component/optical-channel/state/output-power");
Andrea Campanelladadf6402019-08-07 15:24:11 +0200332 if (config == null || config.getString("instant") == null) {
333 return Optional.empty();
334 }
335 double currentPower = Float.valueOf(config.getString("instant")).doubleValue();
Boyuan Yan7bee2482019-05-22 17:15:53 -0700336 return Optional.of(currentPower);
337 } catch (IllegalArgumentException e) {
338 return Optional.empty();
339 }
Boyuan Yana00e3d02019-04-10 23:43:12 -0700340 }
Boyuan Yana00e3d02019-04-10 23:43:12 -0700341
Boyuan Yan7bee2482019-05-22 17:15:53 -0700342 /**
343 * mirror method in the internal class.
Andrea Campanella3ccee482019-08-02 19:15:18 +0200344 *
345 * @param port port
Boyuan Yan7bee2482019-05-22 17:15:53 -0700346 * @param component the component
347 * @return current input power
348 */
Andrea Campanelladadf6402019-08-07 15:24:11 +0200349 Optional<Double> currentInputPower(PortNumber port, Object component) {
Boyuan Yan7bee2482019-05-22 17:15:53 -0700350 XMLConfiguration xconf = getOpticalChannelState(
351 cassini, port, "<input-power><instant/></input-power>");
352 try {
353 HierarchicalConfiguration config =
Andrea Campanella3ccee482019-08-02 19:15:18 +0200354 xconf.configurationAt("data/components/component/optical-channel/state/input-power");
Andrea Campanelladadf6402019-08-07 15:24:11 +0200355 if (config == null || config.getString("instant") == null) {
356 return Optional.empty();
357 }
358 double currentPower = Float.valueOf(config.getString("instant")).doubleValue();
Boyuan Yan7bee2482019-05-22 17:15:53 -0700359 return Optional.of(currentPower);
360 } catch (IllegalArgumentException e) {
361 return Optional.empty();
362 }
363 }
Boyuan Yana00e3d02019-04-10 23:43:12 -0700364
Andrea Campanelladadf6402019-08-07 15:24:11 +0200365 Optional<Range<Double>> getTargetPowerRange(PortNumber port, Object component) {
366 double targetMin = -30;
367 double targetMax = 1;
Andrea Campanella3ccee482019-08-02 19:15:18 +0200368 return Optional.of(Range.open(targetMin, targetMax));
Boyuan Yan7bee2482019-05-22 17:15:53 -0700369 }
Boyuan Yana00e3d02019-04-10 23:43:12 -0700370
Andrea Campanelladadf6402019-08-07 15:24:11 +0200371 Optional<Range<Double>> getInputPowerRange(PortNumber port, Object component) {
372 double targetMin = -30;
373 double targetMax = 1;
Andrea Campanella3a361452019-08-02 10:17:53 +0200374 return Optional.of(Range.open(targetMin, targetMax));
Boyuan Yan7bee2482019-05-22 17:15:53 -0700375 }
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>.
Andrea Campanella3ccee482019-08-02 19:15:18 +0200385 *
386 * @param pc power config instance
387 * @param port the port number
Boyuan Yan7bee2482019-05-22 17:15:53 -0700388 * @param underState the filter condition
389 * @return RPC reply
390 */
391 private static XMLConfiguration getOpticalChannelState(CassiniTerminalDevicePowerConfig pc,
Andrea Campanella3ccee482019-08-02 19:15:18 +0200392 PortNumber port, String underState) {
Boyuan Yan7bee2482019-05-22 17:15:53 -0700393 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.
Andrea Campanella3ccee482019-08-02 19:15:18 +0200411 *
412 * @param pc power config instance
Boyuan Yan7bee2482019-05-22 17:15:53 -0700413 * @param portNumber the port number
414 * @return the component name
415 */
416 private static String ocName(CassiniTerminalDevicePowerConfig 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
Boyuan Yan7bee2482019-05-22 17:15:53 -0700423 /**
424 * Parse filtering string from port and component.
Andrea Campanella3ccee482019-08-02 19:15:18 +0200425 *
Boyuan Yan7bee2482019-05-22 17:15:53 -0700426 * @param portNumber Port Number
Andrea Campanella3ccee482019-08-02 19:15:18 +0200427 * @param component port component (optical-channel)
428 * @param power power value set.
Boyuan Yan7bee2482019-05-22 17:15:53 -0700429 * @return filtering string in xml format
430 */
431 private static String parsePort(CassiniTerminalDevicePowerConfig pc, PortNumber portNumber,
Andrea Campanelladadf6402019-08-07 15:24:11 +0200432 Object component, Double power) {
Boyuan Yan7bee2482019-05-22 17:15:53 -0700433 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>").append("<name>").append(name).append("</name>");
437 if (power != null) {
438 // This is an edit-config operation.
439 sb.append("<optical-channel xmlns=\"http://openconfig.net/yang/terminal-device\">")
440 .append("<config>")
441 .append("<target-output-power>")
442 .append(power)
443 .append("</target-output-power>")
444 .append("</config>")
445 .append("</optical-channel>");
446 }
447 sb.append("</component>").append("</components>");
448 return sb.toString();
449 } else {
450 log.error("Cannot process the component {}.", component.getClass());
451 return null;
452 }
453 }
Boyuan Yana00e3d02019-04-10 23:43:12 -0700454 }
455}