blob: 5daa288abbd495279a55092b63f729fe05821818 [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)) {
Jan Kundrátb9af9732020-02-23 17:42:18 +010057 log.error("Cannot set target power on anything but a Media Channel");
Jan Kundrát981fe472019-10-15 22:44:19 +020058 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";
Jan Kundrátb9af9732020-02-23 17:42:18 +010078 log.debug("{}: setting power for MC {} to {}",
79 data().deviceId(), channel.getKey().toUpperCase(), power);
Jan Kundrát981fe472019-10-15 22:44:19 +020080 var sb = new StringBuilder();
81 sb.append(CzechLightDiscovery.XML_MC_OPEN);
82 sb.append("<channel>");
83 sb.append(channel.getKey());
84 sb.append("</channel>");
85 sb.append("<");
86 sb.append(element);
87 sb.append("><power>");
88 sb.append(power);
89 sb.append("</power></");
90 sb.append(element);
91 sb.append(">");
92 sb.append(CzechLightDiscovery.XML_MC_CLOSE);
93 doEditConfig("merge", sb.toString());
94 return;
95 default:
96 log.error("Target power is only supported on WSS-based devices");
97 return;
98 }
99 }
100
101 @Override
102 public Optional<Double> currentPower(PortNumber port, T component) {
103 if (component instanceof OchSignal) {
104 // FIXME: this should be actually very easy for MCs that are routed...
105 log.debug("per-MC power not implemented yet");
106 return Optional.empty();
107 }
108 switch (deviceType()) {
109 case LINE_DEGREE:
110 case ADD_DROP_FLEX:
111 if (port.toLong() == CzechLightDiscovery.PORT_COMMON) {
112 return Optional.ofNullable(fetchLeafDouble(CzechLightDiscovery.NS_CZECHLIGHT_ROADM_DEVICE,
113 "aggregate-power/common-out"));
114 } else {
115 return Optional.ofNullable(fetchLeafSum(CzechLightDiscovery.NS_CZECHLIGHT_ROADM_DEVICE,
116 "media-channels[drop/port = '" +
117 CzechLightDiscovery.leafPortName(deviceType(), port.toLong()) +
118 "']/power/leaf-out"));
119 }
120 case COHERENT_ADD_DROP:
121 if (component instanceof OchSignal) {
122 log.debug("Coherent Add/Drop: cannot query per-MC channel power");
123 return Optional.empty();
124 }
125 if (port.toLong() == CzechLightDiscovery.PORT_COMMON) {
126 return Optional.ofNullable(fetchLeafDouble(CzechLightDiscovery.NS_CZECHLIGHT_COHERENT_A_D,
127 "aggregate-power/express-out"));
128 } else {
129 return Optional.ofNullable(fetchLeafDouble(CzechLightDiscovery.NS_CZECHLIGHT_COHERENT_A_D,
130 "aggregate-power/drop"));
131 }
132 case INLINE_AMP:
133 return Optional.ofNullable(fetchLeafDouble(CzechLightDiscovery.NS_CZECHLIGHT_INLINE_AMP,
134 inlineAmpStageNameFor(port) + "/optical-power/output"));
135 default:
136 assert false : "unhandled device type";
137 }
138 return Optional.empty();
139 }
140
141 @Override
142 public Optional<Double> currentInputPower(PortNumber port, T component) {
143 if (component instanceof OchSignal) {
144 log.debug("per-MC power not implemented yet");
145 return Optional.empty();
146 }
147 switch (deviceType()) {
148 case LINE_DEGREE:
149 case ADD_DROP_FLEX:
150 if (port.toLong() == CzechLightDiscovery.PORT_COMMON) {
151 return Optional.ofNullable(fetchLeafDouble(CzechLightDiscovery.NS_CZECHLIGHT_ROADM_DEVICE,
152 "aggregate-power/common-in"));
153 } else {
154 return Optional.ofNullable(fetchLeafSum(CzechLightDiscovery.NS_CZECHLIGHT_ROADM_DEVICE,
155 "media-channels[add/port = '" +
156 CzechLightDiscovery.leafPortName(deviceType(), port.toLong()) +
157 "']/power/leaf-in"));
158 }
159 case COHERENT_ADD_DROP:
160 if (component instanceof OchSignal) {
161 log.debug("Coherent Add/Drop: cannot query per-MC channel power");
162 return Optional.empty();
163 }
164 if (port.toLong() == CzechLightDiscovery.PORT_COMMON) {
165 return Optional.ofNullable(fetchLeafDouble(CzechLightDiscovery.NS_CZECHLIGHT_COHERENT_A_D,
166 "aggregate-power/express-in"));
167 } else {
168 return Optional.ofNullable(fetchLeafDouble(CzechLightDiscovery.NS_CZECHLIGHT_COHERENT_A_D,
169 "client-ports[port='" + Long.toString(port.toLong()) + "']/input-power"));
170 }
171 case INLINE_AMP:
172 return Optional.ofNullable(fetchLeafDouble(CzechLightDiscovery.NS_CZECHLIGHT_INLINE_AMP,
173 inlineAmpStageNameFor(port) + "/optical-power/input"));
174 default:
175 assert false : "unhandled device type";
176 }
177 return Optional.empty();
178 }
179
180 @Override
181 public Optional<Range<Double>> getTargetPowerRange(PortNumber portNumber, T component) {
182 switch (deviceType()) {
183 case LINE_DEGREE:
184 case ADD_DROP_FLEX:
185 if (component instanceof OchSignal) {
186 // not all values might be actually set, it's complicated, so at least return some limit
187 return Optional.ofNullable(Range.closed(-25.0, 5.0));
188 }
189 default:
190 // pass
191 }
192 return Optional.empty();
193 }
194
195 @Override
196 public Optional<Range<Double>> getInputPowerRange(PortNumber portNumber, T component) {
197 switch (deviceType()) {
198 case LINE_DEGREE:
199 case ADD_DROP_FLEX:
200 if (component instanceof OchSignal) {
201 // not all values might be actually set, it's complicated, so at least return some limit
202 return Optional.ofNullable(Range.closed(-30.0, +10.0));
203 }
204 default:
205 // pass
206 }
207 return Optional.empty();
208 }
209
210 private CzechLightDiscovery.DeviceType deviceType() {
211 var annotations = this.handler().get(DeviceService.class).getDevice(handler().data().deviceId()).annotations();
212 return CzechLightDiscovery.DeviceType.valueOf(annotations.value(CzechLightDiscovery.DEVICE_TYPE_ANNOTATION));
213 }
214
215 private Double fetchLeafDouble(final String namespace, final String xpath) {
216 try {
217 final var res = doGetXPath("M", namespace, "/M:" + xpath);
218 final var key = CzechLightDiscovery.xpathToXmlKey(xpath);
219 if (!res.containsKey(key)) {
220 log.error("<get> reply does not contain data for key '{}'", key);
221 return null;
222 }
223 return res.getDouble(key);
224 } catch (NetconfException e) {
225 log.error("Cannot read data from NETCONF: {}", e);
226 return null;
227 }
228 }
229
230 private Double fetchLeafSum(final String namespace, final String xpath) {
231 try {
232 final var data = doGetXPath("M", namespace, "/M:" + xpath);
233 final var key = CzechLightDiscovery.xpathToXmlKey(xpath);
234 final var power = Arrays.stream(data.getStringArray(key))
235 .map(s -> Double.valueOf(s))
236 .map(dBm -> CzechLightDiscovery.dbmToMilliwatts(dBm))
237 .reduce(0.0, Double::sum);
238 log.debug(" -> power lin {}, dBm: {}", power, CzechLightDiscovery.milliwattsToDbm(power));
239 return CzechLightDiscovery.milliwattsToDbm(power);
240 } catch (NetconfException e) {
241 log.error("Cannot read data from NETCONF: {}", e);
242 return null;
243 }
244 }
245
246 private String inlineAmpStageNameFor(final PortNumber port) {
247 return port.toLong() == CzechLightDiscovery.PORT_INLINE_WEST ? "west-to-east" : "east-to-west";
248 }
249
250 private HierarchicalConfiguration doGetXPath(final String prefix, final String namespace, final String xpathFilter)
251 throws NetconfException {
252 NetconfSession session = getNetconfSession();
253 if (session == null) {
254 log.error("Cannot request NETCONF session for {}", data().deviceId());
255 return null;
256 }
257 return CzechLightDiscovery.doGetXPath(session, prefix, namespace, xpathFilter);
258 }
259
260 private HierarchicalConfiguration doGetSubtree(final String subtreeXml) throws NetconfException {
261 NetconfSession session = getNetconfSession();
262 if (session == null) {
263 log.error("Cannot request NETCONF session for {}", data().deviceId());
264 return null;
265 }
266 return CzechLightDiscovery.doGetSubtree(session, subtreeXml);
267 }
268
269 public boolean doEditConfig(String mode, String cfg) {
270 NetconfSession session = getNetconfSession();
271 if (session == null) {
272 log.error("Cannot request NETCONF session for {}", data().deviceId());
273 return false;
274 }
275
276 try {
277 return session.editConfig(DatastoreId.RUNNING, mode, cfg);
278 } catch (NetconfException e) {
279 throw new IllegalStateException(new NetconfException("Failed to edit configuration.", e));
280 }
281 }
282
283 private NetconfSession getNetconfSession() {
284 NetconfController controller =
285 checkNotNull(handler().get(NetconfController.class));
286 return controller.getNetconfDevice(data().deviceId()).getSession();
287 }
288}