blob: 24251e6e7e7511eae301f3d8de0eaf8a6693db30 [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 */
97 private XMLConfiguration executeRpc(NetconfSession session, String message) {
98 try {
99 CompletableFuture<String> fut = session.rpc(message);
100 String rpcReply = fut.get();
101 XMLConfiguration xconf = (XMLConfiguration) XmlConfigParser.loadXmlString(rpcReply);
102 xconf.setExpressionEngine(new XPathExpressionEngine());
103 return xconf;
104 } catch (NetconfException ne) {
105 log.error("Exception on Netconf protocol: {}.", ne);
106 } catch (InterruptedException ie) {
107 log.error("Interrupted Exception: {}.", ie);
108 } catch (ExecutionException ee) {
109 log.error("Concurrent Exception while executing Netconf operation: {}.", ee);
110 }
111 return null;
112 }
113
114 /**
115 * Get the target-output-power value on specific optical-channel.
Andrea Campanella3ccee482019-08-02 19:15:18 +0200116 *
117 * @param port the port
Boyuan Yana00e3d02019-04-10 23:43:12 -0700118 * @param component the port component. It should be 'oc-name' in the Annotations of Port.
Boyuan Yan6b23b382019-06-04 11:59:35 -0700119 * 'oc-name' could be mapped to '/component/name' in openconfig yang.
Boyuan Yana00e3d02019-04-10 23:43:12 -0700120 * @return target power value
121 */
122 @Override
Andrea Campanelladadf6402019-08-07 15:24:11 +0200123 public Optional<Double> getTargetPower(PortNumber port, T component) {
Boyuan Yan7bee2482019-05-22 17:15:53 -0700124 checkState(component);
125 return state.getTargetPower(port, component);
126 }
127
128 @Override
Andrea Campanelladadf6402019-08-07 15:24:11 +0200129 public void setTargetPower(PortNumber port, T component, double power) {
Boyuan Yan7bee2482019-05-22 17:15:53 -0700130 checkState(component);
131 state.setTargetPower(port, component, power);
132 }
133
134 @Override
Andrea Campanelladadf6402019-08-07 15:24:11 +0200135 public Optional<Double> currentPower(PortNumber port, T component) {
Boyuan Yan7bee2482019-05-22 17:15:53 -0700136 checkState(component);
137 return state.currentPower(port, component);
138 }
139
140 @Override
Andrea Campanelladadf6402019-08-07 15:24:11 +0200141 public Optional<Double> currentInputPower(PortNumber port, T component) {
Boyuan Yan7bee2482019-05-22 17:15:53 -0700142 checkState(component);
143 return state.currentInputPower(port, component);
144 }
145
146 @Override
Andrea Campanelladadf6402019-08-07 15:24:11 +0200147 public Optional<Range<Double>> getTargetPowerRange(PortNumber port, T component) {
Boyuan Yan7bee2482019-05-22 17:15:53 -0700148 checkState(component);
149 return state.getTargetPowerRange(port, component);
150 }
151
152 @Override
Andrea Campanelladadf6402019-08-07 15:24:11 +0200153 public Optional<Range<Double>> getInputPowerRange(PortNumber port, T component) {
Boyuan Yan7bee2482019-05-22 17:15:53 -0700154 checkState(component);
155 return state.getInputPowerRange(port, component);
156 }
157
158 @Override
159 public List<PortNumber> getPorts(T component) {
160 checkState(component);
161 return state.getPorts(component);
162 }
163
164
165 /**
166 * Set the ComponentType to invoke proper methods for different template T.
Andrea Campanella3ccee482019-08-02 19:15:18 +0200167 *
Boyuan Yan7bee2482019-05-22 17:15:53 -0700168 * @param component the component.
169 */
170 void checkState(Object component) {
171 String clsName = component.getClass().getName();
172 switch (clsName) {
173 case "org.onosproject.net.Direction":
174 state = ComponentType.DIRECTION;
175 break;
176 case "org.onosproject.net.OchSignal":
177 state = ComponentType.OCHSIGNAL;
178 break;
179 default:
180 log.error("Cannot parse the component type {}.", clsName);
181 log.info("The component content is {}.", component.toString());
182 }
183 state.cassini = this;
184 }
185
186 /**
187 * Component type.
188 */
189 enum ComponentType {
190
191 /**
192 * Direction.
193 */
194 DIRECTION() {
195 @Override
Andrea Campanelladadf6402019-08-07 15:24:11 +0200196 public Optional<Double> getTargetPower(PortNumber port, Object component) {
Boyuan Yan7bee2482019-05-22 17:15:53 -0700197 return super.getTargetPower(port, component);
198 }
Andrea Campanella3ccee482019-08-02 19:15:18 +0200199
Boyuan Yan7bee2482019-05-22 17:15:53 -0700200 @Override
Andrea Campanelladadf6402019-08-07 15:24:11 +0200201 public void setTargetPower(PortNumber port, Object component, double power) {
Boyuan Yan7bee2482019-05-22 17:15:53 -0700202 super.setTargetPower(port, component, power);
203 }
204 },
205
206 /**
207 * OchSignal.
208 */
209 OCHSIGNAL() {
210 @Override
Andrea Campanelladadf6402019-08-07 15:24:11 +0200211 public Optional<Double> getTargetPower(PortNumber port, Object component) {
Boyuan Yan7bee2482019-05-22 17:15:53 -0700212 return super.getTargetPower(port, component);
213 }
214
215 @Override
Andrea Campanelladadf6402019-08-07 15:24:11 +0200216 public void setTargetPower(PortNumber port, Object component, double power) {
Boyuan Yan7bee2482019-05-22 17:15:53 -0700217 super.setTargetPower(port, component, power);
218 }
219 };
220
221
Boyuan Yan7bee2482019-05-22 17:15:53 -0700222 CassiniTerminalDevicePowerConfig cassini;
223
224 /**
225 * mirror method in the internal class.
Andrea Campanella3ccee482019-08-02 19:15:18 +0200226 *
227 * @param port port
Boyuan Yan7bee2482019-05-22 17:15:53 -0700228 * @param component component
229 * @return target power
230 */
Andrea Campanelladadf6402019-08-07 15:24:11 +0200231 Optional<Double> getTargetPower(PortNumber port, Object component) {
Boyuan Yan7bee2482019-05-22 17:15:53 -0700232 NetconfSession session = cassini.getNetconfSession(cassini.did());
233 checkNotNull(session);
234 String filter = parsePort(cassini, port, null, null);
235 StringBuilder rpcReq = new StringBuilder();
236 rpcReq.append(RPC_TAG_NETCONF_BASE)
Andrea Campanella3ccee482019-08-02 19:15:18 +0200237 .append("<get-config>")
238 .append("<source>")
239 .append("<" + DatastoreId.RUNNING + "/>")
240 .append("</source>")
Boyuan Yan7bee2482019-05-22 17:15:53 -0700241 .append("<filter type='subtree'>")
242 .append(filter)
243 .append("</filter>")
Andrea Campanella3ccee482019-08-02 19:15:18 +0200244 .append("</get-config>")
Boyuan Yan7bee2482019-05-22 17:15:53 -0700245 .append(RPC_CLOSE_TAG);
246 XMLConfiguration xconf = cassini.executeRpc(session, rpcReq.toString());
Andrea Campanella3ccee482019-08-02 19:15:18 +0200247 if (xconf == null) {
248 log.error("Error in executingRpc");
249 return Optional.empty();
250 }
Boyuan Yan7bee2482019-05-22 17:15:53 -0700251 try {
252 HierarchicalConfiguration config =
Andrea Campanella3ccee482019-08-02 19:15:18 +0200253 xconf.configurationAt("data/components/component/optical-channel/config");
Andrea Campanelladadf6402019-08-07 15:24:11 +0200254 if (config == null || config.getString("target-output-power") == null) {
255 return Optional.empty();
256 }
257 double power = Float.valueOf(config.getString("target-output-power")).doubleValue();
Boyuan Yan7bee2482019-05-22 17:15:53 -0700258 return Optional.of(power);
259 } catch (IllegalArgumentException e) {
260 return Optional.empty();
261 }
Boyuan Yana00e3d02019-04-10 23:43:12 -0700262 }
263
Boyuan Yan7bee2482019-05-22 17:15:53 -0700264 /**
265 * mirror method in the internal class.
Andrea Campanella3ccee482019-08-02 19:15:18 +0200266 *
267 * @param port port
Boyuan Yan7bee2482019-05-22 17:15:53 -0700268 * @param component component
Andrea Campanella3ccee482019-08-02 19:15:18 +0200269 * @param power target value
Boyuan Yan7bee2482019-05-22 17:15:53 -0700270 */
Andrea Campanelladadf6402019-08-07 15:24:11 +0200271 void setTargetPower(PortNumber port, Object component, double power) {
Boyuan Yan7bee2482019-05-22 17:15:53 -0700272 NetconfSession session = cassini.getNetconfSession(cassini.did());
273 checkNotNull(session);
274 String editConfig = parsePort(cassini, port, null, power);
275 StringBuilder rpcReq = new StringBuilder();
276 rpcReq.append(RPC_TAG_NETCONF_BASE)
277 .append("<edit-config>")
Andrea Campanella3a361452019-08-02 10:17:53 +0200278 .append("<target><" + DatastoreId.CANDIDATE + "/></target>")
Boyuan Yan7bee2482019-05-22 17:15:53 -0700279 .append("<config>")
280 .append(editConfig)
281 .append("</config>")
282 .append("</edit-config>")
283 .append(RPC_CLOSE_TAG);
Andrea Campanella3a361452019-08-02 10:17:53 +0200284 log.info("Setting power {}", rpcReq.toString());
Boyuan Yan7bee2482019-05-22 17:15:53 -0700285 XMLConfiguration xconf = cassini.executeRpc(session, rpcReq.toString());
286 // The successful reply should be "<rpc-reply ...><ok /></rpc-reply>"
287 if (!xconf.getRoot().getChild(0).getName().equals("ok")) {
288 log.error("The <edit-config> operation to set target-output-power of Port({}:{}) is failed.",
289 port.toString(), component.toString());
290 }
Andrea Campanella3a361452019-08-02 10:17:53 +0200291 try {
292 session.commit();
293 } catch (NetconfException e) {
Andrea Campanella3ccee482019-08-02 19:15:18 +0200294 log.error("error committing channel power", e);
Andrea Campanella3a361452019-08-02 10:17:53 +0200295 }
Boyuan Yana00e3d02019-04-10 23:43:12 -0700296 }
Boyuan Yan7bee2482019-05-22 17:15:53 -0700297
298 /**
299 * mirror method in the internal class.
Andrea Campanella3ccee482019-08-02 19:15:18 +0200300 *
301 * @param port port
Boyuan Yan7bee2482019-05-22 17:15:53 -0700302 * @param component the component.
303 * @return current output power.
304 */
Andrea Campanelladadf6402019-08-07 15:24:11 +0200305 Optional<Double> currentPower(PortNumber port, Object component) {
Boyuan Yan7bee2482019-05-22 17:15:53 -0700306 XMLConfiguration xconf = getOpticalChannelState(
Andrea Campanella3ccee482019-08-02 19:15:18 +0200307 cassini, port, "<output-power><instant/></output-power>");
Boyuan Yan7bee2482019-05-22 17:15:53 -0700308 try {
309 HierarchicalConfiguration config =
Andrea Campanella3ccee482019-08-02 19:15:18 +0200310 xconf.configurationAt("data/components/component/optical-channel/state/output-power");
Andrea Campanelladadf6402019-08-07 15:24:11 +0200311 if (config == null || config.getString("instant") == null) {
312 return Optional.empty();
313 }
314 double currentPower = Float.valueOf(config.getString("instant")).doubleValue();
Boyuan Yan7bee2482019-05-22 17:15:53 -0700315 return Optional.of(currentPower);
316 } catch (IllegalArgumentException e) {
317 return Optional.empty();
318 }
Boyuan Yana00e3d02019-04-10 23:43:12 -0700319 }
Boyuan Yana00e3d02019-04-10 23:43:12 -0700320
Boyuan Yan7bee2482019-05-22 17:15:53 -0700321 /**
322 * mirror method in the internal class.
Andrea Campanella3ccee482019-08-02 19:15:18 +0200323 *
324 * @param port port
Boyuan Yan7bee2482019-05-22 17:15:53 -0700325 * @param component the component
326 * @return current input power
327 */
Andrea Campanelladadf6402019-08-07 15:24:11 +0200328 Optional<Double> currentInputPower(PortNumber port, Object component) {
Boyuan Yan7bee2482019-05-22 17:15:53 -0700329 XMLConfiguration xconf = getOpticalChannelState(
330 cassini, port, "<input-power><instant/></input-power>");
331 try {
332 HierarchicalConfiguration config =
Andrea Campanella3ccee482019-08-02 19:15:18 +0200333 xconf.configurationAt("data/components/component/optical-channel/state/input-power");
Andrea Campanelladadf6402019-08-07 15:24:11 +0200334 if (config == null || config.getString("instant") == null) {
335 return Optional.empty();
336 }
337 double currentPower = Float.valueOf(config.getString("instant")).doubleValue();
Boyuan Yan7bee2482019-05-22 17:15:53 -0700338 return Optional.of(currentPower);
339 } catch (IllegalArgumentException e) {
340 return Optional.empty();
341 }
342 }
Boyuan Yana00e3d02019-04-10 23:43:12 -0700343
Andrea Campanelladadf6402019-08-07 15:24:11 +0200344 Optional<Range<Double>> getTargetPowerRange(PortNumber port, Object component) {
345 double targetMin = -30;
346 double targetMax = 1;
Andrea Campanella3ccee482019-08-02 19:15:18 +0200347 return Optional.of(Range.open(targetMin, targetMax));
Boyuan Yan7bee2482019-05-22 17:15:53 -0700348 }
Boyuan Yana00e3d02019-04-10 23:43:12 -0700349
Andrea Campanelladadf6402019-08-07 15:24:11 +0200350 Optional<Range<Double>> getInputPowerRange(PortNumber port, Object component) {
351 double targetMin = -30;
352 double targetMax = 1;
Andrea Campanella3a361452019-08-02 10:17:53 +0200353 return Optional.of(Range.open(targetMin, targetMax));
Boyuan Yan7bee2482019-05-22 17:15:53 -0700354 }
355
356 List<PortNumber> getPorts(Object component) {
357 // FIXME
358 log.warn("Not Implemented Yet!");
359 return new ArrayList<PortNumber>();
360 }
361
362 /**
363 * Get filtered content under <optical-channel><state>.
Andrea Campanella3ccee482019-08-02 19:15:18 +0200364 *
365 * @param pc power config instance
366 * @param port the port number
Boyuan Yan7bee2482019-05-22 17:15:53 -0700367 * @param underState the filter condition
368 * @return RPC reply
369 */
370 private static XMLConfiguration getOpticalChannelState(CassiniTerminalDevicePowerConfig pc,
Andrea Campanella3ccee482019-08-02 19:15:18 +0200371 PortNumber port, String underState) {
Boyuan Yan7bee2482019-05-22 17:15:53 -0700372 NetconfSession session = pc.getNetconfSession(pc.did());
373 checkNotNull(session);
374 String name = ocName(pc, port);
375 StringBuilder rpcReq = new StringBuilder(RPC_TAG_NETCONF_BASE);
376 rpcReq.append("<get><filter><components xmlns=\"http://openconfig.net/yang/platform\"><component>")
377 .append("<name>").append(name).append("</name>")
378 .append("<optical-channel xmlns=\"http://openconfig.net/yang/terminal-device\">")
379 .append("<state>")
380 .append(underState)
381 .append("</state></optical-channel></component></components></filter></get>")
382 .append(RPC_CLOSE_TAG);
Andrea Campanella3ccee482019-08-02 19:15:18 +0200383 log.info("Getting Optical Channel State {}", rpcReq.toString());
384 StringWriter stringWriter = new StringWriter();
Boyuan Yan7bee2482019-05-22 17:15:53 -0700385 XMLConfiguration xconf = pc.executeRpc(session, rpcReq.toString());
Andrea Campanella3ccee482019-08-02 19:15:18 +0200386 try {
387 xconf.save(stringWriter);
388 } catch (ConfigurationException e) {
389 log.error("XML Config Exception ", e);
390 }
391 log.info("Optical Channel State {}", stringWriter.toString());
Boyuan Yan7bee2482019-05-22 17:15:53 -0700392 return xconf;
393 }
394
395
396 /**
397 * Extract component name from portNumber's annotations.
Andrea Campanella3ccee482019-08-02 19:15:18 +0200398 *
399 * @param pc power config instance
Boyuan Yan7bee2482019-05-22 17:15:53 -0700400 * @param portNumber the port number
401 * @return the component name
402 */
403 private static String ocName(CassiniTerminalDevicePowerConfig pc, PortNumber portNumber) {
404 DeviceService deviceService = DefaultServiceDirectory.getService(DeviceService.class);
405 DeviceId deviceId = pc.handler().data().deviceId();
406 return deviceService.getPort(deviceId, portNumber).annotations().value("oc-name");
407 }
408
409
Boyuan Yan7bee2482019-05-22 17:15:53 -0700410 /**
411 * Parse filtering string from port and component.
Andrea Campanella3ccee482019-08-02 19:15:18 +0200412 *
Boyuan Yan7bee2482019-05-22 17:15:53 -0700413 * @param portNumber Port Number
Andrea Campanella3ccee482019-08-02 19:15:18 +0200414 * @param component port component (optical-channel)
415 * @param power power value set.
Boyuan Yan7bee2482019-05-22 17:15:53 -0700416 * @return filtering string in xml format
417 */
418 private static String parsePort(CassiniTerminalDevicePowerConfig pc, PortNumber portNumber,
Andrea Campanelladadf6402019-08-07 15:24:11 +0200419 Object component, Double power) {
Boyuan Yan7bee2482019-05-22 17:15:53 -0700420 if (component == null) {
421 String name = ocName(pc, portNumber);
422 StringBuilder sb = new StringBuilder("<components xmlns=\"http://openconfig.net/yang/platform\">");
423 sb.append("<component>").append("<name>").append(name).append("</name>");
424 if (power != null) {
425 // This is an edit-config operation.
426 sb.append("<optical-channel xmlns=\"http://openconfig.net/yang/terminal-device\">")
427 .append("<config>")
428 .append("<target-output-power>")
429 .append(power)
430 .append("</target-output-power>")
431 .append("</config>")
432 .append("</optical-channel>");
433 }
434 sb.append("</component>").append("</components>");
435 return sb.toString();
436 } else {
437 log.error("Cannot process the component {}.", component.getClass());
438 return null;
439 }
440 }
Boyuan Yana00e3d02019-04-10 23:43:12 -0700441 }
442}