blob: a474dfa6835432ec1979d9424add91cbbf0ab16b [file] [log] [blame]
Andrea Campanellae1e3e442019-10-21 13:45:32 +02001/*
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
17package org.onosproject.odtn.impl;
18
19import com.fasterxml.jackson.core.JsonEncoding;
20import com.fasterxml.jackson.core.JsonFactory;
21import com.fasterxml.jackson.core.JsonGenerator;
22import com.fasterxml.jackson.databind.JsonNode;
23import com.fasterxml.jackson.databind.ObjectMapper;
24import com.fasterxml.jackson.databind.ObjectReader;
25import com.google.common.annotations.Beta;
26import org.apache.commons.lang3.tuple.Pair;
27import org.onlab.graph.ScalarWeight;
28import org.onosproject.core.ApplicationId;
29import org.onosproject.core.CoreService;
30import org.onosproject.net.ChannelSpacing;
31import org.onosproject.net.ConnectPoint;
32import org.onosproject.net.DefaultPath;
33import org.onosproject.net.Device;
34import org.onosproject.net.DeviceId;
35import org.onosproject.net.Direction;
36import org.onosproject.net.GridType;
37import org.onosproject.net.Link;
38import org.onosproject.net.OchSignal;
39import org.onosproject.net.Path;
40import org.onosproject.net.behaviour.PowerConfig;
41import org.onosproject.net.device.DeviceService;
42import org.onosproject.net.intent.Intent;
43import org.onosproject.net.intent.IntentEvent;
44import org.onosproject.net.intent.IntentId;
45import org.onosproject.net.intent.IntentListener;
46import org.onosproject.net.intent.IntentService;
47import org.onosproject.net.link.LinkService;
48import org.onosproject.net.provider.ProviderId;
49import org.onosproject.odtn.GnpyService;
50import org.onosproject.store.service.AtomicCounter;
51import org.onosproject.store.service.StorageService;
52import org.osgi.service.component.annotations.Activate;
53import org.osgi.service.component.annotations.Component;
54import org.osgi.service.component.annotations.Reference;
55import org.osgi.service.component.annotations.ReferenceCardinality;
56import org.slf4j.Logger;
57import org.slf4j.LoggerFactory;
58
59import javax.ws.rs.core.MediaType;
60import java.io.ByteArrayInputStream;
61import java.io.ByteArrayOutputStream;
62import java.io.IOException;
63import java.util.ArrayList;
64import java.util.HashMap;
65import java.util.Iterator;
66import java.util.List;
67import java.util.Map;
68import java.util.Set;
69import java.util.stream.Collectors;
70import java.util.stream.StreamSupport;
71
72import static java.lang.Math.log10;
73import static org.onosproject.net.ChannelSpacing.CHL_50GHZ;
74import static org.onosproject.net.ChannelSpacing.CHL_6P25GHZ;
75import static org.onosproject.net.optical.util.OpticalIntentUtility.createOpticalIntent;
76
77/**
78 * Implementation of GnpyService.
79 */
80@Beta
81@Component(immediate = true, service = GnpyService.class)
82public class GnpyManager implements GnpyService {
83
84 private final Logger log = LoggerFactory.getLogger(getClass());
85
86 @Reference(cardinality = ReferenceCardinality.MANDATORY)
87 protected IntentService intentService;
88
89 @Reference(cardinality = ReferenceCardinality.MANDATORY)
90 protected DeviceService deviceService;
91
92 @Reference(cardinality = ReferenceCardinality.MANDATORY)
93 protected CoreService coreService;
94
95 @Reference(cardinality = ReferenceCardinality.MANDATORY)
96 protected LinkService linkService;
97
98 @Reference(cardinality = ReferenceCardinality.MANDATORY)
99 protected StorageService storageService;
100
101 HttpUtil gnpyHttpUtil;
102
103 private static final String APP_ID = "org.onosproject.odtn-service";
104
105 private static final ProviderId PROVIDER_ID = new ProviderId("odtn", "gnpy");
106
107 private ApplicationId appId;
108
109 private AtomicCounter counter;
110
111 private Map<IntentId, GnpyPowerInfo> intentsPowerMap = new HashMap<>();
112
113 @Activate
114 protected void activate() {
115 log.info("Started");
116 appId = coreService.getAppId(APP_ID);
117 counter = storageService.getAtomicCounter("GNPy-connection-counter");
118 }
119
120
121 @Override
122 public boolean connectGnpy(String protocol, String ip, String port, String username, String password) {
123 gnpyHttpUtil = new HttpUtil(protocol, ip, port);
124 gnpyHttpUtil.connect(username, password);
125 return gnpyHttpUtil.get("/gnpy-experimental", MediaType.APPLICATION_JSON_TYPE) != null;
126 }
127
128 @Override
129 public boolean disconnectGnpy() {
130 gnpyHttpUtil.disconnect();
131 gnpyHttpUtil = null;
132 return true;
133 }
134
135 @Override
136 public boolean isConnected() {
137 return gnpyHttpUtil != null;
138 }
139
140 @Override
141 public Pair<IntentId, Double> obtainConnectivity(ConnectPoint ingress, ConnectPoint egress, boolean bidirectional) {
142 ByteArrayOutputStream connectivityRequest = createGnpyRequest(ingress, egress, bidirectional);
143 String response = gnpyHttpUtil.post(null, "/gnpy-experimental",
144 new ByteArrayInputStream(connectivityRequest.toByteArray()),
145 MediaType.APPLICATION_JSON_TYPE, String.class);
146 ObjectMapper om = new ObjectMapper();
147 final ObjectReader reader = om.reader();
148 JsonNode jsonNode;
149 try {
150 jsonNode = reader.readTree(response);
151 if (jsonNode == null) {
152 log.error("JsonNode is null for response {}", response);
153 return null;
154 }
155 log.info("Response {}", response);
156 String bestPath;
157 try {
158 bestPath = getBestOsnrPathKey(jsonNode);
159 } catch (IllegalStateException e) {
160 log.error("Exception while contacting GNPy", e);
161 return null;
162 }
163 OchSignal ochSignal = createOchSignal(jsonNode);
164 Map<DeviceId, Double> deviceAtoBPowerMap = new HashMap<>();
165 Map<DeviceId, Double> deviceBtoAPowerMap = new HashMap<>();
166 //TODO this list is currently only populated in the forward direction
167 List<DeviceId> deviceIds = getDeviceAndPopulatePowerMap(jsonNode, deviceAtoBPowerMap,
168 deviceBtoAPowerMap, bestPath);
169 Path suggestedPath = createSuggestedPath(deviceIds);
170 log.info("Suggested path {}", suggestedPath);
171
172 Intent intent = createOpticalIntent(ingress, egress, deviceService,
173 null, appId, bidirectional, ochSignal, suggestedPath);
174
175 intentsPowerMap.put(intent.id(), new GnpyPowerInfo(deviceAtoBPowerMap, deviceBtoAPowerMap,
176 getLaunchPower(jsonNode), suggestedPath.links(),
177 ingress, egress, ochSignal));
178 intentService.submit(intent);
179 return Pair.of(intent.id(), getOsnr(jsonNode, bestPath));
180 } catch (IOException e) {
181 log.error("Exception while reading response {}", response, e);
182 return null;
183 }
184 }
185
186 private String getBestOsnrPathKey(JsonNode connectivityReply) throws IllegalStateException {
187 Double bestOsnr = -100.0;
188 String bestPathId = "";
189 if (connectivityReply.get("result").asText().contains("Service error")) {
190 throw new IllegalStateException(connectivityReply.get("result").asText());
191 }
192 Iterator<JsonNode> paths = connectivityReply.get("result").get("response")
193 .elements();
194 while (paths.hasNext()) {
195 JsonNode path = paths.next();
196 String respId = path.get("response-id").asText();
197 double osnr = getOsnr(connectivityReply, respId);
198 if (osnr > bestOsnr) {
199 bestOsnr = osnr;
200 bestPathId = respId;
201 }
202 }
203 return bestPathId;
204 }
205
206 protected Path createSuggestedPath(List<DeviceId> deviceIds) {
207 List<Link> listLinks = new ArrayList<>();
208 for (int i = 0; i < deviceIds.size() - 1; i++) {
209 Set<Link> links = linkService.getDeviceLinks(deviceIds.get(i));
210
211 for (Link link : links) {
212 if (link.dst().deviceId().equals(deviceIds.get(i + 1))) {
213 listLinks.add(link);
214 }
215 }
216 }
217 return new DefaultPath(PROVIDER_ID, listLinks, new ScalarWeight(1));
218
219 }
220
221 protected List<DeviceId> getDeviceAndPopulatePowerMap(JsonNode connectivityReply,
222 Map<DeviceId, Double> deviceAtoBPowerMap,
223 Map<DeviceId, Double> deviceBtoAPowerMap,
224 String name) {
225 List<DeviceId> deviceIds = new ArrayList<>();
226 if (connectivityReply.has("result")
227 && connectivityReply.get("result").has("response")) {
228 JsonNode response = connectivityReply.get("result").get("response");
229 //getting the a-b path.
230 Iterator<JsonNode> paths = connectivityReply.get("result").get("response")
231 .elements();
232 while (paths.hasNext()) {
233 JsonNode path = paths.next();
234 if (path.get("response-id").asText().equals(name)) {
235 Iterator<JsonNode> elements = path.get("path-properties")
236 .get("reversed-path-route-objects").elements();
237 Iterable<JsonNode> iterable = () -> elements;
238 List<JsonNode> elementsList = StreamSupport
239 .stream(iterable.spliterator(), false)
240 .collect(Collectors.toList());
241 Iterator<JsonNode> reversePathRoute = path.get("path-properties")
242 .get("reversed-path-route-objects").elements();
243 Iterable<JsonNode> reversedIterable = () -> reversePathRoute;
244 List<JsonNode> reversedElementsList = StreamSupport
245 .stream(reversedIterable.spliterator(), false)
246 .collect(Collectors.toList());
247 for (int i = 0; i < elementsList.size() - 1; i++) {
248 if (elementsList.get(i).get("path-route-object").has("num-unnum-hop")) {
249 String elementId = elementsList.get(i).get("path-route-object")
250 .get("num-unnum-hop").get("node-id")
251 .asText();
252 //TODO this is a workaround until we understand better the
253 // topology mapping between ONOS and GNPy
254 if (elementId.startsWith("netconf:")) {
255 double power = -99;
256 if (!elementsList.get(i).get("path-route-object")
257 .get("num-unnum-hop").get("gnpy-node-type")
258 .asText().equals("transceiver")) {
259 power = getPerHopPower(elementsList.get(i + 2));
260 }
261 deviceAtoBPowerMap.put(DeviceId.deviceId(elementId), power);
262 for (int j = 0; j < reversedElementsList.size() - 1; j++) {
263 if (reversedElementsList.get(j).get("path-route-object").has("num-unnum-hop")) {
264 String reversedElementId = reversedElementsList.get(j).get("path-route-object")
265 .get("num-unnum-hop").get("node-id")
266 .asText();
267 double reversePower = -99;
268 if (reversedElementId.equals(elementId)) {
269 reversePower = getPerHopPower(reversedElementsList.get(j + 2));
270 deviceBtoAPowerMap.put(DeviceId.deviceId(elementId), reversePower);
271 }
272 }
273 }
274 deviceIds.add(DeviceId.deviceId(elementId));
275 }
276 }
277 }
278 break;
279 }
280 }
281 } else {
282 log.warn("Can't retrieve devices {}", connectivityReply);
283 }
284 return deviceIds;
285 }
286
287 protected OchSignal createOchSignal(JsonNode connectivityReply) throws IllegalArgumentException {
288 if (connectivityReply.has("result")
289 && connectivityReply.get("result").has("response")) {
290 Iterator<JsonNode> elements = connectivityReply.get("result").get("response").elements()
291 .next().get("path-properties").get("path-route-objects").elements();
292 Iterable<JsonNode> iterable = () -> elements;
293 List<JsonNode> elementsList = StreamSupport
294 .stream(iterable.spliterator(), false)
295 .collect(Collectors.toList());
296 int n = 0;
297 int m = 0;
298 for (JsonNode node : elementsList) {
299 if (node.get("path-route-object").has("label-hop")) {
300 n = node.get("path-route-object").get("label-hop").get("N").asInt();
301 m = node.get("path-route-object").get("label-hop").get("M").asInt();
302 break;
303 }
304 }
305 int offset = 193100;
306
307 double centralFreq = offset + (n * CHL_6P25GHZ.frequency().asGHz());
308 try {
309 int multiplier = getMultplier(centralFreq, GridType.DWDM, CHL_50GHZ);
310 return new OchSignal(GridType.DWDM, CHL_50GHZ, multiplier, 4);
311 } catch (RuntimeException e) {
312 /* catching RuntimeException as both NullPointerException (thrown by
313 * checkNotNull) and IllegalArgumentException (thrown by checkArgument)
314 * are subclasses of RuntimeException.
315 */
316 throw new IllegalArgumentException(e);
317 }
318 }
319 return null;
320 }
321
322 protected double getLaunchPower(JsonNode connectivityReply) {
323 double power = -99;
324 if (connectivityReply.has("result")
325 && connectivityReply.get("result").has("response")) {
326 Iterator<JsonNode> elements = connectivityReply.get("result").get("response")
327 .elements().next().get("path-properties").get("path-metric").elements();
328 Iterable<JsonNode> iterable = () -> elements;
329 List<JsonNode> elementsList = StreamSupport
330 .stream(iterable.spliterator(), false)
331 .collect(Collectors.toList());
332 for (JsonNode node : elementsList) {
333 if (node.has("metric-type") &&
334 node.get("metric-type").asText().equals("reference_power")) {
335 power = node.get("accumulative-value").asDouble();
336 break;
337 }
338 }
339 }
340 return 10 * log10(power * 1000);
341 }
342
343 protected double getPerHopPower(JsonNode pathRouteObj) {
344 double power = -99;
345 if (pathRouteObj.get("path-route-object").has("target-channel-power")) {
346 power = pathRouteObj.get("path-route-object")
347 .get("target-channel-power").get("value")
348 .asDouble();
349 }
350 return power;
351 }
352
353 protected double getOsnr(JsonNode connectivityReply, String name) {
354 double osnr = -1;
355 if (connectivityReply.has("result")
356 && connectivityReply.get("result").has("response")) {
357 Iterator<JsonNode> paths = connectivityReply.get("result").get("response")
358 .elements();
359 while (paths.hasNext()) {
360 JsonNode path = paths.next();
361 if (path.get("response-id").asText().equals(name)) {
362 Iterator<JsonNode> elements = path.get("path-properties").get("path-metric").elements();
363 Iterable<JsonNode> iterable = () -> elements;
364 List<JsonNode> elementsList = StreamSupport
365 .stream(iterable.spliterator(), false)
366 .collect(Collectors.toList());
367 for (JsonNode node : elementsList) {
368 if (node.has("metric-type") &&
369 node.get("metric-type").asText().equals("OSNR-0.1nm")) {
370 osnr = node.get("accumulative-value").asDouble();
371 break;
372 }
373 }
374 if (osnr != -1) {
375 break;
376 }
377 }
378 }
379 }
380 return osnr;
381 }
382
383 private int getMultplier(double wavelength, GridType gridType, ChannelSpacing channelSpacing) {
384 long baseFreq;
385 switch (gridType) {
386 case DWDM:
387 baseFreq = 193100;
388 break;
389 case CWDM:
390 case FLEX:
391 case UNKNOWN:
392 default:
393 baseFreq = 0L;
394 break;
395 }
396 return (int) ((wavelength - baseFreq) / (channelSpacing.frequency().asGHz()));
397 }
398
399 protected ByteArrayOutputStream createGnpyRequest(ConnectPoint ingress,
400 ConnectPoint egress, boolean bidirectional) {
401 /*
402 {
403 "path-request": [
404 {
405 "request-id": "first",
406 "source": "trx-Amsterdam",
407 "destination": "trx-Bremen",
408 "src-tp-id": "trx-Amsterdam",
409 "dst-tp-id": "trx-Bremen",
410 "bidirectional": false,
411 "path-constraints": {
412 "te-bandwidth": {
413 "technology": "flexi-grid",
414 "trx_type": "Voyager",
415 "trx_mode": null,
416 "effective-freq-slot": [
417 {
418 "N": "null",
419 "M": "null"
420 }
421 ],
422 "spacing": 50000000000.0,
423 "max-nb-of-channel": null,
424 "output-power": null,
425 "path_bandwidth": 100000000000.0
426 }
427 }
428 }
429 ]
430 }
431 */
432 ByteArrayOutputStream stream = new ByteArrayOutputStream();
433 try {
434 JsonGenerator generator = getJsonGenerator(stream);
435 generator.writeStartObject();
436 generator.writeArrayFieldStart("path-request");
437 generator.writeStartObject();
438 generator.writeStringField("request-id", "onos-" + counter.getAndIncrement());
439 generator.writeStringField("source", ingress.deviceId().toString());
440 generator.writeStringField("destination", egress.deviceId().toString());
441 generator.writeStringField("src-tp-id", ingress.deviceId().toString());
442 generator.writeStringField("dst-tp-id", egress.deviceId().toString());
443 generator.writeBooleanField("bidirectional", bidirectional);
444 generator.writeObjectFieldStart("path-constraints");
445 generator.writeObjectFieldStart("te-bandwidth");
446 generator.writeStringField("technology", "flexi-grid");
447 generator.writeStringField("trx_type", "Cassini"); //TODO make variable
448 generator.writeNullField("trx_mode");
449 generator.writeArrayFieldStart("effective-freq-slot");
450 generator.writeStartObject();
451 generator.writeStringField("N", "null");
452 generator.writeStringField("M", "null");
453 generator.writeEndObject();
454 generator.writeEndArray();
455 generator.writeNumberField("spacing", 50000000000.0);
456 generator.writeNullField("max-nb-of-channel");
457 generator.writeNullField("output-power");
458 generator.writeNumberField("path_bandwidth", 100000000000.0);
459 generator.writeEndObject();
460 generator.writeEndObject();
461 generator.writeEndObject();
462 generator.writeEndArray();
463 generator.writeEndObject();
464 generator.close();
465 return stream;
466 } catch (IOException e) {
467 log.error("Cant' create json", e);
468 }
469 return stream;
470
471 }
472
473 private JsonGenerator getJsonGenerator(ByteArrayOutputStream stream) throws IOException {
474 JsonFactory factory = new JsonFactory();
475 return factory.createGenerator(stream, JsonEncoding.UTF8);
476 }
477
478 /**
479 * Internal listener for tracking the intent deletion events.
480 */
481 private class InternalIntentListener implements IntentListener {
482
483 @Override
484 public boolean isRelevant(IntentEvent event) {
485 return intentsPowerMap.keySet().contains(event.subject().id());
486 }
487
488 @Override
489 public void event(IntentEvent event) {
490 setPathPower(event.subject());
491
492 }
493 }
494
495 private void setPathPower(Intent intent) {
496 GnpyPowerInfo powerInfo = intentsPowerMap.get(intent.id());
497 for (Link link : powerInfo.path()) {
498 Device ingressDev = deviceService.getDevice(link.src().deviceId());
499 if (ingressDev.is(PowerConfig.class)) {
500 if (powerInfo.deviceAtoBPowerMap().get(link.src().deviceId()) != -99) {
501 log.info("Configuring power {} for {}",
502 powerInfo.deviceAtoBPowerMap().get(link.src().deviceId()),
503 link.src().deviceId());
504 ingressDev.as(PowerConfig.class)
505 .setTargetPower(link.src().port(), powerInfo.ochSignal(),
506 powerInfo.deviceAtoBPowerMap()
507 .get(link.src().deviceId()));
508 } else {
509 log.warn("Can't determine power for {}", link.src().deviceId());
510 }
511 }
512 Device egressDev = deviceService.getDevice(link.dst().deviceId());
513 if (egressDev.is(PowerConfig.class)) {
514 if (powerInfo.deviceBtoAPowerMap().get(link.dst().deviceId()) != -99) {
515 log.info("Configuring power {} for {}",
516 powerInfo.deviceBtoAPowerMap().get(link.dst().deviceId()),
517 link.dst().deviceId());
518 egressDev.as(PowerConfig.class)
519 .setTargetPower(link.dst().port(), powerInfo.ochSignal(),
520 powerInfo.deviceBtoAPowerMap()
521 .get(link.dst().deviceId()));
522 } else {
523 log.warn("Can't determine power for {}", link.dst().deviceId());
524 }
525 }
526 }
527 Device ingressDevice = deviceService.getDevice(powerInfo.ingress().deviceId());
528 if (ingressDevice.is(PowerConfig.class)) {
529 if (powerInfo.launchPower() != -99) {
530 log.info("Configuring ingress with power {} for {}",
531 powerInfo.launchPower(), ingressDevice);
532 ingressDevice.as(PowerConfig.class)
533 .setTargetPower(powerInfo.ingress().port(), Direction.ALL, powerInfo.launchPower());
534 }
535 }
536 Device egressDevice = deviceService.getDevice(powerInfo.ingress().deviceId());
537 if (egressDevice.is(PowerConfig.class)) {
538 if (powerInfo.launchPower() != -99) {
539 log.info("Configuring egress with power {} for {}",
540 powerInfo.launchPower(), ingressDevice);
541 ingressDevice.as(PowerConfig.class)
542 .setTargetPower(powerInfo.ingress().port(), Direction.ALL, powerInfo.launchPower());
543 }
544 }
545 }
546}