blob: 7b312bf59fb209d50044e46255a24e5702150ba8 [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
61 private static final long NO_POWER = -50;
62
63 private static final Logger log = getLogger(CassiniTerminalDevicePowerConfig.class);
64
Boyuan Yan7bee2482019-05-22 17:15:53 -070065 private ComponentType state = ComponentType.DIRECTION;
66
Boyuan Yana00e3d02019-04-10 23:43:12 -070067 /**
68 * Returns the NetconfSession with the device for which the method was called.
69 *
70 * @param deviceId device indetifier
Boyuan Yana00e3d02019-04-10 23:43:12 -070071 * @return The netconf session or null
72 */
73 private NetconfSession getNetconfSession(DeviceId deviceId) {
74 NetconfController controller = handler().get(NetconfController.class);
75 NetconfDevice ncdev = controller.getDevicesMap().get(deviceId);
76 if (ncdev == null) {
77 log.trace("No netconf device, returning null session");
78 return null;
79 }
80 return ncdev.getSession();
81 }
82
83 /**
84 * Get the deviceId for which the methods apply.
85 *
86 * @return The deviceId as contained in the handler data
87 */
88 private DeviceId did() {
89 return handler().data().deviceId();
90 }
91
92 /**
Boyuan Yana00e3d02019-04-10 23:43:12 -070093 * Execute RPC request.
Andrea Campanella3ccee482019-08-02 19:15:18 +020094 *
Boyuan Yana00e3d02019-04-10 23:43:12 -070095 * @param session Netconf session
96 * @param message Netconf message in XML format
97 * @return XMLConfiguration object
98 */
99 private XMLConfiguration executeRpc(NetconfSession session, String message) {
100 try {
101 CompletableFuture<String> fut = session.rpc(message);
102 String rpcReply = fut.get();
103 XMLConfiguration xconf = (XMLConfiguration) XmlConfigParser.loadXmlString(rpcReply);
104 xconf.setExpressionEngine(new XPathExpressionEngine());
105 return xconf;
106 } catch (NetconfException ne) {
107 log.error("Exception on Netconf protocol: {}.", ne);
108 } catch (InterruptedException ie) {
109 log.error("Interrupted Exception: {}.", ie);
110 } catch (ExecutionException ee) {
111 log.error("Concurrent Exception while executing Netconf operation: {}.", ee);
112 }
113 return null;
114 }
115
116 /**
117 * Get the target-output-power value on specific optical-channel.
Andrea Campanella3ccee482019-08-02 19:15:18 +0200118 *
119 * @param port the port
Boyuan Yana00e3d02019-04-10 23:43:12 -0700120 * @param component the port component. It should be 'oc-name' in the Annotations of Port.
Boyuan Yan6b23b382019-06-04 11:59:35 -0700121 * 'oc-name' could be mapped to '/component/name' in openconfig yang.
Boyuan Yana00e3d02019-04-10 23:43:12 -0700122 * @return target power value
123 */
124 @Override
Boyuan Yan7bee2482019-05-22 17:15:53 -0700125 public Optional<Long> getTargetPower(PortNumber port, T component) {
126 checkState(component);
127 return state.getTargetPower(port, component);
128 }
129
130 @Override
131 public void setTargetPower(PortNumber port, T component, long power) {
132 checkState(component);
133 state.setTargetPower(port, component, power);
134 }
135
136 @Override
137 public Optional<Long> currentPower(PortNumber port, T component) {
138 checkState(component);
139 return state.currentPower(port, component);
140 }
141
142 @Override
143 public Optional<Long> currentInputPower(PortNumber port, T component) {
144 checkState(component);
145 return state.currentInputPower(port, component);
146 }
147
148 @Override
149 public Optional<Range<Long>> getTargetPowerRange(PortNumber port, T component) {
150 checkState(component);
151 return state.getTargetPowerRange(port, component);
152 }
153
154 @Override
155 public Optional<Range<Long>> getInputPowerRange(PortNumber port, T component) {
156 checkState(component);
157 return state.getInputPowerRange(port, component);
158 }
159
160 @Override
161 public List<PortNumber> getPorts(T component) {
162 checkState(component);
163 return state.getPorts(component);
164 }
165
166
167 /**
168 * Set the ComponentType to invoke proper methods for different template T.
Andrea Campanella3ccee482019-08-02 19:15:18 +0200169 *
Boyuan Yan7bee2482019-05-22 17:15:53 -0700170 * @param component the component.
171 */
172 void checkState(Object component) {
173 String clsName = component.getClass().getName();
174 switch (clsName) {
175 case "org.onosproject.net.Direction":
176 state = ComponentType.DIRECTION;
177 break;
178 case "org.onosproject.net.OchSignal":
179 state = ComponentType.OCHSIGNAL;
180 break;
181 default:
182 log.error("Cannot parse the component type {}.", clsName);
183 log.info("The component content is {}.", component.toString());
184 }
185 state.cassini = this;
186 }
187
188 /**
189 * Component type.
190 */
191 enum ComponentType {
192
193 /**
194 * Direction.
195 */
196 DIRECTION() {
197 @Override
198 public Optional<Long> getTargetPower(PortNumber port, Object component) {
199 return super.getTargetPower(port, component);
200 }
Andrea Campanella3ccee482019-08-02 19:15:18 +0200201
Boyuan Yan7bee2482019-05-22 17:15:53 -0700202 @Override
203 public void setTargetPower(PortNumber port, Object component, long power) {
204 super.setTargetPower(port, component, power);
205 }
206 },
207
208 /**
209 * OchSignal.
210 */
211 OCHSIGNAL() {
212 @Override
213 public Optional<Long> getTargetPower(PortNumber port, Object component) {
214 return super.getTargetPower(port, component);
215 }
216
217 @Override
218 public void setTargetPower(PortNumber port, Object component, long power) {
219 super.setTargetPower(port, component, power);
220 }
221 };
222
223
Boyuan Yan7bee2482019-05-22 17:15:53 -0700224 CassiniTerminalDevicePowerConfig cassini;
225
226 /**
227 * mirror method in the internal class.
Andrea Campanella3ccee482019-08-02 19:15:18 +0200228 *
229 * @param port port
Boyuan Yan7bee2482019-05-22 17:15:53 -0700230 * @param component component
231 * @return target power
232 */
233 Optional<Long> getTargetPower(PortNumber port, Object component) {
234 NetconfSession session = cassini.getNetconfSession(cassini.did());
235 checkNotNull(session);
236 String filter = parsePort(cassini, port, null, null);
237 StringBuilder rpcReq = new StringBuilder();
238 rpcReq.append(RPC_TAG_NETCONF_BASE)
Andrea Campanella3ccee482019-08-02 19:15:18 +0200239 .append("<get-config>")
240 .append("<source>")
241 .append("<" + DatastoreId.RUNNING + "/>")
242 .append("</source>")
Boyuan Yan7bee2482019-05-22 17:15:53 -0700243 .append("<filter type='subtree'>")
244 .append(filter)
245 .append("</filter>")
Andrea Campanella3ccee482019-08-02 19:15:18 +0200246 .append("</get-config>")
Boyuan Yan7bee2482019-05-22 17:15:53 -0700247 .append(RPC_CLOSE_TAG);
248 XMLConfiguration xconf = cassini.executeRpc(session, rpcReq.toString());
Andrea Campanella3ccee482019-08-02 19:15:18 +0200249 if (xconf == null) {
250 log.error("Error in executingRpc");
251 return Optional.empty();
252 }
Boyuan Yan7bee2482019-05-22 17:15:53 -0700253 try {
254 HierarchicalConfiguration config =
Andrea Campanella3ccee482019-08-02 19:15:18 +0200255 xconf.configurationAt("data/components/component/optical-channel/config");
Boyuan Yan7bee2482019-05-22 17:15:53 -0700256 long power = Float.valueOf(config.getString("target-output-power")).longValue();
257 return Optional.of(power);
258 } catch (IllegalArgumentException e) {
259 return Optional.empty();
260 }
Boyuan Yana00e3d02019-04-10 23:43:12 -0700261 }
262
Boyuan Yan7bee2482019-05-22 17:15:53 -0700263 /**
264 * mirror method in the internal class.
Andrea Campanella3ccee482019-08-02 19:15:18 +0200265 *
266 * @param port port
Boyuan Yan7bee2482019-05-22 17:15:53 -0700267 * @param component component
Andrea Campanella3ccee482019-08-02 19:15:18 +0200268 * @param power target value
Boyuan Yan7bee2482019-05-22 17:15:53 -0700269 */
270 void setTargetPower(PortNumber port, Object component, long power) {
271 NetconfSession session = cassini.getNetconfSession(cassini.did());
272 checkNotNull(session);
273 String editConfig = parsePort(cassini, port, null, power);
274 StringBuilder rpcReq = new StringBuilder();
275 rpcReq.append(RPC_TAG_NETCONF_BASE)
276 .append("<edit-config>")
Andrea Campanella3a361452019-08-02 10:17:53 +0200277 .append("<target><" + DatastoreId.CANDIDATE + "/></target>")
Boyuan Yan7bee2482019-05-22 17:15:53 -0700278 .append("<config>")
279 .append(editConfig)
280 .append("</config>")
281 .append("</edit-config>")
282 .append(RPC_CLOSE_TAG);
Andrea Campanella3a361452019-08-02 10:17:53 +0200283 log.info("Setting power {}", rpcReq.toString());
Boyuan Yan7bee2482019-05-22 17:15:53 -0700284 XMLConfiguration xconf = cassini.executeRpc(session, rpcReq.toString());
285 // The successful reply should be "<rpc-reply ...><ok /></rpc-reply>"
286 if (!xconf.getRoot().getChild(0).getName().equals("ok")) {
287 log.error("The <edit-config> operation to set target-output-power of Port({}:{}) is failed.",
288 port.toString(), component.toString());
289 }
Andrea Campanella3a361452019-08-02 10:17:53 +0200290 try {
291 session.commit();
292 } catch (NetconfException e) {
Andrea Campanella3ccee482019-08-02 19:15:18 +0200293 log.error("error committing channel power", e);
Andrea Campanella3a361452019-08-02 10:17:53 +0200294 }
Boyuan Yana00e3d02019-04-10 23:43:12 -0700295 }
Boyuan Yan7bee2482019-05-22 17:15:53 -0700296
297 /**
298 * mirror method in the internal class.
Andrea Campanella3ccee482019-08-02 19:15:18 +0200299 *
300 * @param port port
Boyuan Yan7bee2482019-05-22 17:15:53 -0700301 * @param component the component.
302 * @return current output power.
303 */
304 Optional<Long> currentPower(PortNumber port, Object component) {
305 XMLConfiguration xconf = getOpticalChannelState(
Andrea Campanella3ccee482019-08-02 19:15:18 +0200306 cassini, port, "<output-power><instant/></output-power>");
Boyuan Yan7bee2482019-05-22 17:15:53 -0700307 try {
308 HierarchicalConfiguration config =
Andrea Campanella3ccee482019-08-02 19:15:18 +0200309 xconf.configurationAt("data/components/component/optical-channel/state/output-power");
Boyuan Yan7bee2482019-05-22 17:15:53 -0700310 long currentPower = Float.valueOf(config.getString("instant")).longValue();
311 return Optional.of(currentPower);
312 } catch (IllegalArgumentException e) {
313 return Optional.empty();
314 }
Boyuan Yana00e3d02019-04-10 23:43:12 -0700315 }
Boyuan Yana00e3d02019-04-10 23:43:12 -0700316
Boyuan Yan7bee2482019-05-22 17:15:53 -0700317 /**
318 * mirror method in the internal class.
Andrea Campanella3ccee482019-08-02 19:15:18 +0200319 *
320 * @param port port
Boyuan Yan7bee2482019-05-22 17:15:53 -0700321 * @param component the component
322 * @return current input power
323 */
324 Optional<Long> currentInputPower(PortNumber port, Object component) {
325 XMLConfiguration xconf = getOpticalChannelState(
326 cassini, port, "<input-power><instant/></input-power>");
327 try {
328 HierarchicalConfiguration config =
Andrea Campanella3ccee482019-08-02 19:15:18 +0200329 xconf.configurationAt("data/components/component/optical-channel/state/input-power");
Boyuan Yan7bee2482019-05-22 17:15:53 -0700330 long currentPower = Float.valueOf(config.getString("instant")).longValue();
331 return Optional.of(currentPower);
332 } catch (IllegalArgumentException e) {
333 return Optional.empty();
334 }
335 }
Boyuan Yana00e3d02019-04-10 23:43:12 -0700336
Boyuan Yan7bee2482019-05-22 17:15:53 -0700337 Optional<Range<Long>> getTargetPowerRange(PortNumber port, Object component) {
Andrea Campanella3ccee482019-08-02 19:15:18 +0200338 long targetMin = -30;
339 long targetMax = 1;
340 return Optional.of(Range.open(targetMin, targetMax));
Boyuan Yan7bee2482019-05-22 17:15:53 -0700341 }
Boyuan Yana00e3d02019-04-10 23:43:12 -0700342
Boyuan Yan7bee2482019-05-22 17:15:53 -0700343 Optional<Range<Long>> getInputPowerRange(PortNumber port, Object component) {
Andrea Campanella3a361452019-08-02 10:17:53 +0200344 long targetMin = -30;
345 long targetMax = 1;
346 return Optional.of(Range.open(targetMin, targetMax));
Boyuan Yan7bee2482019-05-22 17:15:53 -0700347 }
348
349 List<PortNumber> getPorts(Object component) {
350 // FIXME
351 log.warn("Not Implemented Yet!");
352 return new ArrayList<PortNumber>();
353 }
354
355 /**
356 * Get filtered content under <optical-channel><state>.
Andrea Campanella3ccee482019-08-02 19:15:18 +0200357 *
358 * @param pc power config instance
359 * @param port the port number
Boyuan Yan7bee2482019-05-22 17:15:53 -0700360 * @param underState the filter condition
361 * @return RPC reply
362 */
363 private static XMLConfiguration getOpticalChannelState(CassiniTerminalDevicePowerConfig pc,
Andrea Campanella3ccee482019-08-02 19:15:18 +0200364 PortNumber port, String underState) {
Boyuan Yan7bee2482019-05-22 17:15:53 -0700365 NetconfSession session = pc.getNetconfSession(pc.did());
366 checkNotNull(session);
367 String name = ocName(pc, port);
368 StringBuilder rpcReq = new StringBuilder(RPC_TAG_NETCONF_BASE);
369 rpcReq.append("<get><filter><components xmlns=\"http://openconfig.net/yang/platform\"><component>")
370 .append("<name>").append(name).append("</name>")
371 .append("<optical-channel xmlns=\"http://openconfig.net/yang/terminal-device\">")
372 .append("<state>")
373 .append(underState)
374 .append("</state></optical-channel></component></components></filter></get>")
375 .append(RPC_CLOSE_TAG);
Andrea Campanella3ccee482019-08-02 19:15:18 +0200376 log.info("Getting Optical Channel State {}", rpcReq.toString());
377 StringWriter stringWriter = new StringWriter();
Boyuan Yan7bee2482019-05-22 17:15:53 -0700378 XMLConfiguration xconf = pc.executeRpc(session, rpcReq.toString());
Andrea Campanella3ccee482019-08-02 19:15:18 +0200379 try {
380 xconf.save(stringWriter);
381 } catch (ConfigurationException e) {
382 log.error("XML Config Exception ", e);
383 }
384 log.info("Optical Channel State {}", stringWriter.toString());
Boyuan Yan7bee2482019-05-22 17:15:53 -0700385 return xconf;
386 }
387
388
389 /**
390 * Extract component name from portNumber's annotations.
Andrea Campanella3ccee482019-08-02 19:15:18 +0200391 *
392 * @param pc power config instance
Boyuan Yan7bee2482019-05-22 17:15:53 -0700393 * @param portNumber the port number
394 * @return the component name
395 */
396 private static String ocName(CassiniTerminalDevicePowerConfig pc, PortNumber portNumber) {
397 DeviceService deviceService = DefaultServiceDirectory.getService(DeviceService.class);
398 DeviceId deviceId = pc.handler().data().deviceId();
399 return deviceService.getPort(deviceId, portNumber).annotations().value("oc-name");
400 }
401
402
Boyuan Yan7bee2482019-05-22 17:15:53 -0700403 /**
404 * Parse filtering string from port and component.
Andrea Campanella3ccee482019-08-02 19:15:18 +0200405 *
Boyuan Yan7bee2482019-05-22 17:15:53 -0700406 * @param portNumber Port Number
Andrea Campanella3ccee482019-08-02 19:15:18 +0200407 * @param component port component (optical-channel)
408 * @param power power value set.
Boyuan Yan7bee2482019-05-22 17:15:53 -0700409 * @return filtering string in xml format
410 */
411 private static String parsePort(CassiniTerminalDevicePowerConfig pc, PortNumber portNumber,
412 Object component, Long power) {
413 if (component == null) {
414 String name = ocName(pc, portNumber);
415 StringBuilder sb = new StringBuilder("<components xmlns=\"http://openconfig.net/yang/platform\">");
416 sb.append("<component>").append("<name>").append(name).append("</name>");
417 if (power != null) {
418 // This is an edit-config operation.
419 sb.append("<optical-channel xmlns=\"http://openconfig.net/yang/terminal-device\">")
420 .append("<config>")
421 .append("<target-output-power>")
422 .append(power)
423 .append("</target-output-power>")
424 .append("</config>")
425 .append("</optical-channel>");
426 }
427 sb.append("</component>").append("</components>");
428 return sb.toString();
429 } else {
430 log.error("Cannot process the component {}.", component.getClass());
431 return null;
432 }
433 }
Boyuan Yana00e3d02019-04-10 23:43:12 -0700434 }
435}