GNPy integration
Installs the path with the best OSRn value according to gnpy, while also setting the power at each stage.
usage:
odtn-connect-gnpy-command http 127.0.0.1 8080 test test
odtn-gnpy-connection-command -b netconf:127.0.0.1:1101/1 netconf:127.0.0.1:1102/1
Change-Id: I82b6d57d07ab5a3873ce37eb44639ebaa85c733f
diff --git a/apps/odtn/service/BUILD b/apps/odtn/service/BUILD
index abf6ee6..5226c66 100644
--- a/apps/odtn/service/BUILD
+++ b/apps/odtn/service/BUILD
@@ -5,6 +5,17 @@
"//apps/yang:onos-apps-yang",
"//apps/optical-model:onos-apps-optical-model",
"//protocols/netconf/api:onos-protocols-netconf-api",
+ "@jersey_client//jar",
+ "@jersey_server//jar",
+ "@jersey_common//jar",
+ "@jersey_security//jar",
+ "@httpclient_osgi//jar",
+ "@httpcore_osgi//jar",
+ "@javax_ws_rs_api//jar",
+ "@hk2_api//jar",
+ "@aopalliance_repackaged//jar",
+ "@javax_inject//jar",
+ "//utils/rest:onlab-rest",
]
osgi_jar_with_tests(
diff --git a/apps/odtn/service/src/main/java/org/onosproject/odtn/GnpyService.java b/apps/odtn/service/src/main/java/org/onosproject/odtn/GnpyService.java
new file mode 100644
index 0000000..4016b3e
--- /dev/null
+++ b/apps/odtn/service/src/main/java/org/onosproject/odtn/GnpyService.java
@@ -0,0 +1,66 @@
+/*
+ * 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;
+
+import org.apache.commons.lang3.tuple.Pair;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.intent.IntentId;
+
+/**
+ * Interface for Integration with GNPy optical tool.
+ */
+public interface GnpyService {
+
+
+ /**
+ * Connectes to an instance of GNPY optical planning and simulation tool.
+ *
+ * @param protocol the protocol, eg HTTP or HTTPS
+ * @param ip the ip of the service
+ * @param port the port fo the service
+ * @param username the username of the service
+ * @param password the password.
+ * @return true if connection was successful
+ */
+ boolean connectGnpy(String protocol, String ip, String port, String username, String password);
+
+ /**
+ * Disconnects for the connected GNPy instance.
+ *
+ * @return true if successful
+ */
+ boolean disconnectGnpy();
+
+ /**
+ * Checks connectivity an instance of GNPY optical planning and simulation tool.
+ *
+ * @return true if connection was successful
+ */
+ boolean isConnected();
+
+ /**
+ * Obtains the best connectivity from A to B according to GSNR from GNPY, possibly bidirectional.
+ *
+ * @param ingress the ingress connect point
+ * @param egress the egress connect point
+ * @param bidirectional true if bidirectional connectivity is required.
+ * @return the Id of the intent that was installed as a result of the computation.
+ */
+ Pair<IntentId, Double> obtainConnectivity(ConnectPoint ingress, ConnectPoint egress, boolean bidirectional);
+
+
+}
diff --git a/apps/odtn/service/src/main/java/org/onosproject/odtn/cli/impl/ConnectGnpyCommand.java b/apps/odtn/service/src/main/java/org/onosproject/odtn/cli/impl/ConnectGnpyCommand.java
new file mode 100644
index 0000000..a397b9f
--- /dev/null
+++ b/apps/odtn/service/src/main/java/org/onosproject/odtn/cli/impl/ConnectGnpyCommand.java
@@ -0,0 +1,84 @@
+/*
+ * 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.cli.impl;
+
+import com.google.common.annotations.Beta;
+import com.google.common.base.Preconditions;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.odtn.GnpyService;
+import org.slf4j.Logger;
+
+import static org.slf4j.LoggerFactory.getLogger;
+
+@Beta
+@Service
+@Command(scope = "onos", name = "odtn-connect-gnpy-command",
+ description = "show tapi context command")
+public class ConnectGnpyCommand extends AbstractShellCommand {
+
+ private static final Logger log = getLogger(ConnectGnpyCommand.class);
+
+ @Argument(index = 0, name = "protocol",
+ description = "protocol for requests, e.g. HTTP or HTTPS",
+ required = true, multiValued = false)
+ String protocol = "";
+
+ @Argument(index = 1, name = "ip",
+ description = "Ip of GNPy instance",
+ required = true, multiValued = false)
+ String ip = "";
+
+ @Argument(index = 2, name = "port",
+ description = "Protocol of GNPy instance",
+ required = true, multiValued = false)
+ String port = "";
+
+ @Argument(index = 3, name = "username",
+ description = "Username of GNPy instance",
+ required = true, multiValued = false)
+ String username = "";
+
+ @Argument(index = 4, name = "password",
+ description = "Password of GNPy instance",
+ required = true, multiValued = false)
+ String password = "";
+
+ @Option(name = "-d", aliases = "--disconnect",
+ description = "If this argument is passed the connection with gNPY is removed.",
+ required = false, multiValued = false)
+ private boolean disconnect = false;
+
+ @Override
+ protected void doExecute() {
+ GnpyService service = get(GnpyService.class);
+ String msg;
+ if (disconnect) {
+ msg = service.disconnectGnpy() ? "gnpy disconnect" : "error in disconnecting gnpy";
+ } else {
+ Preconditions.checkNotNull(ip, "Ip must be specified");
+ Preconditions.checkNotNull(password, "password must be specified");
+ msg = service.connectGnpy(protocol, ip, port, username, password) ? "gnpy connected" :
+ "error in connecting gnpy, please check logs";
+ }
+ print(msg);
+ }
+
+}
diff --git a/apps/odtn/service/src/main/java/org/onosproject/odtn/cli/impl/GnpyConnectionRequestCommand.java b/apps/odtn/service/src/main/java/org/onosproject/odtn/cli/impl/GnpyConnectionRequestCommand.java
new file mode 100644
index 0000000..c434fdb
--- /dev/null
+++ b/apps/odtn/service/src/main/java/org/onosproject/odtn/cli/impl/GnpyConnectionRequestCommand.java
@@ -0,0 +1,95 @@
+/*
+ * 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.cli.impl;
+
+import com.google.common.annotations.Beta;
+import org.apache.commons.lang3.tuple.Pair;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.Option;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.cli.net.ConnectPointCompleter;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.intent.IntentId;
+import org.onosproject.odtn.GnpyService;
+import org.slf4j.Logger;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static org.slf4j.LoggerFactory.getLogger;
+
+@Beta
+@Service
+@Command(scope = "onos", name = "odtn-gnpy-connection-command",
+ description = "show tapi context command")
+public class GnpyConnectionRequestCommand extends AbstractShellCommand {
+
+ private static final Logger log = getLogger(GnpyConnectionRequestCommand.class);
+
+ @Argument(index = 0, name = "ingress",
+ description = "Ingress Device/Port Description",
+ required = true, multiValued = false)
+ @Completion(ConnectPointCompleter.class)
+ String ingressString = "";
+
+ @Argument(index = 1, name = "egress",
+ description = "Egress Device/Port Description",
+ required = true, multiValued = false)
+ @Completion(ConnectPointCompleter.class)
+ String egressString = "";
+
+ @Option(name = "-b", aliases = "--bidirectional",
+ description = "If this argument is passed the optical link created will be bidirectional, " +
+ "else the link will be unidirectional.",
+ required = false, multiValued = false)
+ private boolean bidirectional = false;
+
+ @Override
+ protected void doExecute() {
+ GnpyService service = get(GnpyService.class);
+
+ if (!service.isConnected()) {
+ print("gNPY is not connected, please issue `odtn-connect-gnpy-command` first");
+ return;
+ }
+
+ ConnectPoint ingress = createConnectPoint(ingressString);
+ ConnectPoint egress = createConnectPoint(egressString);
+
+ Pair<IntentId, Double> intentIdAndOsnr =
+ service.obtainConnectivity(ingress, egress, bidirectional);
+
+ if (intentIdAndOsnr != null) {
+ print("Optical Connectivity from %s to %s submitted through GNPy. \n", ingress, egress);
+ print("Expected GSNR %.2f dB", intentIdAndOsnr.getRight().doubleValue());
+ print("Intent: %s", intentIdAndOsnr.getLeft());
+ } else {
+ print("Error in submitting Optical Connectivity submitted through GNPy, please see logs");
+ }
+ }
+
+ private ConnectPoint createConnectPoint(String devicePortString) {
+ String[] splitted = devicePortString.split("/");
+
+ checkArgument(splitted.length == 2,
+ "Connect point must be in \"deviceUri/portNumber\" format");
+
+ return ConnectPoint.deviceConnectPoint(devicePortString);
+ }
+
+}
diff --git a/apps/odtn/service/src/main/java/org/onosproject/odtn/impl/GnpyManager.java b/apps/odtn/service/src/main/java/org/onosproject/odtn/impl/GnpyManager.java
new file mode 100644
index 0000000..a474dfa
--- /dev/null
+++ b/apps/odtn/service/src/main/java/org/onosproject/odtn/impl/GnpyManager.java
@@ -0,0 +1,546 @@
+/*
+ * 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());
+ }
+ }
+ }
+}
diff --git a/apps/odtn/service/src/main/java/org/onosproject/odtn/impl/GnpyPowerInfo.java b/apps/odtn/service/src/main/java/org/onosproject/odtn/impl/GnpyPowerInfo.java
new file mode 100644
index 0000000..2923ec0
--- /dev/null
+++ b/apps/odtn/service/src/main/java/org/onosproject/odtn/impl/GnpyPowerInfo.java
@@ -0,0 +1,127 @@
+/*
+ * 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.google.common.annotations.Beta;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Link;
+import org.onosproject.net.OchSignal;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * This class contains information given by gNPY for a given path.
+ */
+@Beta
+public final class GnpyPowerInfo {
+
+ private Map<DeviceId, Double> deviceAtoBPowerMap;
+ private Map<DeviceId, Double> deviceBtoAPowerMap;
+ private double launchPower;
+ private List<Link> path;
+ private ConnectPoint ingress;
+ private ConnectPoint egress;
+ private OchSignal ochSignal;
+
+ /**
+ * Creates the class with information.
+ *
+ * @param deviceAtoBPowerMap the power in a to b direction
+ * @param deviceBtoAPowerMap the power in b to a direction
+ * @param launchPower the power at the TXs
+ * @param path the pat
+ * @param ingress the ingress device (A)
+ * @param egress the egress device (B)
+ * @param ochSignal the signal
+ */
+ public GnpyPowerInfo(Map<DeviceId, Double> deviceAtoBPowerMap,
+ Map<DeviceId, Double> deviceBtoAPowerMap, double launchPower,
+ List<Link> path, ConnectPoint ingress, ConnectPoint egress,
+ OchSignal ochSignal) {
+ this.deviceAtoBPowerMap = deviceAtoBPowerMap;
+ this.deviceBtoAPowerMap = deviceBtoAPowerMap;
+ this.launchPower = launchPower;
+ this.path = path;
+ this.ingress = ingress;
+ this.egress = egress;
+ this.ochSignal = ochSignal;
+ }
+
+ /**
+ * Retrieve the ingress connect point of the path (A).
+ *
+ * @return ingress connect point
+ */
+ public ConnectPoint ingress() {
+ return ingress;
+ }
+
+ /**
+ * Retrieve the egress connect point of the path (B).
+ *
+ * @return egress connect point
+ */
+ public ConnectPoint egress() {
+ return egress;
+ }
+
+ /**
+ * Retrieve the ochSignal.
+ *
+ * @return signal
+ */
+ public OchSignal ochSignal() {
+ return ochSignal;
+ }
+
+ /**
+ * Retrieve the the power in a to b direction.
+ *
+ * @return power map a to b
+ */
+ public Map<DeviceId, Double> deviceAtoBPowerMap() {
+ return deviceAtoBPowerMap;
+ }
+
+ /**
+ * Retrieve the power in b to a direction.
+ *
+ * @return power map b to a
+ */
+ public Map<DeviceId, Double> deviceBtoAPowerMap() {
+ return deviceBtoAPowerMap;
+ }
+
+ /**
+ * Retrieve the launch power at the TX, both A and B.
+ *
+ * @return ingress connect point
+ */
+ public double launchPower() {
+ return launchPower;
+ }
+
+ /**
+ * Retrieve the set of links for the path in the network.
+ *
+ * @return links fo the path
+ */
+ public List<Link> path() {
+ return path;
+ }
+}
diff --git a/apps/odtn/service/src/main/java/org/onosproject/odtn/impl/HttpUtil.java b/apps/odtn/service/src/main/java/org/onosproject/odtn/impl/HttpUtil.java
new file mode 100644
index 0000000..1da0e5d
--- /dev/null
+++ b/apps/odtn/service/src/main/java/org/onosproject/odtn/impl/HttpUtil.java
@@ -0,0 +1,236 @@
+/*
+ * Copyright 2016-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.google.common.annotations.Beta;
+import org.apache.commons.io.IOUtils;
+import org.glassfish.jersey.client.authentication.HttpAuthenticationFeature;
+import org.onosproject.net.DeviceId;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+import javax.ws.rs.ProcessingException;
+import javax.ws.rs.client.Client;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.client.Entity;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.Status;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.security.KeyManagementException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+
+/**
+ * The implementation of HttpUtils.
+ */
+@Beta
+public class HttpUtil {
+
+ private static final Logger log = LoggerFactory.getLogger(HttpUtil.class);
+ private static final String XML = "xml";
+ private static final String JSON = "json";
+ protected static final String DOUBLESLASH = "//";
+ protected static final String COLON = ":";
+ private static final int STATUS_OK = Response.Status.OK.getStatusCode();
+ private static final int STATUS_CREATED = Response.Status.CREATED.getStatusCode();
+ private static final int STATUS_ACCEPTED = Response.Status.ACCEPTED.getStatusCode();
+ private static final String HTTPS = "https";
+
+ private Client client = null;
+ private String protocol = null;
+ private String ip = null;
+ private String port = null;
+
+ public HttpUtil(String protocol, String ip, String port) {
+ //TODO check not null
+ this.protocol = protocol.equals("") ? HTTPS : protocol;
+ this.ip = ip;
+ this.port = port;
+ }
+
+ public void connect(String username, String password) {
+ client = ignoreSslClient();
+ authenticate(client, username, password);
+ }
+
+ public void disconnect() {
+ protocol = "";
+ ip = "";
+ port = "";
+ client = null;
+ }
+
+ public int post(String request, InputStream payload, MediaType mediaType) {
+ Response response = getResponse(request, payload, mediaType);
+ if (response == null) {
+ return Status.NO_CONTENT.getStatusCode();
+ }
+ return response.getStatus();
+ }
+
+ public <T> T post(DeviceId device, String request, InputStream payload, MediaType mediaType,
+ Class<T> responseClass) {
+ Response response = getResponse(request, payload, mediaType);
+ if (response != null && response.hasEntity()) {
+ // Do not read the entity if the responseClass is of type Response. This would allow the
+ // caller to receive the Response directly and try to read its appropriate entity locally.
+ return responseClass == Response.class ? (T) response : response.readEntity(responseClass);
+ }
+ log.error("Response from device {} for request {} contains no entity", device, request);
+ return null;
+ }
+
+ private Response getResponse(String request, InputStream payload, MediaType mediaType) {
+
+ WebTarget wt = getWebTarget(request);
+
+ Response response = null;
+ if (payload != null) {
+ try {
+ response = wt.request(mediaType)
+ .post(Entity.entity(IOUtils.toString(payload, StandardCharsets.UTF_8), mediaType));
+ } catch (IOException e) {
+ log.error("Cannot do POST {} request on GNPY because can't read payload", request);
+ }
+ } else {
+ response = wt.request(mediaType).post(Entity.entity(null, mediaType));
+ }
+ return response;
+ }
+
+ public int put(String request, InputStream payload, MediaType mediaType) {
+
+ WebTarget wt = getWebTarget(request);
+
+ Response response = null;
+ if (payload != null) {
+ try {
+ response = wt.request(mediaType).put(Entity.entity(IOUtils.
+ toString(payload, StandardCharsets.UTF_8), mediaType));
+ } catch (IOException e) {
+ log.error("Cannot do POST {} request on GNPY because can't read payload", request);
+ }
+ } else {
+ response = wt.request(mediaType).put(Entity.entity(null, mediaType));
+ }
+
+ if (response == null) {
+ return Status.NO_CONTENT.getStatusCode();
+ }
+ return response.getStatus();
+ }
+
+ public InputStream get(String request, MediaType mediaType) {
+ WebTarget wt = getWebTarget(request);
+
+ Response s = wt.request(mediaType).get();
+
+ if (checkReply(s)) {
+ return new ByteArrayInputStream(s.readEntity((String.class)).getBytes(StandardCharsets.UTF_8));
+ }
+ return null;
+ }
+
+ public int delete(DeviceId device, String request, InputStream payload, MediaType mediaType) {
+
+ WebTarget wt = getWebTarget(request);
+
+ // FIXME: do we need to delete an entry by enclosing data in DELETE
+ // request?
+ // wouldn't it be nice to use PUT to implement the similar concept?
+ Response response = null;
+ try {
+ response = wt.request(mediaType).delete();
+ } catch (ProcessingException procEx) {
+ log.error("Cannot issue DELETE {} request on device {}", request, device);
+ return Status.SERVICE_UNAVAILABLE.getStatusCode();
+ }
+
+ return response.getStatus();
+ }
+
+ private void authenticate(Client client, String username, String password) {
+ client.register(HttpAuthenticationFeature.basic(username, password));
+
+ }
+
+ protected WebTarget getWebTarget(String request) {
+ log.debug("Sending request to URL {} ", getUrlString(request));
+ return client.target(getUrlString(request));
+ }
+
+ protected String getUrlString(String request) {
+ return protocol + COLON + DOUBLESLASH + ip + COLON + port + request;
+ }
+
+ private boolean checkReply(Response response) {
+ if (response != null) {
+ boolean statusCode = checkStatusCode(response.getStatus());
+ if (!statusCode && response.hasEntity()) {
+ log.error("Failed request, HTTP error msg : " + response.readEntity(String.class));
+ }
+ return statusCode;
+ }
+ log.error("Null reply from device");
+ return false;
+ }
+
+ private boolean checkStatusCode(int statusCode) {
+ if (statusCode == STATUS_OK || statusCode == STATUS_CREATED || statusCode == STATUS_ACCEPTED) {
+ return true;
+ } else {
+ log.error("Failed request, HTTP error code : " + statusCode);
+ return false;
+ }
+ }
+
+ private Client ignoreSslClient() {
+ SSLContext sslcontext = null;
+
+ try {
+ sslcontext = SSLContext.getInstance("TLS");
+ sslcontext.init(null, new TrustManager[]{new X509TrustManager() {
+ @Override
+ public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
+ }
+
+ @Override
+ public void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
+ }
+
+ @Override
+ public X509Certificate[] getAcceptedIssuers() {
+ return new X509Certificate[0];
+ }
+ } }, new java.security.SecureRandom());
+ } catch (NoSuchAlgorithmException | KeyManagementException e) {
+ throw new IllegalStateException(e);
+ }
+
+ return ClientBuilder.newBuilder().sslContext(sslcontext).hostnameVerifier((s1, s2) -> true).build();
+ }
+
+}
diff --git a/apps/odtn/service/src/main/resources/gnpy-response.json b/apps/odtn/service/src/main/resources/gnpy-response.json
new file mode 100644
index 0000000..2446e4b
--- /dev/null
+++ b/apps/odtn/service/src/main/resources/gnpy-response.json
@@ -0,0 +1,1010 @@
+{
+ "result": {
+ "response": [
+ {
+ "response-id": "first",
+ "path-properties": {
+ "path-metric": [
+ {
+ "metric-type": "SNR-bandwidth",
+ "accumulative-value": 16.92
+ },
+ {
+ "metric-type": "SNR-0.1nm",
+ "accumulative-value": 21.0
+ },
+ {
+ "metric-type": "OSNR-bandwidth",
+ "accumulative-value": 16.92
+ },
+ {
+ "metric-type": "OSNR-0.1nm",
+ "accumulative-value": 21.0
+ },
+ {
+ "metric-type": "reference_power",
+ "accumulative-value": 0.001
+ },
+ {
+ "metric-type": "path_bandwidth",
+ "accumulative-value": 100000000000.0
+ }
+ ],
+ "z-a-path-metric": [
+ {
+ "metric-type": "SNR-bandwidth",
+ "accumulative-value": 16.92
+ },
+ {
+ "metric-type": "SNR-0.1nm",
+ "accumulative-value": 21.0
+ },
+ {
+ "metric-type": "OSNR-bandwidth",
+ "accumulative-value": 16.92
+ },
+ {
+ "metric-type": "OSNR-0.1nm",
+ "accumulative-value": 21.0
+ },
+ {
+ "metric-type": "reference_power",
+ "accumulative-value": 0.001
+ },
+ {
+ "metric-type": "path_bandwidth",
+ "accumulative-value": 100000000000.0
+ }
+ ],
+ "path-route-objects": [
+ {
+ "path-route-object": {
+ "index": 0,
+ "num-unnum-hop": {
+ "node-id": "trx-Amsterdam",
+ "link-tp-id": "trx-Amsterdam",
+ "gnpy-node-type": "transceiver"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 1,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 2,
+ "transponder": {
+ "transponder-type": "Voyager",
+ "transponder-mode": "mode 1"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 3,
+ "num-unnum-hop": {
+ "node-id": "roadm-Amsterdam-AD",
+ "link-tp-id": "roadm-Amsterdam-AD",
+ "gnpy-node-type": "ROADM"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 4,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 5,
+ "target-channel-power": {
+ "value": -12
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 6,
+ "num-unnum-hop": {
+ "node-id": "splice-(roadm-Amsterdam-AD)-(patch-(roadm-Amsterdam-AD)-(roadm-Amsterdam-L2))",
+ "link-tp-id": "splice-(roadm-Amsterdam-AD)-(patch-(roadm-Amsterdam-AD)-(roadm-Amsterdam-L2))"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 7,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 8,
+ "num-unnum-hop": {
+ "node-id": "patch-(roadm-Amsterdam-AD)-(roadm-Amsterdam-L2)",
+ "link-tp-id": "patch-(roadm-Amsterdam-AD)-(roadm-Amsterdam-L2)"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 9,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 10,
+ "num-unnum-hop": {
+ "node-id": "splice-(patch-(roadm-Amsterdam-AD)-(roadm-Amsterdam-L2))-(roadm-Amsterdam-L2)",
+ "link-tp-id": "splice-(patch-(roadm-Amsterdam-AD)-(roadm-Amsterdam-L2))-(roadm-Amsterdam-L2)"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 11,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 12,
+ "num-unnum-hop": {
+ "node-id": "roadm-Amsterdam-L2",
+ "link-tp-id": "roadm-Amsterdam-L2",
+ "gnpy-node-type": "ROADM"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 13,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 14,
+ "target-channel-power": {
+ "value": -23
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 15,
+ "num-unnum-hop": {
+ "node-id": "roadm-Amsterdam-L2-booster",
+ "link-tp-id": "roadm-Amsterdam-L2-booster",
+ "gnpy-node-type": "EDFA"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 16,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 17,
+ "target-channel-power": {
+ "value": -1
+ },
+ "output-voa": {
+ "value": 12.0
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 18,
+ "num-unnum-hop": {
+ "node-id": "fiber-Amsterdam-Cologne",
+ "link-tp-id": "fiber-Amsterdam-Cologne"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 19,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 20,
+ "num-unnum-hop": {
+ "node-id": "roadm-Cologne-L1-preamp",
+ "link-tp-id": "roadm-Cologne-L1-preamp",
+ "gnpy-node-type": "EDFA"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 21,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 22,
+ "target-channel-power": {
+ "value": 1.0
+ },
+ "output-voa": {
+ "value": 0.0
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 23,
+ "num-unnum-hop": {
+ "node-id": "roadm-Cologne-L1",
+ "link-tp-id": "roadm-Cologne-L1",
+ "gnpy-node-type": "ROADM"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 24,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 25,
+ "target-channel-power": {
+ "value": -12
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 26,
+ "num-unnum-hop": {
+ "node-id": "splice-(roadm-Cologne-L1)-(patch-(roadm-Cologne-L1)-(roadm-Cologne-L2))",
+ "link-tp-id": "splice-(roadm-Cologne-L1)-(patch-(roadm-Cologne-L1)-(roadm-Cologne-L2))"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 27,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 28,
+ "num-unnum-hop": {
+ "node-id": "patch-(roadm-Cologne-L1)-(roadm-Cologne-L2)",
+ "link-tp-id": "patch-(roadm-Cologne-L1)-(roadm-Cologne-L2)"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 29,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 30,
+ "num-unnum-hop": {
+ "node-id": "splice-(patch-(roadm-Cologne-L1)-(roadm-Cologne-L2))-(roadm-Cologne-L2)",
+ "link-tp-id": "splice-(patch-(roadm-Cologne-L1)-(roadm-Cologne-L2))-(roadm-Cologne-L2)"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 31,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 32,
+ "num-unnum-hop": {
+ "node-id": "roadm-Cologne-L2",
+ "link-tp-id": "roadm-Cologne-L2",
+ "gnpy-node-type": "ROADM"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 33,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 34,
+ "target-channel-power": {
+ "value": -23
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 35,
+ "num-unnum-hop": {
+ "node-id": "roadm-Cologne-L2-booster",
+ "link-tp-id": "roadm-Cologne-L2-booster",
+ "gnpy-node-type": "EDFA"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 36,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 37,
+ "target-channel-power": {
+ "value": -1
+ },
+ "output-voa": {
+ "value": 12.0
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 38,
+ "num-unnum-hop": {
+ "node-id": "fiber-Cologne-Bremen",
+ "link-tp-id": "fiber-Cologne-Bremen"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 39,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 40,
+ "num-unnum-hop": {
+ "node-id": "roadm-Bremen-L1-preamp",
+ "link-tp-id": "roadm-Bremen-L1-preamp",
+ "gnpy-node-type": "EDFA"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 41,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 42,
+ "target-channel-power": {
+ "value": 1.0
+ },
+ "output-voa": {
+ "value": 0.0
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 43,
+ "num-unnum-hop": {
+ "node-id": "roadm-Bremen-L1",
+ "link-tp-id": "roadm-Bremen-L1",
+ "gnpy-node-type": "ROADM"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 44,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 45,
+ "target-channel-power": {
+ "value": -12
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 46,
+ "num-unnum-hop": {
+ "node-id": "splice-(roadm-Bremen-L1)-(patch-(roadm-Bremen-L1)-(roadm-Bremen-AD))",
+ "link-tp-id": "splice-(roadm-Bremen-L1)-(patch-(roadm-Bremen-L1)-(roadm-Bremen-AD))"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 47,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 48,
+ "num-unnum-hop": {
+ "node-id": "patch-(roadm-Bremen-L1)-(roadm-Bremen-AD)",
+ "link-tp-id": "patch-(roadm-Bremen-L1)-(roadm-Bremen-AD)"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 49,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 50,
+ "num-unnum-hop": {
+ "node-id": "splice-(patch-(roadm-Bremen-L1)-(roadm-Bremen-AD))-(roadm-Bremen-AD)",
+ "link-tp-id": "splice-(patch-(roadm-Bremen-L1)-(roadm-Bremen-AD))-(roadm-Bremen-AD)"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 51,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 52,
+ "num-unnum-hop": {
+ "node-id": "roadm-Bremen-AD",
+ "link-tp-id": "roadm-Bremen-AD",
+ "gnpy-node-type": "ROADM"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 53,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 54,
+ "target-channel-power": {
+ "value": -25
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 55,
+ "num-unnum-hop": {
+ "node-id": "trx-Bremen",
+ "link-tp-id": "trx-Bremen",
+ "gnpy-node-type": "transceiver"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 56,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 57,
+ "transponder": {
+ "transponder-type": "Voyager",
+ "transponder-mode": "mode 1"
+ }
+ }
+ }
+ ]
+ }
+ },
+ {
+ "response-id": "second",
+ "path-properties": {
+ "path-metric": [
+ {
+ "metric-type": "SNR-bandwidth",
+ "accumulative-value": 19.38
+ },
+ {
+ "metric-type": "SNR-0.1nm",
+ "accumulative-value": 23.46
+ },
+ {
+ "metric-type": "OSNR-bandwidth",
+ "accumulative-value": 19.38
+ },
+ {
+ "metric-type": "OSNR-0.1nm",
+ "accumulative-value": 23.47
+ },
+ {
+ "metric-type": "reference_power",
+ "accumulative-value": 0.001
+ },
+ {
+ "metric-type": "path_bandwidth",
+ "accumulative-value": 100000000000.0
+ }
+ ],
+ "z-a-path-metric": [
+ {
+ "metric-type": "SNR-bandwidth",
+ "accumulative-value": 19.38
+ },
+ {
+ "metric-type": "SNR-0.1nm",
+ "accumulative-value": 23.46
+ },
+ {
+ "metric-type": "OSNR-bandwidth",
+ "accumulative-value": 19.38
+ },
+ {
+ "metric-type": "OSNR-0.1nm",
+ "accumulative-value": 23.47
+ },
+ {
+ "metric-type": "reference_power",
+ "accumulative-value": 0.001
+ },
+ {
+ "metric-type": "path_bandwidth",
+ "accumulative-value": 100000000000.0
+ }
+ ],
+ "path-route-objects": [
+ {
+ "path-route-object": {
+ "index": 0,
+ "num-unnum-hop": {
+ "node-id": "trx-Amsterdam",
+ "link-tp-id": "trx-Amsterdam",
+ "gnpy-node-type": "transceiver"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 1,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 2,
+ "transponder": {
+ "transponder-type": "Voyager",
+ "transponder-mode": "mode 1"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 3,
+ "num-unnum-hop": {
+ "node-id": "roadm-Amsterdam-AD",
+ "link-tp-id": "roadm-Amsterdam-AD",
+ "gnpy-node-type": "ROADM"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 4,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 5,
+ "target-channel-power": {
+ "value": -12
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 6,
+ "num-unnum-hop": {
+ "node-id": "splice-(roadm-Amsterdam-AD)-(patch-(roadm-Amsterdam-AD)-(roadm-Amsterdam-L1))",
+ "link-tp-id": "splice-(roadm-Amsterdam-AD)-(patch-(roadm-Amsterdam-AD)-(roadm-Amsterdam-L1))"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 7,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 8,
+ "num-unnum-hop": {
+ "node-id": "patch-(roadm-Amsterdam-AD)-(roadm-Amsterdam-L1)",
+ "link-tp-id": "patch-(roadm-Amsterdam-AD)-(roadm-Amsterdam-L1)"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 9,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 10,
+ "num-unnum-hop": {
+ "node-id": "splice-(patch-(roadm-Amsterdam-AD)-(roadm-Amsterdam-L1))-(roadm-Amsterdam-L1)",
+ "link-tp-id": "splice-(patch-(roadm-Amsterdam-AD)-(roadm-Amsterdam-L1))-(roadm-Amsterdam-L1)"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 11,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 12,
+ "num-unnum-hop": {
+ "node-id": "roadm-Amsterdam-L1",
+ "link-tp-id": "roadm-Amsterdam-L1",
+ "gnpy-node-type": "ROADM"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 13,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 14,
+ "target-channel-power": {
+ "value": -23
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 15,
+ "num-unnum-hop": {
+ "node-id": "roadm-Amsterdam-L1-booster",
+ "link-tp-id": "roadm-Amsterdam-L1-booster",
+ "gnpy-node-type": "EDFA"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 16,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 17,
+ "target-channel-power": {
+ "value": -1
+ },
+ "output-voa": {
+ "value": 12.0
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 18,
+ "num-unnum-hop": {
+ "node-id": "fiber-Amsterdam-Bremen",
+ "link-tp-id": "fiber-Amsterdam-Bremen"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 19,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 20,
+ "num-unnum-hop": {
+ "node-id": "roadm-Bremen-L2-preamp",
+ "link-tp-id": "roadm-Bremen-L2-preamp",
+ "gnpy-node-type": "EDFA"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 21,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 22,
+ "target-channel-power": {
+ "value": 1.0
+ },
+ "output-voa": {
+ "value": 0.0
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 23,
+ "num-unnum-hop": {
+ "node-id": "roadm-Bremen-L2",
+ "link-tp-id": "roadm-Bremen-L2",
+ "gnpy-node-type": "ROADM"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 24,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 25,
+ "target-channel-power": {
+ "value": -12
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 26,
+ "num-unnum-hop": {
+ "node-id": "splice-(roadm-Bremen-L2)-(patch-(roadm-Bremen-L2)-(roadm-Bremen-AD))",
+ "link-tp-id": "splice-(roadm-Bremen-L2)-(patch-(roadm-Bremen-L2)-(roadm-Bremen-AD))"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 27,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 28,
+ "num-unnum-hop": {
+ "node-id": "patch-(roadm-Bremen-L2)-(roadm-Bremen-AD)",
+ "link-tp-id": "patch-(roadm-Bremen-L2)-(roadm-Bremen-AD)"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 29,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 30,
+ "num-unnum-hop": {
+ "node-id": "splice-(patch-(roadm-Bremen-L2)-(roadm-Bremen-AD))-(roadm-Bremen-AD)",
+ "link-tp-id": "splice-(patch-(roadm-Bremen-L2)-(roadm-Bremen-AD))-(roadm-Bremen-AD)"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 31,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 32,
+ "num-unnum-hop": {
+ "node-id": "roadm-Bremen-AD",
+ "link-tp-id": "roadm-Bremen-AD",
+ "gnpy-node-type": "ROADM"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 33,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 34,
+ "target-channel-power": {
+ "value": -25
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 35,
+ "num-unnum-hop": {
+ "node-id": "trx-Bremen",
+ "link-tp-id": "trx-Bremen",
+ "gnpy-node-type": "transceiver"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 36,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 37,
+ "transponder": {
+ "transponder-type": "Voyager",
+ "transponder-mode": "mode 1"
+ }
+ }
+ }
+ ]
+ }
+ }
+ ]
+ }
+}
diff --git a/apps/odtn/service/src/test/java/org/onosproject/odtn/impl/GnpyManagerTest.java b/apps/odtn/service/src/test/java/org/onosproject/odtn/impl/GnpyManagerTest.java
new file mode 100644
index 0000000..438491e
--- /dev/null
+++ b/apps/odtn/service/src/test/java/org/onosproject/odtn/impl/GnpyManagerTest.java
@@ -0,0 +1,205 @@
+/*
+ * Copyright 2019-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.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.common.collect.ImmutableSet;
+import org.junit.Before;
+import org.junit.Test;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreServiceAdapter;
+import org.onosproject.core.DefaultApplicationId;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DefaultLink;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.GridType;
+import org.onosproject.net.Link;
+import org.onosproject.net.OchSignal;
+import org.onosproject.net.Path;
+import org.onosproject.net.link.LinkServiceAdapter;
+import org.onosproject.net.provider.ProviderId;
+import org.onosproject.store.service.TestStorageService;
+
+import java.io.IOException;
+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 junit.framework.Assert.assertTrue;
+import static junit.framework.TestCase.assertEquals;
+
+/**
+ * Test for parsing gNPY json files.
+ */
+public class GnpyManagerTest {
+
+ private static final String REQUEST = "{\"path-request\":[{\"request-id\":\"onos-0\"," +
+ "\"source\":\"netconf:10.0.254.93:830\"," +
+ "\"destination\":\"netconf:10.0.254.94:830\",\"src-tp-id\":" +
+ "\"netconf:10.0.254.93:830\",\"dst-tp-id\":" +
+ "\"netconf:10.0.254.94:830\",\"bidirectional\":true," +
+ "\"path-constraints\":{\"te-bandwidth\":" +
+ "{\"technology\":\"flexi-grid\",\"trx_type\":\"Cassini\"," +
+ "\"trx_mode\":null,\"effective-freq-slot\":" +
+ "[{\"N\":\"null\",\"M\":\"null\"}],\"spacing\":5.0E10," +
+ "\"max-nb-of-channel\":null,\"output-power\":null," +
+ "\"path_bandwidth\":1.0E11}}}]}";
+
+ private ConnectPoint tx1 = ConnectPoint.fromString("netconf:10.0.254.93:830/1");
+ private ConnectPoint rdm1tx1 = ConnectPoint.fromString("netconf:10.0.254.107:830/1");
+ private ConnectPoint rdm1ln1 = ConnectPoint.fromString("netconf:10.0.254.107:830/2");
+ private ConnectPoint ln1rdm1 = ConnectPoint.fromString("netconf:10.0.254.101:830/1");
+ private ConnectPoint ln1ln2 = ConnectPoint.fromString("netconf:10.0.254.101:830/2");
+ private ConnectPoint ln2ln1 = ConnectPoint.fromString("netconf:10.0.254.102:830/1");
+ private ConnectPoint ln2rdm2 = ConnectPoint.fromString("netconf:10.0.254.102:830/2");
+ private ConnectPoint rdm2ln2 = ConnectPoint.fromString("netconf:10.0.254.225:830/1");
+ private ConnectPoint rdm2tx2 = ConnectPoint.fromString("netconf:10.0.254.225:830/2");
+ private ConnectPoint tx2 = ConnectPoint.fromString("netconf:10.0.254.94:830/1");
+ private Link tx1rdm1Link = DefaultLink.builder().type(Link.Type.OPTICAL)
+ .providerId(ProviderId.NONE).src(rdm1tx1).dst(tx1).build();
+ private Link rmd1ln1Link = DefaultLink.builder().type(Link.Type.OPTICAL)
+ .providerId(ProviderId.NONE).src(ln1rdm1).dst(rdm1ln1).build();
+ private Link ln1ln2Link = DefaultLink.builder().type(Link.Type.OPTICAL)
+ .providerId(ProviderId.NONE).src(ln2ln1).dst(ln1ln2).build();
+ private Link ln2rdm2Link = DefaultLink.builder().type(Link.Type.OPTICAL)
+ .providerId(ProviderId.NONE).src(rdm2ln2).dst(ln2rdm2).build();
+ private Link tx2rmd2Link = DefaultLink.builder().type(Link.Type.OPTICAL)
+ .providerId(ProviderId.NONE).src(tx2).dst(rdm2tx2).build();
+
+ private GnpyManager manager;
+ private JsonNode reply;
+
+ @Before
+ public void setUp() throws Exception {
+ ObjectMapper mapper = new ObjectMapper();
+ reply = mapper.readTree(this.getClass().getResourceAsStream("gnpy-response.json"));
+ manager = new GnpyManager();
+ manager.storageService = new TestStorageService();
+ manager.linkService = new InternalLinkService();
+ manager.coreService = new InternalCoreService();
+ manager.activate();
+ }
+
+ @Test
+ public void testCreateSuggestedPath() throws IOException {
+ Map<DeviceId, Double> deviceAtoBPowerMap = new HashMap<>();
+ Map<DeviceId, Double> deviceBtoAPowerMap = new HashMap<>();
+ List<DeviceId> deviceIds = manager.getDeviceAndPopulatePowerMap(reply, deviceAtoBPowerMap,
+ deviceBtoAPowerMap, "second");
+ Path path = manager.createSuggestedPath(deviceIds);
+ assertTrue(path.links().contains(tx1rdm1Link));
+ assertTrue(path.links().contains(rmd1ln1Link));
+ assertTrue(path.links().contains(ln1ln2Link));
+ assertTrue(path.links().contains(ln2rdm2Link));
+ assertTrue(path.links().contains(tx2rmd2Link));
+ assertEquals(path.src(), tx2);
+ assertEquals(path.dst(), tx1);
+
+ }
+
+ @Test
+ public void testgetDevicePowerMap() throws IOException {
+ Map<DeviceId, Double> deviceAtoBPowerMap = new HashMap<>();
+ Map<DeviceId, Double> deviceBtoAPowerMap = new HashMap<>();
+ manager.getDeviceAndPopulatePowerMap(reply, deviceAtoBPowerMap, deviceBtoAPowerMap, "second");
+ assertEquals(-25.0, deviceAtoBPowerMap.get(DeviceId.deviceId("netconf:10.0.254.107:830")));
+ assertEquals(-12.0, deviceAtoBPowerMap.get(DeviceId.deviceId("netconf:10.0.254.225:830")));
+ assertEquals(-12.0, deviceBtoAPowerMap.get(DeviceId.deviceId("netconf:10.0.254.225:830")));
+ assertEquals(-25.0, deviceBtoAPowerMap.get(DeviceId.deviceId("netconf:10.0.254.107:830")));
+ }
+
+ @Test
+ public void testGetLaunchPower() throws IOException {
+ double power = manager.getLaunchPower(reply);
+ assertEquals(0.0, power);
+ }
+
+ @Test
+ public void testGetPerHopPower() throws IOException {
+ JsonNode response = reply.get("result").get("response");
+ //getting the a-b path.
+ JsonNode responseObj = response.elements()
+ .next();
+ Iterator<JsonNode> elements = responseObj.get("path-properties")
+ .get("path-route-objects").elements();
+ Iterable<JsonNode> iterable = () -> elements;
+ List<JsonNode> elementsList = StreamSupport
+ .stream(iterable.spliterator(), false)
+ .collect(Collectors.toList());
+ double power = manager.getPerHopPower(elementsList.get(5));
+ assertEquals(-12.0, power);
+ }
+
+ @Test
+ public void testGetOsnr() throws IOException {
+ double osnr = manager.getOsnr(reply, "second");
+ assertEquals(23.47, osnr);
+ }
+
+ @Test
+ public void testCreateOchSignal() throws IOException {
+ OchSignal signal = manager.createOchSignal(reply);
+ System.out.println(signal);
+ assertEquals(signal.gridType(), GridType.DWDM);
+ assertEquals(signal.slotWidth().asGHz(), 50.000);
+ assertEquals(-35, signal.spacingMultiplier());
+ }
+
+ @Test
+ public void testCreateGnpyRequest() {
+ ConnectPoint ingress = ConnectPoint.fromString("netconf:10.0.254.93:830/1");
+ ConnectPoint egress = ConnectPoint.fromString("netconf:10.0.254.94:830/1");
+ String output = manager.createGnpyRequest(ingress, egress, true).toString();
+ System.out.println(output);
+ assertEquals("Json to create network connectivity is wrong", REQUEST, output);
+ }
+
+ private class InternalLinkService extends LinkServiceAdapter {
+ @Override
+ public Set<Link> getDeviceLinks(DeviceId deviceId) {
+ if (deviceId.equals(DeviceId.deviceId("netconf:10.0.254.94:830"))) {
+ return ImmutableSet.of(tx2rmd2Link);
+ } else if (deviceId.equals(DeviceId.deviceId("netconf:10.0.254.107:830"))) {
+ return ImmutableSet.of(tx1rdm1Link);
+ } else if (deviceId.equals(DeviceId.deviceId("netconf:10.0.254.101:830"))) {
+ return ImmutableSet.of(rmd1ln1Link);
+ } else if (deviceId.equals(DeviceId.deviceId("netconf:10.0.254.102:830"))) {
+ return ImmutableSet.of(ln1ln2Link);
+ } else if (deviceId.equals(DeviceId.deviceId("netconf:10.0.254.225:830"))) {
+ return ImmutableSet.of(ln2rdm2Link);
+ }
+ return ImmutableSet.of();
+ }
+ }
+
+ private class InternalCoreService extends CoreServiceAdapter {
+ @Override
+ public ApplicationId getAppId(String name) {
+ return new DefaultApplicationId(1, name);
+ }
+ }
+// private class InternalStoreService extends StorageServiceAdapter {
+// @Override
+// public AtomicCounterBuilder atomicCounterBuilder() {
+// return TestAtomicCounter.builder();
+// }
+// }
+}
diff --git a/apps/odtn/service/src/test/resources/org/onosproject/odtn/impl/gnpy-response.json b/apps/odtn/service/src/test/resources/org/onosproject/odtn/impl/gnpy-response.json
new file mode 100644
index 0000000..72a1273
--- /dev/null
+++ b/apps/odtn/service/src/test/resources/org/onosproject/odtn/impl/gnpy-response.json
@@ -0,0 +1,1900 @@
+{
+ "result": {
+ "response": [
+ {
+ "response-id": "first",
+ "path-properties": {
+ "path-metric": [
+ {
+ "metric-type": "SNR-bandwidth",
+ "accumulative-value": 16.92
+ },
+ {
+ "metric-type": "SNR-0.1nm",
+ "accumulative-value": 21.0
+ },
+ {
+ "metric-type": "OSNR-bandwidth",
+ "accumulative-value": 16.92
+ },
+ {
+ "metric-type": "OSNR-0.1nm",
+ "accumulative-value": 21.0
+ },
+ {
+ "metric-type": "reference_power",
+ "accumulative-value": 0.001
+ },
+ {
+ "metric-type": "path_bandwidth",
+ "accumulative-value": 100000000000.0
+ }
+ ],
+ "z-a-path-metric": [
+ {
+ "metric-type": "SNR-bandwidth",
+ "accumulative-value": 16.92
+ },
+ {
+ "metric-type": "SNR-0.1nm",
+ "accumulative-value": 21.0
+ },
+ {
+ "metric-type": "OSNR-bandwidth",
+ "accumulative-value": 16.92
+ },
+ {
+ "metric-type": "OSNR-0.1nm",
+ "accumulative-value": 21.0
+ },
+ {
+ "metric-type": "reference_power",
+ "accumulative-value": 0.001
+ },
+ {
+ "metric-type": "path_bandwidth",
+ "accumulative-value": 100000000000.0
+ }
+ ],
+ "path-route-objects": [
+ {
+ "path-route-object": {
+ "index": 0,
+ "num-unnum-hop": {
+ "node-id": "netconf:10.0.254.93:830",
+ "link-tp-id": "netconf:10.0.254.93:830",
+ "gnpy-node-type": "transceiver"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 1,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 2,
+ "transponder": {
+ "transponder-type": "Cassini",
+ "transponder-mode": "dp-qpsk"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 3,
+ "num-unnum-hop": {
+ "node-id": "netconf:10.0.254.107:830",
+ "link-tp-id": "netconf:10.0.254.107:830",
+ "gnpy-node-type": "ROADM"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 4,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 5,
+ "target-channel-power": {
+ "value": -12
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 6,
+ "num-unnum-hop": {
+ "node-id": "splice-(roadm-Amsterdam-AD)-(patch-(roadm-Amsterdam-AD)-(roadm-Amsterdam-L2))",
+ "link-tp-id": "splice-(roadm-Amsterdam-AD)-(patch-(roadm-Amsterdam-AD)-(roadm-Amsterdam-L2))"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 7,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 8,
+ "num-unnum-hop": {
+ "node-id": "patch-(roadm-Amsterdam-AD)-(roadm-Amsterdam-L2)",
+ "link-tp-id": "patch-(roadm-Amsterdam-AD)-(roadm-Amsterdam-L2)"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 9,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 10,
+ "num-unnum-hop": {
+ "node-id": "splice-(patch-(roadm-Amsterdam-AD)-(roadm-Amsterdam-L2))-(roadm-Amsterdam-L2)",
+ "link-tp-id": "splice-(patch-(roadm-Amsterdam-AD)-(roadm-Amsterdam-L2))-(roadm-Amsterdam-L2)"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 11,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 12,
+ "num-unnum-hop": {
+ "node-id": "netconf:10.0.254.235:830",
+ "link-tp-id": "netconf:10.0.254.235:830",
+ "gnpy-node-type": "ROADM"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 13,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 14,
+ "target-channel-power": {
+ "value": -23
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 15,
+ "num-unnum-hop": {
+ "node-id": "roadm-Amsterdam-L2-booster",
+ "link-tp-id": "roadm-Amsterdam-L2-booster",
+ "gnpy-node-type": "EDFA"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 16,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 17,
+ "target-channel-power": {
+ "value": -1
+ },
+ "output-voa": {
+ "value": 12.0
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 18,
+ "num-unnum-hop": {
+ "node-id": "fiber-Amsterdam-Cologne",
+ "link-tp-id": "fiber-Amsterdam-Cologne"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 19,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 20,
+ "num-unnum-hop": {
+ "node-id": "roadm-Cologne-L1-preamp",
+ "link-tp-id": "roadm-Cologne-L1-preamp",
+ "gnpy-node-type": "EDFA"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 21,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 22,
+ "target-channel-power": {
+ "value": 1.0
+ },
+ "output-voa": {
+ "value": 0.0
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 23,
+ "num-unnum-hop": {
+ "node-id": "netconf:10.0.254.99:830",
+ "link-tp-id": "netconf:10.0.254.99:830",
+ "gnpy-node-type": "ROADM"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 24,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 25,
+ "target-channel-power": {
+ "value": -12
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 26,
+ "num-unnum-hop": {
+ "node-id": "splice-(roadm-Cologne-L1)-(patch-(roadm-Cologne-L1)-(roadm-Cologne-L2))",
+ "link-tp-id": "splice-(roadm-Cologne-L1)-(patch-(roadm-Cologne-L1)-(roadm-Cologne-L2))"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 27,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 28,
+ "num-unnum-hop": {
+ "node-id": "patch-(roadm-Cologne-L1)-(roadm-Cologne-L2)",
+ "link-tp-id": "patch-(roadm-Cologne-L1)-(roadm-Cologne-L2)"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 29,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 30,
+ "num-unnum-hop": {
+ "node-id": "splice-(patch-(roadm-Cologne-L1)-(roadm-Cologne-L2))-(roadm-Cologne-L2)",
+ "link-tp-id": "splice-(patch-(roadm-Cologne-L1)-(roadm-Cologne-L2))-(roadm-Cologne-L2)"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 31,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 32,
+ "num-unnum-hop": {
+ "node-id": "netconf:10.0.254.104:830",
+ "link-tp-id": "netconf:10.0.254.104:830",
+ "gnpy-node-type": "ROADM"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 33,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 34,
+ "target-channel-power": {
+ "value": -23
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 35,
+ "num-unnum-hop": {
+ "node-id": "roadm-Cologne-L2-booster",
+ "link-tp-id": "roadm-Cologne-L2-booster",
+ "gnpy-node-type": "EDFA"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 36,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 37,
+ "target-channel-power": {
+ "value": -1
+ },
+ "output-voa": {
+ "value": 12.0
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 38,
+ "num-unnum-hop": {
+ "node-id": "fiber-Cologne-Bremen",
+ "link-tp-id": "fiber-Cologne-Bremen"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 39,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 40,
+ "num-unnum-hop": {
+ "node-id": "roadm-Bremen-L1-preamp",
+ "link-tp-id": "roadm-Bremen-L1-preamp",
+ "gnpy-node-type": "EDFA"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 41,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 42,
+ "target-channel-power": {
+ "value": 1.0
+ },
+ "output-voa": {
+ "value": 0.0
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 43,
+ "num-unnum-hop": {
+ "node-id": "netconf:10.0.254.100:830",
+ "link-tp-id": "netconf:10.0.254.100:830",
+ "gnpy-node-type": "ROADM"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 44,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 45,
+ "target-channel-power": {
+ "value": -12
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 46,
+ "num-unnum-hop": {
+ "node-id": "splice-(roadm-Bremen-L1)-(patch-(roadm-Bremen-L1)-(roadm-Bremen-AD))",
+ "link-tp-id": "splice-(roadm-Bremen-L1)-(patch-(roadm-Bremen-L1)-(roadm-Bremen-AD))"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 47,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 48,
+ "num-unnum-hop": {
+ "node-id": "patch-(roadm-Bremen-L1)-(roadm-Bremen-AD)",
+ "link-tp-id": "patch-(roadm-Bremen-L1)-(roadm-Bremen-AD)"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 49,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 50,
+ "num-unnum-hop": {
+ "node-id": "splice-(patch-(roadm-Bremen-L1)-(roadm-Bremen-AD))-(roadm-Bremen-AD)",
+ "link-tp-id": "splice-(patch-(roadm-Bremen-L1)-(roadm-Bremen-AD))-(roadm-Bremen-AD)"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 51,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 52,
+ "num-unnum-hop": {
+ "node-id": "netconf:10.0.254.225:830",
+ "link-tp-id": "netconf:10.0.254.225:830",
+ "gnpy-node-type": "ROADM"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 53,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 54,
+ "target-channel-power": {
+ "value": -25
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 55,
+ "num-unnum-hop": {
+ "node-id": "netconf:10.0.254.94:830",
+ "link-tp-id": "netconf:10.0.254.94:830",
+ "gnpy-node-type": "transceiver"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 56,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 57,
+ "transponder": {
+ "transponder-type": "Cassini",
+ "transponder-mode": "dp-qpsk"
+ }
+ }
+ }
+ ],
+ "reversed-path-route-objects": [
+ {
+ "path-route-object": {
+ "index": 0,
+ "num-unnum-hop": {
+ "node-id": "netconf:10.0.254.94:830",
+ "link-tp-id": "netconf:10.0.254.94:830",
+ "gnpy-node-type": "transceiver"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 1,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 2,
+ "transponder": {
+ "transponder-type": "Cassini",
+ "transponder-mode": "dp-qpsk"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 3,
+ "num-unnum-hop": {
+ "node-id": "netconf:10.0.254.225:830",
+ "link-tp-id": "netconf:10.0.254.225:830",
+ "gnpy-node-type": "ROADM"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 4,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 5,
+ "target-channel-power": {
+ "value": -12
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 6,
+ "num-unnum-hop": {
+ "node-id": "splice-(roadm-Bremen-AD)-(patch-(roadm-Bremen-AD)-(roadm-Bremen-L1))",
+ "link-tp-id": "splice-(roadm-Bremen-AD)-(patch-(roadm-Bremen-AD)-(roadm-Bremen-L1))"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 7,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 8,
+ "num-unnum-hop": {
+ "node-id": "patch-(roadm-Bremen-AD)-(roadm-Bremen-L1)",
+ "link-tp-id": "patch-(roadm-Bremen-AD)-(roadm-Bremen-L1)"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 9,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 10,
+ "num-unnum-hop": {
+ "node-id": "splice-(patch-(roadm-Bremen-AD)-(roadm-Bremen-L1))-(roadm-Bremen-L1)",
+ "link-tp-id": "splice-(patch-(roadm-Bremen-AD)-(roadm-Bremen-L1))-(roadm-Bremen-L1)"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 11,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 12,
+ "num-unnum-hop": {
+ "node-id": "netconf:10.0.254.100:830",
+ "link-tp-id": "netconf:10.0.254.100:830",
+ "gnpy-node-type": "ROADM"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 13,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 14,
+ "target-channel-power": {
+ "value": -23
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 15,
+ "num-unnum-hop": {
+ "node-id": "roadm-Bremen-L1-booster",
+ "link-tp-id": "roadm-Bremen-L1-booster",
+ "gnpy-node-type": "EDFA"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 16,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 17,
+ "target-channel-power": {
+ "value": -1
+ },
+ "output-voa": {
+ "value": 12.0
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 18,
+ "num-unnum-hop": {
+ "node-id": "fiber-Bremen-Cologne",
+ "link-tp-id": "fiber-Bremen-Cologne"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 19,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 20,
+ "num-unnum-hop": {
+ "node-id": "roadm-Cologne-L2-preamp",
+ "link-tp-id": "roadm-Cologne-L2-preamp",
+ "gnpy-node-type": "EDFA"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 21,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 22,
+ "target-channel-power": {
+ "value": 1.0
+ },
+ "output-voa": {
+ "value": 0.0
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 23,
+ "num-unnum-hop": {
+ "node-id": "netconf:10.0.254.104:830",
+ "link-tp-id": "netconf:10.0.254.104:830",
+ "gnpy-node-type": "ROADM"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 24,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 25,
+ "target-channel-power": {
+ "value": -12
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 26,
+ "num-unnum-hop": {
+ "node-id": "splice-(roadm-Cologne-L2)-(patch-(roadm-Cologne-L2)-(roadm-Cologne-L1))",
+ "link-tp-id": "splice-(roadm-Cologne-L2)-(patch-(roadm-Cologne-L2)-(roadm-Cologne-L1))"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 27,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 28,
+ "num-unnum-hop": {
+ "node-id": "patch-(roadm-Cologne-L2)-(roadm-Cologne-L1)",
+ "link-tp-id": "patch-(roadm-Cologne-L2)-(roadm-Cologne-L1)"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 29,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 30,
+ "num-unnum-hop": {
+ "node-id": "splice-(patch-(roadm-Cologne-L2)-(roadm-Cologne-L1))-(roadm-Cologne-L1)",
+ "link-tp-id": "splice-(patch-(roadm-Cologne-L2)-(roadm-Cologne-L1))-(roadm-Cologne-L1)"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 31,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 32,
+ "num-unnum-hop": {
+ "node-id": "netconf:10.0.254.99:830",
+ "link-tp-id": "netconf:10.0.254.99:830",
+ "gnpy-node-type": "ROADM"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 33,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 34,
+ "target-channel-power": {
+ "value": -23
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 35,
+ "num-unnum-hop": {
+ "node-id": "roadm-Cologne-L1-booster",
+ "link-tp-id": "roadm-Cologne-L1-booster",
+ "gnpy-node-type": "EDFA"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 36,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 37,
+ "target-channel-power": {
+ "value": -1
+ },
+ "output-voa": {
+ "value": 12.0
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 38,
+ "num-unnum-hop": {
+ "node-id": "fiber-Cologne-Amsterdam",
+ "link-tp-id": "fiber-Cologne-Amsterdam"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 39,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 40,
+ "num-unnum-hop": {
+ "node-id": "roadm-Amsterdam-L2-preamp",
+ "link-tp-id": "roadm-Amsterdam-L2-preamp",
+ "gnpy-node-type": "EDFA"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 41,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 42,
+ "target-channel-power": {
+ "value": 1.0
+ },
+ "output-voa": {
+ "value": 0.0
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 43,
+ "num-unnum-hop": {
+ "node-id": "netconf:10.0.254.235:830",
+ "link-tp-id": "netconf:10.0.254.235:830",
+ "gnpy-node-type": "ROADM"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 44,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 45,
+ "target-channel-power": {
+ "value": -12
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 46,
+ "num-unnum-hop": {
+ "node-id": "splice-(roadm-Amsterdam-L2)-(patch-(roadm-Amsterdam-L2)-(roadm-Amsterdam-AD))",
+ "link-tp-id": "splice-(roadm-Amsterdam-L2)-(patch-(roadm-Amsterdam-L2)-(roadm-Amsterdam-AD))"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 47,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 48,
+ "num-unnum-hop": {
+ "node-id": "patch-(roadm-Amsterdam-L2)-(roadm-Amsterdam-AD)",
+ "link-tp-id": "patch-(roadm-Amsterdam-L2)-(roadm-Amsterdam-AD)"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 49,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 50,
+ "num-unnum-hop": {
+ "node-id": "splice-(patch-(roadm-Amsterdam-L2)-(roadm-Amsterdam-AD))-(roadm-Amsterdam-AD)",
+ "link-tp-id": "splice-(patch-(roadm-Amsterdam-L2)-(roadm-Amsterdam-AD))-(roadm-Amsterdam-AD)"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 51,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 52,
+ "num-unnum-hop": {
+ "node-id": "netconf:10.0.254.107:830",
+ "link-tp-id": "netconf:10.0.254.107:830",
+ "gnpy-node-type": "ROADM"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 53,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 54,
+ "target-channel-power": {
+ "value": -25
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 55,
+ "num-unnum-hop": {
+ "node-id": "netconf:10.0.254.93:830",
+ "link-tp-id": "netconf:10.0.254.93:830",
+ "gnpy-node-type": "transceiver"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 56,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 57,
+ "transponder": {
+ "transponder-type": "Cassini",
+ "transponder-mode": "dp-qpsk"
+ }
+ }
+ }
+ ]
+ }
+ },
+ {
+ "response-id": "second",
+ "path-properties": {
+ "path-metric": [
+ {
+ "metric-type": "SNR-bandwidth",
+ "accumulative-value": 19.38
+ },
+ {
+ "metric-type": "SNR-0.1nm",
+ "accumulative-value": 23.46
+ },
+ {
+ "metric-type": "OSNR-bandwidth",
+ "accumulative-value": 19.38
+ },
+ {
+ "metric-type": "OSNR-0.1nm",
+ "accumulative-value": 23.47
+ },
+ {
+ "metric-type": "reference_power",
+ "accumulative-value": 0.001
+ },
+ {
+ "metric-type": "path_bandwidth",
+ "accumulative-value": 100000000000.0
+ }
+ ],
+ "z-a-path-metric": [
+ {
+ "metric-type": "SNR-bandwidth",
+ "accumulative-value": 19.38
+ },
+ {
+ "metric-type": "SNR-0.1nm",
+ "accumulative-value": 23.46
+ },
+ {
+ "metric-type": "OSNR-bandwidth",
+ "accumulative-value": 19.38
+ },
+ {
+ "metric-type": "OSNR-0.1nm",
+ "accumulative-value": 23.47
+ },
+ {
+ "metric-type": "reference_power",
+ "accumulative-value": 0.001
+ },
+ {
+ "metric-type": "path_bandwidth",
+ "accumulative-value": 100000000000.0
+ }
+ ],
+ "path-route-objects": [
+ {
+ "path-route-object": {
+ "index": 0,
+ "num-unnum-hop": {
+ "node-id": "netconf:10.0.254.93:830",
+ "link-tp-id": "netconf:10.0.254.93:830",
+ "gnpy-node-type": "transceiver"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 1,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 2,
+ "transponder": {
+ "transponder-type": "Cassini",
+ "transponder-mode": "dp-qpsk"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 3,
+ "num-unnum-hop": {
+ "node-id": "netconf:10.0.254.107:830",
+ "link-tp-id": "netconf:10.0.254.107:830",
+ "gnpy-node-type": "ROADM"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 4,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 5,
+ "target-channel-power": {
+ "value": -12
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 6,
+ "num-unnum-hop": {
+ "node-id": "splice-(roadm-Amsterdam-AD)-(patch-(roadm-Amsterdam-AD)-(roadm-Amsterdam-L1))",
+ "link-tp-id": "splice-(roadm-Amsterdam-AD)-(patch-(roadm-Amsterdam-AD)-(roadm-Amsterdam-L1))"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 7,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 8,
+ "num-unnum-hop": {
+ "node-id": "patch-(roadm-Amsterdam-AD)-(roadm-Amsterdam-L1)",
+ "link-tp-id": "patch-(roadm-Amsterdam-AD)-(roadm-Amsterdam-L1)"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 9,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 10,
+ "num-unnum-hop": {
+ "node-id": "splice-(patch-(roadm-Amsterdam-AD)-(roadm-Amsterdam-L1))-(roadm-Amsterdam-L1)",
+ "link-tp-id": "splice-(patch-(roadm-Amsterdam-AD)-(roadm-Amsterdam-L1))-(roadm-Amsterdam-L1)"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 11,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 12,
+ "num-unnum-hop": {
+ "node-id": "netconf:10.0.254.101:830",
+ "link-tp-id": "netconf:10.0.254.101:830",
+ "gnpy-node-type": "ROADM"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 13,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 14,
+ "target-channel-power": {
+ "value": -23
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 15,
+ "num-unnum-hop": {
+ "node-id": "roadm-Amsterdam-L1-booster",
+ "link-tp-id": "roadm-Amsterdam-L1-booster",
+ "gnpy-node-type": "EDFA"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 16,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 17,
+ "target-channel-power": {
+ "value": -1
+ },
+ "output-voa": {
+ "value": 12.0
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 18,
+ "num-unnum-hop": {
+ "node-id": "fiber-Amsterdam-Bremen",
+ "link-tp-id": "fiber-Amsterdam-Bremen"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 19,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 20,
+ "num-unnum-hop": {
+ "node-id": "roadm-Bremen-L2-preamp",
+ "link-tp-id": "roadm-Bremen-L2-preamp",
+ "gnpy-node-type": "EDFA"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 21,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 22,
+ "target-channel-power": {
+ "value": 1.0
+ },
+ "output-voa": {
+ "value": 0.0
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 23,
+ "num-unnum-hop": {
+ "node-id": "netconf:10.0.254.102:830",
+ "link-tp-id": "netconf:10.0.254.102:830",
+ "gnpy-node-type": "ROADM"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 24,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 25,
+ "target-channel-power": {
+ "value": -12
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 26,
+ "num-unnum-hop": {
+ "node-id": "splice-(roadm-Bremen-L2)-(patch-(roadm-Bremen-L2)-(roadm-Bremen-AD))",
+ "link-tp-id": "splice-(roadm-Bremen-L2)-(patch-(roadm-Bremen-L2)-(roadm-Bremen-AD))"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 27,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 28,
+ "num-unnum-hop": {
+ "node-id": "patch-(roadm-Bremen-L2)-(roadm-Bremen-AD)",
+ "link-tp-id": "patch-(roadm-Bremen-L2)-(roadm-Bremen-AD)"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 29,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 30,
+ "num-unnum-hop": {
+ "node-id": "splice-(patch-(roadm-Bremen-L2)-(roadm-Bremen-AD))-(roadm-Bremen-AD)",
+ "link-tp-id": "splice-(patch-(roadm-Bremen-L2)-(roadm-Bremen-AD))-(roadm-Bremen-AD)"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 31,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 32,
+ "num-unnum-hop": {
+ "node-id": "netconf:10.0.254.225:830",
+ "link-tp-id": "netconf:10.0.254.225:830",
+ "gnpy-node-type": "ROADM"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 33,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 34,
+ "target-channel-power": {
+ "value": -25
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 35,
+ "num-unnum-hop": {
+ "node-id": "netconf:10.0.254.94:830",
+ "link-tp-id": "netconf:10.0.254.94:830",
+ "gnpy-node-type": "transceiver"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 36,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 37,
+ "transponder": {
+ "transponder-type": "Cassini",
+ "transponder-mode": "dp-qpsk"
+ }
+ }
+ }
+ ],
+ "reversed-path-route-objects": [
+ {
+ "path-route-object": {
+ "index": 0,
+ "num-unnum-hop": {
+ "node-id": "netconf:10.0.254.94:830",
+ "link-tp-id": "netconf:10.0.254.94:830",
+ "gnpy-node-type": "transceiver"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 1,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 2,
+ "transponder": {
+ "transponder-type": "Cassini",
+ "transponder-mode": "dp-qpsk"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 3,
+ "num-unnum-hop": {
+ "node-id": "netconf:10.0.254.225:830",
+ "link-tp-id": "netconf:10.0.254.225:830",
+ "gnpy-node-type": "ROADM"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 4,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 5,
+ "target-channel-power": {
+ "value": -12
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 6,
+ "num-unnum-hop": {
+ "node-id": "splice-(roadm-Bremen-AD)-(patch-(roadm-Bremen-AD)-(roadm-Bremen-L2))",
+ "link-tp-id": "splice-(roadm-Bremen-AD)-(patch-(roadm-Bremen-AD)-(roadm-Bremen-L2))"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 7,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 8,
+ "num-unnum-hop": {
+ "node-id": "patch-(roadm-Bremen-AD)-(roadm-Bremen-L2)",
+ "link-tp-id": "patch-(roadm-Bremen-AD)-(roadm-Bremen-L2)"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 9,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 10,
+ "num-unnum-hop": {
+ "node-id": "splice-(patch-(roadm-Bremen-AD)-(roadm-Bremen-L2))-(roadm-Bremen-L2)",
+ "link-tp-id": "splice-(patch-(roadm-Bremen-AD)-(roadm-Bremen-L2))-(roadm-Bremen-L2)"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 11,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 12,
+ "num-unnum-hop": {
+ "node-id": "netconf:10.0.254.102:830",
+ "link-tp-id": "netconf:10.0.254.102:830",
+ "gnpy-node-type": "ROADM"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 13,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 14,
+ "target-channel-power": {
+ "value": -23
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 15,
+ "num-unnum-hop": {
+ "node-id": "roadm-Bremen-L2-booster",
+ "link-tp-id": "roadm-Bremen-L2-booster",
+ "gnpy-node-type": "EDFA"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 16,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 17,
+ "target-channel-power": {
+ "value": -1
+ },
+ "output-voa": {
+ "value": 12.0
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 18,
+ "num-unnum-hop": {
+ "node-id": "fiber-Bremen-Amsterdam",
+ "link-tp-id": "fiber-Bremen-Amsterdam"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 19,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 20,
+ "num-unnum-hop": {
+ "node-id": "roadm-Amsterdam-L1-preamp",
+ "link-tp-id": "roadm-Amsterdam-L1-preamp",
+ "gnpy-node-type": "EDFA"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 21,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 22,
+ "target-channel-power": {
+ "value": 1.0
+ },
+ "output-voa": {
+ "value": 0.0
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 23,
+ "num-unnum-hop": {
+ "node-id": "netconf:10.0.254.101:830",
+ "link-tp-id": "netconf:10.0.254.101:830",
+ "gnpy-node-type": "ROADM"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 24,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 25,
+ "target-channel-power": {
+ "value": -12
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 26,
+ "num-unnum-hop": {
+ "node-id": "splice-(roadm-Amsterdam-L1)-(patch-(roadm-Amsterdam-L1)-(roadm-Amsterdam-AD))",
+ "link-tp-id": "splice-(roadm-Amsterdam-L1)-(patch-(roadm-Amsterdam-L1)-(roadm-Amsterdam-AD))"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 27,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 28,
+ "num-unnum-hop": {
+ "node-id": "patch-(roadm-Amsterdam-L1)-(roadm-Amsterdam-AD)",
+ "link-tp-id": "patch-(roadm-Amsterdam-L1)-(roadm-Amsterdam-AD)"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 29,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 30,
+ "num-unnum-hop": {
+ "node-id": "splice-(patch-(roadm-Amsterdam-L1)-(roadm-Amsterdam-AD))-(roadm-Amsterdam-AD)",
+ "link-tp-id": "splice-(patch-(roadm-Amsterdam-L1)-(roadm-Amsterdam-AD))-(roadm-Amsterdam-AD)"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 31,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 32,
+ "num-unnum-hop": {
+ "node-id": "netconf:10.0.254.107:830",
+ "link-tp-id": "netconf:10.0.254.107:830",
+ "gnpy-node-type": "ROADM"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 33,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 34,
+ "target-channel-power": {
+ "value": -25
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 35,
+ "num-unnum-hop": {
+ "node-id": "netconf:10.0.254.93:830",
+ "link-tp-id": "netconf:10.0.254.93:830",
+ "gnpy-node-type": "transceiver"
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 36,
+ "label-hop": {
+ "N": -284,
+ "M": 4
+ }
+ }
+ },
+ {
+ "path-route-object": {
+ "index": 37,
+ "transponder": {
+ "transponder-type": "Cassini",
+ "transponder-mode": "dp-qpsk"
+ }
+ }
+ }
+ ]
+ }
+ }
+ ]
+ }
+}
diff --git a/apps/optical-model/src/main/java/org/onosproject/net/optical/cli/AddOpticalIntentCommand.java b/apps/optical-model/src/main/java/org/onosproject/net/optical/cli/AddOpticalIntentCommand.java
index 833b20d..1564167 100644
--- a/apps/optical-model/src/main/java/org/onosproject/net/optical/cli/AddOpticalIntentCommand.java
+++ b/apps/optical-model/src/main/java/org/onosproject/net/optical/cli/AddOpticalIntentCommand.java
@@ -145,7 +145,7 @@
ConnectPoint egress = createConnectPoint(egressString);
Intent intent = createOpticalIntent(ingress, egress, deviceService,
- key(), appId(), bidirectional, createOchSignal());
+ key(), appId(), bidirectional, createOchSignal(), null);
service.submit(intent);
print("Optical intent submitted:\n%s", intent.toString());
diff --git a/apps/optical-model/src/main/java/org/onosproject/net/optical/util/OpticalIntentUtility.java b/apps/optical-model/src/main/java/org/onosproject/net/optical/util/OpticalIntentUtility.java
index 8aea7c9..53c6cec 100644
--- a/apps/optical-model/src/main/java/org/onosproject/net/optical/util/OpticalIntentUtility.java
+++ b/apps/optical-model/src/main/java/org/onosproject/net/optical/util/OpticalIntentUtility.java
@@ -61,12 +61,13 @@
* @param bidirectional if this argument is true, the optical link created
* will be bidirectional, otherwise the link will be unidirectional.
* @param signal optical signal
+ * @param suggestedPath suggested path
*
* @return created intent
*/
public static Intent createOpticalIntent(ConnectPoint ingress, ConnectPoint
egress, DeviceService deviceService, Key key, ApplicationId appId, boolean
- bidirectional, OchSignal signal) {
+ bidirectional, OchSignal signal, Path suggestedPath) {
Intent intent = null;
@@ -125,6 +126,7 @@
.signalType(signalType)
.bidirectional(bidirectional)
.ochSignal(signal)
+ .suggestedPath(suggestedPath)
.build();
} else {
log.debug("Unable to create optical intent between connect points {} and {}", ingress, egress);