blob: 33dc66b9ee524c8f08f318abbfdd1ec9c367088d [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;
22import org.apache.commons.configuration.HierarchicalConfiguration;
23import org.apache.commons.configuration.XMLConfiguration;
24import org.apache.commons.configuration.tree.xpath.XPathExpressionEngine;
25import org.onlab.osgi.DefaultServiceDirectory;
26import org.onosproject.drivers.utilities.XmlConfigParser;
27import org.onosproject.net.DeviceId;
Boyuan Yana00e3d02019-04-10 23:43:12 -070028import org.onosproject.net.PortNumber;
29import org.onosproject.net.behaviour.PowerConfig;
30import org.onosproject.net.device.DeviceService;
31import org.onosproject.net.driver.AbstractHandlerBehaviour;
Andrea Campanella3a361452019-08-02 10:17:53 +020032import org.onosproject.netconf.DatastoreId;
Boyuan Yana00e3d02019-04-10 23:43:12 -070033import org.onosproject.netconf.NetconfController;
34import org.onosproject.netconf.NetconfDevice;
35import org.onosproject.netconf.NetconfException;
36import org.onosproject.netconf.NetconfSession;
37import org.slf4j.Logger;
38
39import java.util.ArrayList;
40import java.util.List;
41import java.util.Optional;
42import java.util.concurrent.CompletableFuture;
43import java.util.concurrent.ExecutionException;
44
Boyuan Yan7bee2482019-05-22 17:15:53 -070045import static com.google.common.base.Preconditions.checkNotNull;
Boyuan Yana00e3d02019-04-10 23:43:12 -070046import static org.slf4j.LoggerFactory.getLogger;
47
48/**
49 * Driver Implementation of the PowerConfig for OpenConfig terminal devices.
50 *
51 */
Boyuan Yan7bee2482019-05-22 17:15:53 -070052public class CassiniTerminalDevicePowerConfig<T>
53 extends AbstractHandlerBehaviour implements PowerConfig<T> {
Boyuan Yana00e3d02019-04-10 23:43:12 -070054
55 private static final String RPC_TAG_NETCONF_BASE =
56 "<rpc xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">";
57
58 private static final String RPC_CLOSE_TAG = "</rpc>";
59
60 private static final long NO_POWER = -50;
61
62 private static final Logger log = getLogger(CassiniTerminalDevicePowerConfig.class);
63
Boyuan Yan7bee2482019-05-22 17:15:53 -070064 private ComponentType state = ComponentType.DIRECTION;
65
Boyuan Yana00e3d02019-04-10 23:43:12 -070066 /**
67 * Returns the NetconfSession with the device for which the method was called.
68 *
69 * @param deviceId device indetifier
70 *
71 * @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.
94 * @param session Netconf session
95 * @param message Netconf message in XML format
96 * @return XMLConfiguration object
97 */
98 private XMLConfiguration executeRpc(NetconfSession session, String message) {
99 try {
100 CompletableFuture<String> fut = session.rpc(message);
101 String rpcReply = fut.get();
102 XMLConfiguration xconf = (XMLConfiguration) XmlConfigParser.loadXmlString(rpcReply);
103 xconf.setExpressionEngine(new XPathExpressionEngine());
104 return xconf;
105 } catch (NetconfException ne) {
106 log.error("Exception on Netconf protocol: {}.", ne);
107 } catch (InterruptedException ie) {
108 log.error("Interrupted Exception: {}.", ie);
109 } catch (ExecutionException ee) {
110 log.error("Concurrent Exception while executing Netconf operation: {}.", ee);
111 }
112 return null;
113 }
114
115 /**
116 * Get the target-output-power value on specific optical-channel.
117 * @param port the port
118 * @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
Boyuan Yan7bee2482019-05-22 17:15:53 -0700123 public Optional<Long> getTargetPower(PortNumber port, T component) {
124 checkState(component);
125 return state.getTargetPower(port, component);
126 }
127
128 @Override
129 public void setTargetPower(PortNumber port, T component, long power) {
130 checkState(component);
131 state.setTargetPower(port, component, power);
132 }
133
134 @Override
135 public Optional<Long> currentPower(PortNumber port, T component) {
136 checkState(component);
137 return state.currentPower(port, component);
138 }
139
140 @Override
141 public Optional<Long> currentInputPower(PortNumber port, T component) {
142 checkState(component);
143 return state.currentInputPower(port, component);
144 }
145
146 @Override
147 public Optional<Range<Long>> getTargetPowerRange(PortNumber port, T component) {
148 checkState(component);
149 return state.getTargetPowerRange(port, component);
150 }
151
152 @Override
153 public Optional<Range<Long>> getInputPowerRange(PortNumber port, T component) {
154 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.
167 * @param component the component.
168 */
169 void checkState(Object component) {
170 String clsName = component.getClass().getName();
171 switch (clsName) {
172 case "org.onosproject.net.Direction":
173 state = ComponentType.DIRECTION;
174 break;
175 case "org.onosproject.net.OchSignal":
176 state = ComponentType.OCHSIGNAL;
177 break;
178 default:
179 log.error("Cannot parse the component type {}.", clsName);
180 log.info("The component content is {}.", component.toString());
181 }
182 state.cassini = this;
183 }
184
185 /**
186 * Component type.
187 */
188 enum ComponentType {
189
190 /**
191 * Direction.
192 */
193 DIRECTION() {
194 @Override
195 public Optional<Long> getTargetPower(PortNumber port, Object component) {
196 return super.getTargetPower(port, component);
197 }
198 @Override
199 public void setTargetPower(PortNumber port, Object component, long power) {
200 super.setTargetPower(port, component, power);
201 }
202 },
203
204 /**
205 * OchSignal.
206 */
207 OCHSIGNAL() {
208 @Override
209 public Optional<Long> getTargetPower(PortNumber port, Object component) {
210 return super.getTargetPower(port, component);
211 }
212
213 @Override
214 public void setTargetPower(PortNumber port, Object component, long power) {
215 super.setTargetPower(port, component, power);
216 }
217 };
218
219
220
221 CassiniTerminalDevicePowerConfig cassini;
222
223 /**
224 * mirror method in the internal class.
225 * @param port port
226 * @param component component
227 * @return target power
228 */
229 Optional<Long> getTargetPower(PortNumber port, Object component) {
230 NetconfSession session = cassini.getNetconfSession(cassini.did());
231 checkNotNull(session);
232 String filter = parsePort(cassini, port, null, null);
233 StringBuilder rpcReq = new StringBuilder();
234 rpcReq.append(RPC_TAG_NETCONF_BASE)
235 .append("<get>")
236 .append("<filter type='subtree'>")
237 .append(filter)
238 .append("</filter>")
239 .append("</get>")
240 .append(RPC_CLOSE_TAG);
241 XMLConfiguration xconf = cassini.executeRpc(session, rpcReq.toString());
242 try {
243 HierarchicalConfiguration config =
Andrea Campanella3a361452019-08-02 10:17:53 +0200244 xconf.configurationAt("components/component/optical-channel/state");
Boyuan Yan7bee2482019-05-22 17:15:53 -0700245 long power = Float.valueOf(config.getString("target-output-power")).longValue();
246 return Optional.of(power);
247 } catch (IllegalArgumentException e) {
248 return Optional.empty();
249 }
Boyuan Yana00e3d02019-04-10 23:43:12 -0700250 }
251
Boyuan Yan7bee2482019-05-22 17:15:53 -0700252 /**
253 * mirror method in the internal class.
254 * @param port port
255 * @param component component
256 * @param power target value
257 */
258 void setTargetPower(PortNumber port, Object component, long power) {
259 NetconfSession session = cassini.getNetconfSession(cassini.did());
260 checkNotNull(session);
261 String editConfig = parsePort(cassini, port, null, power);
262 StringBuilder rpcReq = new StringBuilder();
263 rpcReq.append(RPC_TAG_NETCONF_BASE)
264 .append("<edit-config>")
Andrea Campanella3a361452019-08-02 10:17:53 +0200265 .append("<target><" + DatastoreId.CANDIDATE + "/></target>")
Boyuan Yan7bee2482019-05-22 17:15:53 -0700266 .append("<config>")
267 .append(editConfig)
268 .append("</config>")
269 .append("</edit-config>")
270 .append(RPC_CLOSE_TAG);
Andrea Campanella3a361452019-08-02 10:17:53 +0200271 log.info("Setting power {}", rpcReq.toString());
Boyuan Yan7bee2482019-05-22 17:15:53 -0700272 XMLConfiguration xconf = cassini.executeRpc(session, rpcReq.toString());
273 // The successful reply should be "<rpc-reply ...><ok /></rpc-reply>"
274 if (!xconf.getRoot().getChild(0).getName().equals("ok")) {
275 log.error("The <edit-config> operation to set target-output-power of Port({}:{}) is failed.",
276 port.toString(), component.toString());
277 }
Andrea Campanella3a361452019-08-02 10:17:53 +0200278 try {
279 session.commit();
280 } catch (NetconfException e) {
281 log.error("error committing channel power");
282 }
Boyuan Yana00e3d02019-04-10 23:43:12 -0700283 }
Boyuan Yan7bee2482019-05-22 17:15:53 -0700284
285 /**
286 * mirror method in the internal class.
287 * @param port port
288 * @param component the component.
289 * @return current output power.
290 */
291 Optional<Long> currentPower(PortNumber port, Object component) {
292 XMLConfiguration xconf = getOpticalChannelState(
293 cassini, port, "<output-power><instant/></output-power>");
294 try {
295 HierarchicalConfiguration config =
Andrea Campanella3a361452019-08-02 10:17:53 +0200296 xconf.configurationAt("components/component/optical-channel/state/output-power");
Boyuan Yan7bee2482019-05-22 17:15:53 -0700297 long currentPower = Float.valueOf(config.getString("instant")).longValue();
298 return Optional.of(currentPower);
299 } catch (IllegalArgumentException e) {
300 return Optional.empty();
301 }
Boyuan Yana00e3d02019-04-10 23:43:12 -0700302 }
Boyuan Yana00e3d02019-04-10 23:43:12 -0700303
Boyuan Yan7bee2482019-05-22 17:15:53 -0700304 /**
305 * mirror method in the internal class.
306 * @param port port
307 * @param component the component
308 * @return current input power
309 */
310 Optional<Long> currentInputPower(PortNumber port, Object component) {
311 XMLConfiguration xconf = getOpticalChannelState(
312 cassini, port, "<input-power><instant/></input-power>");
313 try {
314 HierarchicalConfiguration config =
Andrea Campanella3a361452019-08-02 10:17:53 +0200315 xconf.configurationAt("components/component/optical-channel/state/input-power");
Boyuan Yan7bee2482019-05-22 17:15:53 -0700316 long currentPower = Float.valueOf(config.getString("instant")).longValue();
317 return Optional.of(currentPower);
318 } catch (IllegalArgumentException e) {
319 return Optional.empty();
320 }
321 }
Boyuan Yana00e3d02019-04-10 23:43:12 -0700322
Boyuan Yan7bee2482019-05-22 17:15:53 -0700323 Optional<Range<Long>> getTargetPowerRange(PortNumber port, Object component) {
Andrea Campanella3a361452019-08-02 10:17:53 +0200324 long targetMin = -30;
325 long targetMax = 1;
Boyuan Yan7bee2482019-05-22 17:15:53 -0700326 return Optional.of(Range.open(targetMin, targetMax));
Boyuan Yan7bee2482019-05-22 17:15:53 -0700327 }
Boyuan Yana00e3d02019-04-10 23:43:12 -0700328
Boyuan Yan7bee2482019-05-22 17:15:53 -0700329 Optional<Range<Long>> getInputPowerRange(PortNumber port, Object component) {
Andrea Campanella3a361452019-08-02 10:17:53 +0200330 long targetMin = -30;
331 long targetMax = 1;
332 return Optional.of(Range.open(targetMin, targetMax));
Boyuan Yan7bee2482019-05-22 17:15:53 -0700333 }
334
335 List<PortNumber> getPorts(Object component) {
336 // FIXME
337 log.warn("Not Implemented Yet!");
338 return new ArrayList<PortNumber>();
339 }
340
341 /**
342 * Get filtered content under <optical-channel><state>.
343 * @param pc power config instance
344 * @param port the port number
345 * @param underState the filter condition
346 * @return RPC reply
347 */
348 private static XMLConfiguration getOpticalChannelState(CassiniTerminalDevicePowerConfig pc,
349 PortNumber port, String underState) {
350 NetconfSession session = pc.getNetconfSession(pc.did());
351 checkNotNull(session);
352 String name = ocName(pc, port);
353 StringBuilder rpcReq = new StringBuilder(RPC_TAG_NETCONF_BASE);
354 rpcReq.append("<get><filter><components xmlns=\"http://openconfig.net/yang/platform\"><component>")
355 .append("<name>").append(name).append("</name>")
356 .append("<optical-channel xmlns=\"http://openconfig.net/yang/terminal-device\">")
357 .append("<state>")
358 .append(underState)
359 .append("</state></optical-channel></component></components></filter></get>")
360 .append(RPC_CLOSE_TAG);
361 XMLConfiguration xconf = pc.executeRpc(session, rpcReq.toString());
362 return xconf;
363 }
364
365
366 /**
367 * Extract component name from portNumber's annotations.
368 * @param pc power config instance
369 * @param portNumber the port number
370 * @return the component name
371 */
372 private static String ocName(CassiniTerminalDevicePowerConfig pc, PortNumber portNumber) {
373 DeviceService deviceService = DefaultServiceDirectory.getService(DeviceService.class);
374 DeviceId deviceId = pc.handler().data().deviceId();
375 return deviceService.getPort(deviceId, portNumber).annotations().value("oc-name");
376 }
377
378
379
380 /**
381 * Parse filtering string from port and component.
382 * @param portNumber Port Number
383 * @param component port component (optical-channel)
384 * @param power power value set.
385 * @return filtering string in xml format
386 */
387 private static String parsePort(CassiniTerminalDevicePowerConfig pc, PortNumber portNumber,
388 Object component, Long power) {
389 if (component == null) {
390 String name = ocName(pc, portNumber);
391 StringBuilder sb = new StringBuilder("<components xmlns=\"http://openconfig.net/yang/platform\">");
392 sb.append("<component>").append("<name>").append(name).append("</name>");
393 if (power != null) {
394 // This is an edit-config operation.
395 sb.append("<optical-channel xmlns=\"http://openconfig.net/yang/terminal-device\">")
396 .append("<config>")
397 .append("<target-output-power>")
398 .append(power)
399 .append("</target-output-power>")
400 .append("</config>")
401 .append("</optical-channel>");
402 }
403 sb.append("</component>").append("</components>");
404 return sb.toString();
405 } else {
406 log.error("Cannot process the component {}.", component.getClass());
407 return null;
408 }
409 }
Boyuan Yana00e3d02019-04-10 23:43:12 -0700410 }
411}