blob: 8e63bd71afc03f3429a4be3802324802cb5d1841 [file] [log] [blame]
Jan Kundrát981fe472019-10-15 22:44:19 +02001/*
2 * Copyright 2019-2020 Jan Kundrát, CESNET, <jan.kundrat@cesnet.cz> and 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
17package org.onosproject.drivers.czechlight;
18
19import com.google.common.collect.Range;
20import org.apache.commons.configuration.HierarchicalConfiguration;
21import org.onosproject.net.PortNumber;
22import org.onosproject.net.OchSignal;
23import org.onosproject.net.behaviour.PowerConfig;
24import org.onosproject.net.device.DeviceService;
25import org.onosproject.net.driver.AbstractHandlerBehaviour;
26
27import java.util.Arrays;
28import java.util.Optional;
29
30import org.onosproject.netconf.DatastoreId;
31import org.onosproject.netconf.NetconfController;
32import org.onosproject.netconf.NetconfException;
33import org.onosproject.netconf.NetconfSession;
34import org.slf4j.Logger;
35
36import static com.google.common.base.Preconditions.checkNotNull;
37import static org.slf4j.LoggerFactory.getLogger;
38
39public class CzechLightPowerConfig<T> extends AbstractHandlerBehaviour
40 implements PowerConfig<T> {
41
42 private final Logger log = getLogger(getClass());
43
44
45 @Override
46 public Optional<Double> getTargetPower(PortNumber port, T component) {
47 return Optional.empty();
48 }
49
50 //Used by the ROADM app to set the "attenuation" parameter
51 @Override
52 public void setTargetPower(PortNumber port, T component, double power) {
53 switch (deviceType()) {
54 case LINE_DEGREE:
55 case ADD_DROP_FLEX:
56 if (!(component instanceof OchSignal)) {
57 log.error("Cannot set target power or anything but a Media Channel");
58 return;
59 }
60 HierarchicalConfiguration xml;
61 try {
62 xml = doGetSubtree(CzechLightDiscovery.CHANNEL_DEFS_FILTER + CzechLightDiscovery.MC_ROUTING_FILTER);
63 } catch (NetconfException e) {
64 log.error("Cannot read data from NETCONF: {}", e);
65 return;
66 }
67 final var allChannels = MediaChannelDefinition.parseChannelDefinitions(xml);
68 final var och = ((OchSignal) component);
69 final var channel = allChannels.entrySet().stream()
70 .filter(entry -> MediaChannelDefinition.mcMatches(entry, och))
71 .findAny()
72 .orElse(null);
73 if (channel == null) {
74 log.error("Cannot map OCh definition {} to a channel from the channel plan", och);
75 return;
76 }
77 final String element = port.toLong() == CzechLightDiscovery.PORT_COMMON ? "add" : "drop";
78 log.debug("{}: {} power for {} to {}", data().deviceId(), channel.getKey(), power);
79 var sb = new StringBuilder();
80 sb.append(CzechLightDiscovery.XML_MC_OPEN);
81 sb.append("<channel>");
82 sb.append(channel.getKey());
83 sb.append("</channel>");
84 sb.append("<");
85 sb.append(element);
86 sb.append("><power>");
87 sb.append(power);
88 sb.append("</power></");
89 sb.append(element);
90 sb.append(">");
91 sb.append(CzechLightDiscovery.XML_MC_CLOSE);
92 doEditConfig("merge", sb.toString());
93 return;
94 default:
95 log.error("Target power is only supported on WSS-based devices");
96 return;
97 }
98 }
99
100 @Override
101 public Optional<Double> currentPower(PortNumber port, T component) {
102 if (component instanceof OchSignal) {
103 // FIXME: this should be actually very easy for MCs that are routed...
104 log.debug("per-MC power not implemented yet");
105 return Optional.empty();
106 }
107 switch (deviceType()) {
108 case LINE_DEGREE:
109 case ADD_DROP_FLEX:
110 if (port.toLong() == CzechLightDiscovery.PORT_COMMON) {
111 return Optional.ofNullable(fetchLeafDouble(CzechLightDiscovery.NS_CZECHLIGHT_ROADM_DEVICE,
112 "aggregate-power/common-out"));
113 } else {
114 return Optional.ofNullable(fetchLeafSum(CzechLightDiscovery.NS_CZECHLIGHT_ROADM_DEVICE,
115 "media-channels[drop/port = '" +
116 CzechLightDiscovery.leafPortName(deviceType(), port.toLong()) +
117 "']/power/leaf-out"));
118 }
119 case COHERENT_ADD_DROP:
120 if (component instanceof OchSignal) {
121 log.debug("Coherent Add/Drop: cannot query per-MC channel power");
122 return Optional.empty();
123 }
124 if (port.toLong() == CzechLightDiscovery.PORT_COMMON) {
125 return Optional.ofNullable(fetchLeafDouble(CzechLightDiscovery.NS_CZECHLIGHT_COHERENT_A_D,
126 "aggregate-power/express-out"));
127 } else {
128 return Optional.ofNullable(fetchLeafDouble(CzechLightDiscovery.NS_CZECHLIGHT_COHERENT_A_D,
129 "aggregate-power/drop"));
130 }
131 case INLINE_AMP:
132 return Optional.ofNullable(fetchLeafDouble(CzechLightDiscovery.NS_CZECHLIGHT_INLINE_AMP,
133 inlineAmpStageNameFor(port) + "/optical-power/output"));
134 default:
135 assert false : "unhandled device type";
136 }
137 return Optional.empty();
138 }
139
140 @Override
141 public Optional<Double> currentInputPower(PortNumber port, T component) {
142 if (component instanceof OchSignal) {
143 log.debug("per-MC power not implemented yet");
144 return Optional.empty();
145 }
146 switch (deviceType()) {
147 case LINE_DEGREE:
148 case ADD_DROP_FLEX:
149 if (port.toLong() == CzechLightDiscovery.PORT_COMMON) {
150 return Optional.ofNullable(fetchLeafDouble(CzechLightDiscovery.NS_CZECHLIGHT_ROADM_DEVICE,
151 "aggregate-power/common-in"));
152 } else {
153 return Optional.ofNullable(fetchLeafSum(CzechLightDiscovery.NS_CZECHLIGHT_ROADM_DEVICE,
154 "media-channels[add/port = '" +
155 CzechLightDiscovery.leafPortName(deviceType(), port.toLong()) +
156 "']/power/leaf-in"));
157 }
158 case COHERENT_ADD_DROP:
159 if (component instanceof OchSignal) {
160 log.debug("Coherent Add/Drop: cannot query per-MC channel power");
161 return Optional.empty();
162 }
163 if (port.toLong() == CzechLightDiscovery.PORT_COMMON) {
164 return Optional.ofNullable(fetchLeafDouble(CzechLightDiscovery.NS_CZECHLIGHT_COHERENT_A_D,
165 "aggregate-power/express-in"));
166 } else {
167 return Optional.ofNullable(fetchLeafDouble(CzechLightDiscovery.NS_CZECHLIGHT_COHERENT_A_D,
168 "client-ports[port='" + Long.toString(port.toLong()) + "']/input-power"));
169 }
170 case INLINE_AMP:
171 return Optional.ofNullable(fetchLeafDouble(CzechLightDiscovery.NS_CZECHLIGHT_INLINE_AMP,
172 inlineAmpStageNameFor(port) + "/optical-power/input"));
173 default:
174 assert false : "unhandled device type";
175 }
176 return Optional.empty();
177 }
178
179 @Override
180 public Optional<Range<Double>> getTargetPowerRange(PortNumber portNumber, T component) {
181 switch (deviceType()) {
182 case LINE_DEGREE:
183 case ADD_DROP_FLEX:
184 if (component instanceof OchSignal) {
185 // not all values might be actually set, it's complicated, so at least return some limit
186 return Optional.ofNullable(Range.closed(-25.0, 5.0));
187 }
188 default:
189 // pass
190 }
191 return Optional.empty();
192 }
193
194 @Override
195 public Optional<Range<Double>> getInputPowerRange(PortNumber portNumber, T component) {
196 switch (deviceType()) {
197 case LINE_DEGREE:
198 case ADD_DROP_FLEX:
199 if (component instanceof OchSignal) {
200 // not all values might be actually set, it's complicated, so at least return some limit
201 return Optional.ofNullable(Range.closed(-30.0, +10.0));
202 }
203 default:
204 // pass
205 }
206 return Optional.empty();
207 }
208
209 private CzechLightDiscovery.DeviceType deviceType() {
210 var annotations = this.handler().get(DeviceService.class).getDevice(handler().data().deviceId()).annotations();
211 return CzechLightDiscovery.DeviceType.valueOf(annotations.value(CzechLightDiscovery.DEVICE_TYPE_ANNOTATION));
212 }
213
214 private Double fetchLeafDouble(final String namespace, final String xpath) {
215 try {
216 final var res = doGetXPath("M", namespace, "/M:" + xpath);
217 final var key = CzechLightDiscovery.xpathToXmlKey(xpath);
218 if (!res.containsKey(key)) {
219 log.error("<get> reply does not contain data for key '{}'", key);
220 return null;
221 }
222 return res.getDouble(key);
223 } catch (NetconfException e) {
224 log.error("Cannot read data from NETCONF: {}", e);
225 return null;
226 }
227 }
228
229 private Double fetchLeafSum(final String namespace, final String xpath) {
230 try {
231 final var data = doGetXPath("M", namespace, "/M:" + xpath);
232 final var key = CzechLightDiscovery.xpathToXmlKey(xpath);
233 final var power = Arrays.stream(data.getStringArray(key))
234 .map(s -> Double.valueOf(s))
235 .map(dBm -> CzechLightDiscovery.dbmToMilliwatts(dBm))
236 .reduce(0.0, Double::sum);
237 log.debug(" -> power lin {}, dBm: {}", power, CzechLightDiscovery.milliwattsToDbm(power));
238 return CzechLightDiscovery.milliwattsToDbm(power);
239 } catch (NetconfException e) {
240 log.error("Cannot read data from NETCONF: {}", e);
241 return null;
242 }
243 }
244
245 private String inlineAmpStageNameFor(final PortNumber port) {
246 return port.toLong() == CzechLightDiscovery.PORT_INLINE_WEST ? "west-to-east" : "east-to-west";
247 }
248
249 private HierarchicalConfiguration doGetXPath(final String prefix, final String namespace, final String xpathFilter)
250 throws NetconfException {
251 NetconfSession session = getNetconfSession();
252 if (session == null) {
253 log.error("Cannot request NETCONF session for {}", data().deviceId());
254 return null;
255 }
256 return CzechLightDiscovery.doGetXPath(session, prefix, namespace, xpathFilter);
257 }
258
259 private HierarchicalConfiguration doGetSubtree(final String subtreeXml) throws NetconfException {
260 NetconfSession session = getNetconfSession();
261 if (session == null) {
262 log.error("Cannot request NETCONF session for {}", data().deviceId());
263 return null;
264 }
265 return CzechLightDiscovery.doGetSubtree(session, subtreeXml);
266 }
267
268 public boolean doEditConfig(String mode, String cfg) {
269 NetconfSession session = getNetconfSession();
270 if (session == null) {
271 log.error("Cannot request NETCONF session for {}", data().deviceId());
272 return false;
273 }
274
275 try {
276 return session.editConfig(DatastoreId.RUNNING, mode, cfg);
277 } catch (NetconfException e) {
278 throw new IllegalStateException(new NetconfException("Failed to edit configuration.", e));
279 }
280 }
281
282 private NetconfSession getNetconfSession() {
283 NetconfController controller =
284 checkNotNull(handler().get(NetconfController.class));
285 return controller.getNetconfDevice(data().deviceId()).getSession();
286 }
287}