blob: a474dfa6835432ec1979d9424add91cbbf0ab16b [file] [log] [blame]
/*
* Copyright 2018-present Open Networking Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onosproject.odtn.impl;
import com.fasterxml.jackson.core.JsonEncoding;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectReader;
import com.google.common.annotations.Beta;
import org.apache.commons.lang3.tuple.Pair;
import org.onlab.graph.ScalarWeight;
import org.onosproject.core.ApplicationId;
import org.onosproject.core.CoreService;
import org.onosproject.net.ChannelSpacing;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.DefaultPath;
import org.onosproject.net.Device;
import org.onosproject.net.DeviceId;
import org.onosproject.net.Direction;
import org.onosproject.net.GridType;
import org.onosproject.net.Link;
import org.onosproject.net.OchSignal;
import org.onosproject.net.Path;
import org.onosproject.net.behaviour.PowerConfig;
import org.onosproject.net.device.DeviceService;
import org.onosproject.net.intent.Intent;
import org.onosproject.net.intent.IntentEvent;
import org.onosproject.net.intent.IntentId;
import org.onosproject.net.intent.IntentListener;
import org.onosproject.net.intent.IntentService;
import org.onosproject.net.link.LinkService;
import org.onosproject.net.provider.ProviderId;
import org.onosproject.odtn.GnpyService;
import org.onosproject.store.service.AtomicCounter;
import org.onosproject.store.service.StorageService;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.annotations.ReferenceCardinality;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.ws.rs.core.MediaType;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import static java.lang.Math.log10;
import static org.onosproject.net.ChannelSpacing.CHL_50GHZ;
import static org.onosproject.net.ChannelSpacing.CHL_6P25GHZ;
import static org.onosproject.net.optical.util.OpticalIntentUtility.createOpticalIntent;
/**
* Implementation of GnpyService.
*/
@Beta
@Component(immediate = true, service = GnpyService.class)
public class GnpyManager implements GnpyService {
private final Logger log = LoggerFactory.getLogger(getClass());
@Reference(cardinality = ReferenceCardinality.MANDATORY)
protected IntentService intentService;
@Reference(cardinality = ReferenceCardinality.MANDATORY)
protected DeviceService deviceService;
@Reference(cardinality = ReferenceCardinality.MANDATORY)
protected CoreService coreService;
@Reference(cardinality = ReferenceCardinality.MANDATORY)
protected LinkService linkService;
@Reference(cardinality = ReferenceCardinality.MANDATORY)
protected StorageService storageService;
HttpUtil gnpyHttpUtil;
private static final String APP_ID = "org.onosproject.odtn-service";
private static final ProviderId PROVIDER_ID = new ProviderId("odtn", "gnpy");
private ApplicationId appId;
private AtomicCounter counter;
private Map<IntentId, GnpyPowerInfo> intentsPowerMap = new HashMap<>();
@Activate
protected void activate() {
log.info("Started");
appId = coreService.getAppId(APP_ID);
counter = storageService.getAtomicCounter("GNPy-connection-counter");
}
@Override
public boolean connectGnpy(String protocol, String ip, String port, String username, String password) {
gnpyHttpUtil = new HttpUtil(protocol, ip, port);
gnpyHttpUtil.connect(username, password);
return gnpyHttpUtil.get("/gnpy-experimental", MediaType.APPLICATION_JSON_TYPE) != null;
}
@Override
public boolean disconnectGnpy() {
gnpyHttpUtil.disconnect();
gnpyHttpUtil = null;
return true;
}
@Override
public boolean isConnected() {
return gnpyHttpUtil != null;
}
@Override
public Pair<IntentId, Double> obtainConnectivity(ConnectPoint ingress, ConnectPoint egress, boolean bidirectional) {
ByteArrayOutputStream connectivityRequest = createGnpyRequest(ingress, egress, bidirectional);
String response = gnpyHttpUtil.post(null, "/gnpy-experimental",
new ByteArrayInputStream(connectivityRequest.toByteArray()),
MediaType.APPLICATION_JSON_TYPE, String.class);
ObjectMapper om = new ObjectMapper();
final ObjectReader reader = om.reader();
JsonNode jsonNode;
try {
jsonNode = reader.readTree(response);
if (jsonNode == null) {
log.error("JsonNode is null for response {}", response);
return null;
}
log.info("Response {}", response);
String bestPath;
try {
bestPath = getBestOsnrPathKey(jsonNode);
} catch (IllegalStateException e) {
log.error("Exception while contacting GNPy", e);
return null;
}
OchSignal ochSignal = createOchSignal(jsonNode);
Map<DeviceId, Double> deviceAtoBPowerMap = new HashMap<>();
Map<DeviceId, Double> deviceBtoAPowerMap = new HashMap<>();
//TODO this list is currently only populated in the forward direction
List<DeviceId> deviceIds = getDeviceAndPopulatePowerMap(jsonNode, deviceAtoBPowerMap,
deviceBtoAPowerMap, bestPath);
Path suggestedPath = createSuggestedPath(deviceIds);
log.info("Suggested path {}", suggestedPath);
Intent intent = createOpticalIntent(ingress, egress, deviceService,
null, appId, bidirectional, ochSignal, suggestedPath);
intentsPowerMap.put(intent.id(), new GnpyPowerInfo(deviceAtoBPowerMap, deviceBtoAPowerMap,
getLaunchPower(jsonNode), suggestedPath.links(),
ingress, egress, ochSignal));
intentService.submit(intent);
return Pair.of(intent.id(), getOsnr(jsonNode, bestPath));
} catch (IOException e) {
log.error("Exception while reading response {}", response, e);
return null;
}
}
private String getBestOsnrPathKey(JsonNode connectivityReply) throws IllegalStateException {
Double bestOsnr = -100.0;
String bestPathId = "";
if (connectivityReply.get("result").asText().contains("Service error")) {
throw new IllegalStateException(connectivityReply.get("result").asText());
}
Iterator<JsonNode> paths = connectivityReply.get("result").get("response")
.elements();
while (paths.hasNext()) {
JsonNode path = paths.next();
String respId = path.get("response-id").asText();
double osnr = getOsnr(connectivityReply, respId);
if (osnr > bestOsnr) {
bestOsnr = osnr;
bestPathId = respId;
}
}
return bestPathId;
}
protected Path createSuggestedPath(List<DeviceId> deviceIds) {
List<Link> listLinks = new ArrayList<>();
for (int i = 0; i < deviceIds.size() - 1; i++) {
Set<Link> links = linkService.getDeviceLinks(deviceIds.get(i));
for (Link link : links) {
if (link.dst().deviceId().equals(deviceIds.get(i + 1))) {
listLinks.add(link);
}
}
}
return new DefaultPath(PROVIDER_ID, listLinks, new ScalarWeight(1));
}
protected List<DeviceId> getDeviceAndPopulatePowerMap(JsonNode connectivityReply,
Map<DeviceId, Double> deviceAtoBPowerMap,
Map<DeviceId, Double> deviceBtoAPowerMap,
String name) {
List<DeviceId> deviceIds = new ArrayList<>();
if (connectivityReply.has("result")
&& connectivityReply.get("result").has("response")) {
JsonNode response = connectivityReply.get("result").get("response");
//getting the a-b path.
Iterator<JsonNode> paths = connectivityReply.get("result").get("response")
.elements();
while (paths.hasNext()) {
JsonNode path = paths.next();
if (path.get("response-id").asText().equals(name)) {
Iterator<JsonNode> elements = path.get("path-properties")
.get("reversed-path-route-objects").elements();
Iterable<JsonNode> iterable = () -> elements;
List<JsonNode> elementsList = StreamSupport
.stream(iterable.spliterator(), false)
.collect(Collectors.toList());
Iterator<JsonNode> reversePathRoute = path.get("path-properties")
.get("reversed-path-route-objects").elements();
Iterable<JsonNode> reversedIterable = () -> reversePathRoute;
List<JsonNode> reversedElementsList = StreamSupport
.stream(reversedIterable.spliterator(), false)
.collect(Collectors.toList());
for (int i = 0; i < elementsList.size() - 1; i++) {
if (elementsList.get(i).get("path-route-object").has("num-unnum-hop")) {
String elementId = elementsList.get(i).get("path-route-object")
.get("num-unnum-hop").get("node-id")
.asText();
//TODO this is a workaround until we understand better the
// topology mapping between ONOS and GNPy
if (elementId.startsWith("netconf:")) {
double power = -99;
if (!elementsList.get(i).get("path-route-object")
.get("num-unnum-hop").get("gnpy-node-type")
.asText().equals("transceiver")) {
power = getPerHopPower(elementsList.get(i + 2));
}
deviceAtoBPowerMap.put(DeviceId.deviceId(elementId), power);
for (int j = 0; j < reversedElementsList.size() - 1; j++) {
if (reversedElementsList.get(j).get("path-route-object").has("num-unnum-hop")) {
String reversedElementId = reversedElementsList.get(j).get("path-route-object")
.get("num-unnum-hop").get("node-id")
.asText();
double reversePower = -99;
if (reversedElementId.equals(elementId)) {
reversePower = getPerHopPower(reversedElementsList.get(j + 2));
deviceBtoAPowerMap.put(DeviceId.deviceId(elementId), reversePower);
}
}
}
deviceIds.add(DeviceId.deviceId(elementId));
}
}
}
break;
}
}
} else {
log.warn("Can't retrieve devices {}", connectivityReply);
}
return deviceIds;
}
protected OchSignal createOchSignal(JsonNode connectivityReply) throws IllegalArgumentException {
if (connectivityReply.has("result")
&& connectivityReply.get("result").has("response")) {
Iterator<JsonNode> elements = connectivityReply.get("result").get("response").elements()
.next().get("path-properties").get("path-route-objects").elements();
Iterable<JsonNode> iterable = () -> elements;
List<JsonNode> elementsList = StreamSupport
.stream(iterable.spliterator(), false)
.collect(Collectors.toList());
int n = 0;
int m = 0;
for (JsonNode node : elementsList) {
if (node.get("path-route-object").has("label-hop")) {
n = node.get("path-route-object").get("label-hop").get("N").asInt();
m = node.get("path-route-object").get("label-hop").get("M").asInt();
break;
}
}
int offset = 193100;
double centralFreq = offset + (n * CHL_6P25GHZ.frequency().asGHz());
try {
int multiplier = getMultplier(centralFreq, GridType.DWDM, CHL_50GHZ);
return new OchSignal(GridType.DWDM, CHL_50GHZ, multiplier, 4);
} catch (RuntimeException e) {
/* catching RuntimeException as both NullPointerException (thrown by
* checkNotNull) and IllegalArgumentException (thrown by checkArgument)
* are subclasses of RuntimeException.
*/
throw new IllegalArgumentException(e);
}
}
return null;
}
protected double getLaunchPower(JsonNode connectivityReply) {
double power = -99;
if (connectivityReply.has("result")
&& connectivityReply.get("result").has("response")) {
Iterator<JsonNode> elements = connectivityReply.get("result").get("response")
.elements().next().get("path-properties").get("path-metric").elements();
Iterable<JsonNode> iterable = () -> elements;
List<JsonNode> elementsList = StreamSupport
.stream(iterable.spliterator(), false)
.collect(Collectors.toList());
for (JsonNode node : elementsList) {
if (node.has("metric-type") &&
node.get("metric-type").asText().equals("reference_power")) {
power = node.get("accumulative-value").asDouble();
break;
}
}
}
return 10 * log10(power * 1000);
}
protected double getPerHopPower(JsonNode pathRouteObj) {
double power = -99;
if (pathRouteObj.get("path-route-object").has("target-channel-power")) {
power = pathRouteObj.get("path-route-object")
.get("target-channel-power").get("value")
.asDouble();
}
return power;
}
protected double getOsnr(JsonNode connectivityReply, String name) {
double osnr = -1;
if (connectivityReply.has("result")
&& connectivityReply.get("result").has("response")) {
Iterator<JsonNode> paths = connectivityReply.get("result").get("response")
.elements();
while (paths.hasNext()) {
JsonNode path = paths.next();
if (path.get("response-id").asText().equals(name)) {
Iterator<JsonNode> elements = path.get("path-properties").get("path-metric").elements();
Iterable<JsonNode> iterable = () -> elements;
List<JsonNode> elementsList = StreamSupport
.stream(iterable.spliterator(), false)
.collect(Collectors.toList());
for (JsonNode node : elementsList) {
if (node.has("metric-type") &&
node.get("metric-type").asText().equals("OSNR-0.1nm")) {
osnr = node.get("accumulative-value").asDouble();
break;
}
}
if (osnr != -1) {
break;
}
}
}
}
return osnr;
}
private int getMultplier(double wavelength, GridType gridType, ChannelSpacing channelSpacing) {
long baseFreq;
switch (gridType) {
case DWDM:
baseFreq = 193100;
break;
case CWDM:
case FLEX:
case UNKNOWN:
default:
baseFreq = 0L;
break;
}
return (int) ((wavelength - baseFreq) / (channelSpacing.frequency().asGHz()));
}
protected ByteArrayOutputStream createGnpyRequest(ConnectPoint ingress,
ConnectPoint egress, boolean bidirectional) {
/*
{
"path-request": [
{
"request-id": "first",
"source": "trx-Amsterdam",
"destination": "trx-Bremen",
"src-tp-id": "trx-Amsterdam",
"dst-tp-id": "trx-Bremen",
"bidirectional": false,
"path-constraints": {
"te-bandwidth": {
"technology": "flexi-grid",
"trx_type": "Voyager",
"trx_mode": null,
"effective-freq-slot": [
{
"N": "null",
"M": "null"
}
],
"spacing": 50000000000.0,
"max-nb-of-channel": null,
"output-power": null,
"path_bandwidth": 100000000000.0
}
}
}
]
}
*/
ByteArrayOutputStream stream = new ByteArrayOutputStream();
try {
JsonGenerator generator = getJsonGenerator(stream);
generator.writeStartObject();
generator.writeArrayFieldStart("path-request");
generator.writeStartObject();
generator.writeStringField("request-id", "onos-" + counter.getAndIncrement());
generator.writeStringField("source", ingress.deviceId().toString());
generator.writeStringField("destination", egress.deviceId().toString());
generator.writeStringField("src-tp-id", ingress.deviceId().toString());
generator.writeStringField("dst-tp-id", egress.deviceId().toString());
generator.writeBooleanField("bidirectional", bidirectional);
generator.writeObjectFieldStart("path-constraints");
generator.writeObjectFieldStart("te-bandwidth");
generator.writeStringField("technology", "flexi-grid");
generator.writeStringField("trx_type", "Cassini"); //TODO make variable
generator.writeNullField("trx_mode");
generator.writeArrayFieldStart("effective-freq-slot");
generator.writeStartObject();
generator.writeStringField("N", "null");
generator.writeStringField("M", "null");
generator.writeEndObject();
generator.writeEndArray();
generator.writeNumberField("spacing", 50000000000.0);
generator.writeNullField("max-nb-of-channel");
generator.writeNullField("output-power");
generator.writeNumberField("path_bandwidth", 100000000000.0);
generator.writeEndObject();
generator.writeEndObject();
generator.writeEndObject();
generator.writeEndArray();
generator.writeEndObject();
generator.close();
return stream;
} catch (IOException e) {
log.error("Cant' create json", e);
}
return stream;
}
private JsonGenerator getJsonGenerator(ByteArrayOutputStream stream) throws IOException {
JsonFactory factory = new JsonFactory();
return factory.createGenerator(stream, JsonEncoding.UTF8);
}
/**
* Internal listener for tracking the intent deletion events.
*/
private class InternalIntentListener implements IntentListener {
@Override
public boolean isRelevant(IntentEvent event) {
return intentsPowerMap.keySet().contains(event.subject().id());
}
@Override
public void event(IntentEvent event) {
setPathPower(event.subject());
}
}
private void setPathPower(Intent intent) {
GnpyPowerInfo powerInfo = intentsPowerMap.get(intent.id());
for (Link link : powerInfo.path()) {
Device ingressDev = deviceService.getDevice(link.src().deviceId());
if (ingressDev.is(PowerConfig.class)) {
if (powerInfo.deviceAtoBPowerMap().get(link.src().deviceId()) != -99) {
log.info("Configuring power {} for {}",
powerInfo.deviceAtoBPowerMap().get(link.src().deviceId()),
link.src().deviceId());
ingressDev.as(PowerConfig.class)
.setTargetPower(link.src().port(), powerInfo.ochSignal(),
powerInfo.deviceAtoBPowerMap()
.get(link.src().deviceId()));
} else {
log.warn("Can't determine power for {}", link.src().deviceId());
}
}
Device egressDev = deviceService.getDevice(link.dst().deviceId());
if (egressDev.is(PowerConfig.class)) {
if (powerInfo.deviceBtoAPowerMap().get(link.dst().deviceId()) != -99) {
log.info("Configuring power {} for {}",
powerInfo.deviceBtoAPowerMap().get(link.dst().deviceId()),
link.dst().deviceId());
egressDev.as(PowerConfig.class)
.setTargetPower(link.dst().port(), powerInfo.ochSignal(),
powerInfo.deviceBtoAPowerMap()
.get(link.dst().deviceId()));
} else {
log.warn("Can't determine power for {}", link.dst().deviceId());
}
}
}
Device ingressDevice = deviceService.getDevice(powerInfo.ingress().deviceId());
if (ingressDevice.is(PowerConfig.class)) {
if (powerInfo.launchPower() != -99) {
log.info("Configuring ingress with power {} for {}",
powerInfo.launchPower(), ingressDevice);
ingressDevice.as(PowerConfig.class)
.setTargetPower(powerInfo.ingress().port(), Direction.ALL, powerInfo.launchPower());
}
}
Device egressDevice = deviceService.getDevice(powerInfo.ingress().deviceId());
if (egressDevice.is(PowerConfig.class)) {
if (powerInfo.launchPower() != -99) {
log.info("Configuring egress with power {} for {}",
powerInfo.launchPower(), ingressDevice);
ingressDevice.as(PowerConfig.class)
.setTargetPower(powerInfo.ingress().port(), Direction.ALL, powerInfo.launchPower());
}
}
}
}