Add generic json String converter 37/95037/6
authormanuedelf <emmanuelle.delfour@orange.com>
Thu, 4 Feb 2021 09:47:08 +0000 (10:47 +0100)
committermanuedelf <emmanuelle.delfour@orange.com>
Wed, 10 Feb 2021 18:16:09 +0000 (19:16 +0100)
In order to replace DataStoreServiceOperations in PCE, create a generic
json string converter in common module.
This converter will be used for serialization in nbi notifications
module.

Signed-off-by: manuedelf <emmanuelle.delfour@orange.com>
Change-Id: I8df37176a349668d5c9191481b9896c95013925a

common/src/main/java/org/opendaylight/transportpce/common/converter/JsonStringConverter.java [new file with mode: 0644]
common/src/test/java/org/opendaylight/transportpce/common/converter/JsonStringConverterTest.java [new file with mode: 0644]
common/src/test/resources/expected_string.json [new file with mode: 0644]
common/src/test/resources/gnpy_request.json [new file with mode: 0644]

diff --git a/common/src/main/java/org/opendaylight/transportpce/common/converter/JsonStringConverter.java b/common/src/main/java/org/opendaylight/transportpce/common/converter/JsonStringConverter.java
new file mode 100644 (file)
index 0000000..2a071a7
--- /dev/null
@@ -0,0 +1,107 @@
+/*
+ * Copyright © 2021 Orange, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.transportpce.common.converter;
+
+import com.google.common.collect.FluentIterable;
+import com.google.gson.Gson;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonWriter;
+import java.io.IOException;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.io.Writer;
+import org.opendaylight.mdsal.binding.dom.codec.spi.BindingDOMCodecServices;
+import org.opendaylight.mdsal.binding.spec.reflect.BindingReflections;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeWriter;
+import org.opendaylight.yangtools.yang.data.codec.gson.JSONCodecFactory;
+import org.opendaylight.yangtools.yang.data.codec.gson.JSONCodecFactorySupplier;
+import org.opendaylight.yangtools.yang.data.codec.gson.JSONNormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.data.codec.gson.JsonParserStream;
+import org.opendaylight.yangtools.yang.data.codec.gson.JsonWriterFactory;
+import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.data.impl.schema.NormalizedNodeResult;
+import org.opendaylight.yangtools.yang.model.api.SchemaPath;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class JsonStringConverter<T extends DataObject> {
+    private static final Logger LOG = LoggerFactory.getLogger(JsonStringConverter.class);
+    private static final JsonParser PARSER = new JsonParser();
+    private BindingDOMCodecServices bindingDOMCodecServices;
+
+
+    public JsonStringConverter(BindingDOMCodecServices bindingDOMCodecServices) {
+        this.bindingDOMCodecServices = bindingDOMCodecServices;
+    }
+
+    /**
+     * Create a json string from dataobject T.
+     * @param id InstanceIdentifier
+     * @param dataObject T
+     * @param supplier RFC7951 or DRAFT_LHOTKA_NETMOD_YANG_JSON_02
+     * @return Json string representation of the object
+     * @throws IOException if something went wrong.
+     */
+    public String createJsonStringFromDataObject(final InstanceIdentifier<T> id, T dataObject,
+            JSONCodecFactorySupplier supplier) throws IOException {
+        final SchemaPath scPath = SchemaPath.create(FluentIterable.from(id.getPathArguments())
+                .transform(input -> BindingReflections.findQName(input.getType())), true);
+        /*
+         * This function needs : - context - scPath.getParent() -
+         * scPath.getLastComponent().getNamespace(), -
+         * JsonWriterFactory.createJsonWriter(writer)
+         */
+
+        JSONCodecFactory codecFactory = supplier
+                .getShared(bindingDOMCodecServices.getRuntimeContext().getEffectiveModelContext());
+        try (Writer writer = new StringWriter();
+                JsonWriter jsonWriter = JsonWriterFactory.createJsonWriter(writer, 4);) {
+            NormalizedNodeStreamWriter jsonStreamWriter = JSONNormalizedNodeStreamWriter.createExclusiveWriter(
+                    codecFactory, scPath.getParent(), scPath.getLastComponent().getNamespace(), jsonWriter);
+            try (NormalizedNodeWriter nodeWriter = NormalizedNodeWriter.forStreamWriter(jsonStreamWriter)) {
+                nodeWriter.write(bindingDOMCodecServices.toNormalizedNode(id, dataObject).getValue());
+                nodeWriter.flush();
+            }
+            JsonObject asJsonObject = PARSER.parse(writer.toString()).getAsJsonObject();
+            return new Gson().toJson(asJsonObject);
+        } catch (IOException e) {
+            LOG.error("Cannot convert object {} to string ", dataObject);
+            throw e;
+        }
+    }
+
+    /**
+     * Create a dataObject of T type from json string.
+     * @param path YangInstanceIdentifier
+     * @param jsonString String
+     * @param supplier RFC7951 or DRAFT_LHOTKA_NETMOD_YANG_JSON_02
+     * @return T the created object.
+     */
+    public T createDataObjectFromJsonString(YangInstanceIdentifier path, String jsonString,
+            JSONCodecFactorySupplier supplier) {
+        JsonReader reader = new JsonReader(new StringReader(jsonString));
+        NormalizedNodeResult result = new NormalizedNodeResult();
+        try (NormalizedNodeStreamWriter streamWriter = ImmutableNormalizedNodeStreamWriter.from(result);
+                JsonParserStream jsonParser = JsonParserStream.create(streamWriter,
+                        supplier
+                                .getShared(bindingDOMCodecServices.getRuntimeContext().getEffectiveModelContext()))) {
+            jsonParser.parse(reader);
+            return (T) bindingDOMCodecServices
+                    .fromNormalizedNode(path, result.getResult()).getValue();
+        } catch (IOException e) {
+            LOG.warn("An error occured during parsing Json input stream", e);
+            return null;
+        }
+    }
+}
diff --git a/common/src/test/java/org/opendaylight/transportpce/common/converter/JsonStringConverterTest.java b/common/src/test/java/org/opendaylight/transportpce/common/converter/JsonStringConverterTest.java
new file mode 100644 (file)
index 0000000..4ab6716
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ * Copyright © 2021 Orange, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.transportpce.common.converter;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.Reader;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import org.junit.Test;
+import org.opendaylight.transportpce.test.AbstractTest;
+import org.opendaylight.transportpce.test.converter.DataObjectConverter;
+import org.opendaylight.transportpce.test.converter.JSONDataObjectConverter;
+import org.opendaylight.yang.gen.v1.gnpy.gnpy.api.rev190103.GnpyApi;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.codec.gson.JSONCodecFactorySupplier;
+
+public class JsonStringConverterTest extends AbstractTest {
+
+    @Test
+    public void createJsonStringFromDataObjectTest() {
+        DataObjectConverter dataObjectConverter = JSONDataObjectConverter
+                .createWithDataStoreUtil(getDataStoreContextUtil());
+        try (Reader reader = new FileReader("src/test/resources/gnpy_request.json", StandardCharsets.UTF_8)) {
+            NormalizedNode<? extends PathArgument, ?> normalizedNode = dataObjectConverter
+                    .transformIntoNormalizedNode(reader).get();
+            GnpyApi gnpyRequest = (GnpyApi) getDataStoreContextUtil().getBindingDOMCodecServices()
+                    .fromNormalizedNode(YangInstanceIdentifier.of(GnpyApi.QNAME), normalizedNode).getValue();
+            JsonStringConverter<GnpyApi> gnpyJsonCOnverter = new JsonStringConverter<GnpyApi>(
+                    getDataStoreContextUtil().getBindingDOMCodecServices());
+            String jsonString = gnpyJsonCOnverter
+                    .createJsonStringFromDataObject(InstanceIdentifier.builder(GnpyApi.class).build(),
+                            gnpyRequest, JSONCodecFactorySupplier.DRAFT_LHOTKA_NETMOD_YANG_JSON_02);
+            assertEquals("Should be a valid request",
+                    Files.readString(Paths.get("src/test/resources/expected_string.json")), jsonString);
+        } catch (IOException e) {
+            fail("Cannot load path description ");
+        }
+    }
+
+    @Test
+    public void createDataObjectFromJsonStringTest() throws IOException {
+        String json = Files.readString(Paths.get("src/test/resources/expected_string.json"));
+        JsonStringConverter<GnpyApi> gnpyJsonCOnverter = new JsonStringConverter<GnpyApi>(
+                getDataStoreContextUtil().getBindingDOMCodecServices());
+        GnpyApi request = gnpyJsonCOnverter
+                .createDataObjectFromJsonString(YangInstanceIdentifier.of(GnpyApi.QNAME), json,
+                        JSONCodecFactorySupplier.DRAFT_LHOTKA_NETMOD_YANG_JSON_02);
+        assertNotNull("Should not be null", request);
+    }
+}
diff --git a/common/src/test/resources/expected_string.json b/common/src/test/resources/expected_string.json
new file mode 100644 (file)
index 0000000..9a7044d
--- /dev/null
@@ -0,0 +1 @@
+{"gnpy-api":{"topology-file":{"elements":[{"uid":"127.0.0.31","metadata":{"location":{"latitude":0,"longitude":0,"region":"XPONDER-3"}},"type":"gnpy-network-topology:Transceiver"},{"uid":"127.0.0.30","metadata":{"location":{"latitude":0,"longitude":0,"region":"OpenROADM-3"}},"type":"gnpy-network-topology:Roadm","params":{"target_pch_out_db":-20}},{"uid":"127.0.0.51","metadata":{"location":{"latitude":0,"longitude":0,"region":"XPONDER-5"}},"type":"gnpy-network-topology:Transceiver"},{"uid":"127.0.0.50","metadata":{"location":{"latitude":0,"longitude":0,"region":"OpenROADM-5"}},"type":"gnpy-network-topology:Roadm","params":{"target_pch_out_db":-20}},{"uid":"127.0.0.11","metadata":{"location":{"latitude":0,"longitude":0,"region":"XPONDER-1"}},"type":"gnpy-network-topology:Transceiver"},{"uid":"127.0.0.10","metadata":{"location":{"latitude":0,"longitude":0,"region":"OpenROADM-1"}},"type":"gnpy-network-topology:Roadm","params":{"target_pch_out_db":-20}},{"uid":"243.0.0.10","metadata":{"location":{"latitude":0,"longitude":0,"city":"N/A","region":"N/A"}},"type":"gnpy-network-topology:Fiber","type_variety":"SSMF","params":{"con_in":0,"loss_coef":0.12,"con_out":0,"length":60,"att_in":0,"length_units":"gnpy-network-topology:km"}},{"uid":"243.0.0.12","metadata":{"location":{"latitude":0,"longitude":0,"city":"N/A","region":"N/A"}},"type":"gnpy-network-topology:Fiber","type_variety":"SSMF","params":{"con_in":0,"loss_coef":0.29,"con_out":0,"length":100,"att_in":0,"length_units":"gnpy-network-topology:km"}},{"uid":"243.0.0.11","metadata":{"location":{"latitude":0,"longitude":0,"city":"N/A","region":"N/A"}},"type":"gnpy-network-topology:Fiber","type_variety":"SSMF","params":{"con_in":0,"loss_coef":0.12,"con_out":0,"length":60,"att_in":0,"length_units":"gnpy-network-topology:km"}},{"uid":"243.0.0.14","metadata":{"location":{"latitude":0,"longitude":0,"city":"N/A","region":"N/A"}},"type":"gnpy-network-topology:Fiber","type_variety":"SSMF","params":{"con_in":0,"loss_coef":0.29,"con_out":0,"length":100,"att_in":0,"length_units":"gnpy-network-topology:km"}},{"uid":"243.0.0.13","metadata":{"location":{"latitude":0,"longitude":0,"city":"N/A","region":"N/A"}},"type":"gnpy-network-topology:Fiber","type_variety":"SSMF","params":{"con_in":0,"loss_coef":0.2127659574468085124099303584443987347185611724853515625,"con_out":0,"length":140,"att_in":0,"length_units":"gnpy-network-topology:km"}},{"uid":"243.0.0.8","metadata":{"location":{"latitude":0,"longitude":0,"city":"N/A","region":"N/A"}},"type":"gnpy-network-topology:Fiber","type_variety":"SSMF","params":{"con_in":0,"loss_coef":0.11999999999999999555910790149937383830547332763671875,"con_out":0,"length":100,"att_in":0,"length_units":"gnpy-network-topology:km"}},{"uid":"243.0.0.9","metadata":{"location":{"latitude":0,"longitude":0,"city":"N/A","region":"N/A"}},"type":"gnpy-network-topology:Fiber","type_variety":"SSMF","params":{"con_in":0,"loss_coef":0.29,"con_out":0,"length":100,"att_in":0,"length_units":"gnpy-network-topology:km"}},{"uid":"243.0.0.2","metadata":{"location":{"latitude":0,"longitude":0,"city":"N/A","region":"N/A"}},"type":"gnpy-network-topology:Fiber","type_variety":"SSMF","params":{"con_in":0,"loss_coef":0.29,"con_out":0,"length":100,"att_in":0,"length_units":"gnpy-network-topology:km"}},{"uid":"243.0.0.3","metadata":{"location":{"latitude":0,"longitude":0,"city":"N/A","region":"N/A"}},"type":"gnpy-network-topology:Fiber","type_variety":"SSMF","params":{"con_in":0,"loss_coef":0.12,"con_out":0,"length":60,"att_in":0,"length_units":"gnpy-network-topology:km"}},{"uid":"243.0.0.1","metadata":{"location":{"latitude":0,"longitude":0,"city":"N/A","region":"N/A"}},"type":"gnpy-network-topology:Fiber","type_variety":"SSMF","params":{"con_in":0,"loss_coef":0.11999999999999999555910790149937383830547332763671875,"con_out":0,"length":100,"att_in":0,"length_units":"gnpy-network-topology:km"}},{"uid":"243.0.0.6","metadata":{"location":{"latitude":0,"longitude":0,"city":"N/A","region":"N/A"}},"type":"gnpy-network-topology:Fiber","type_variety":"SSMF","params":{"con_in":0,"loss_coef":0.2127659574468085124099303584443987347185611724853515625,"con_out":0,"length":140,"att_in":0,"length_units":"gnpy-network-topology:km"}},{"uid":"243.0.0.7","metadata":{"location":{"latitude":0,"longitude":0,"city":"N/A","region":"N/A"}},"type":"gnpy-network-topology:Fiber","type_variety":"SSMF","params":{"con_in":0,"loss_coef":0.29,"con_out":0,"length":100,"att_in":0,"length_units":"gnpy-network-topology:km"}},{"uid":"243.0.0.4","metadata":{"location":{"latitude":0,"longitude":0,"city":"N/A","region":"N/A"}},"type":"gnpy-network-topology:Fiber","type_variety":"SSMF","params":{"con_in":0,"loss_coef":0.29,"con_out":0,"length":100,"att_in":0,"length_units":"gnpy-network-topology:km"}},{"uid":"243.0.0.5","metadata":{"location":{"latitude":0,"longitude":0,"city":"N/A","region":"N/A"}},"type":"gnpy-network-topology:Fiber","type_variety":"SSMF","params":{"con_in":0,"loss_coef":0.12,"con_out":0,"length":60,"att_in":0,"length_units":"gnpy-network-topology:km"}},{"uid":"127.0.0.20","metadata":{"location":{"latitude":0,"longitude":0,"region":"OpenROADM-2"}},"type":"gnpy-network-topology:Roadm","params":{"target_pch_out_db":-20}},{"uid":"127.0.0.41","metadata":{"location":{"latitude":0,"longitude":0,"region":"XPONDER-4"}},"type":"gnpy-network-topology:Transceiver"},{"uid":"127.0.0.40","metadata":{"location":{"latitude":0,"longitude":0,"region":"OpenROADM-4"}},"type":"gnpy-network-topology:Roadm","params":{"target_pch_out_db":-20}},{"uid":"127.0.0.21","metadata":{"location":{"latitude":0,"longitude":0,"region":"XPONDER-2"}},"type":"gnpy-network-topology:Transceiver"}],"connections":[{"from_node":"127.0.0.51","to_node":"127.0.0.50"},{"from_node":"127.0.0.30","to_node":"127.0.0.31"},{"from_node":"127.0.0.10","to_node":"243.0.0.1"},{"from_node":"243.0.0.1","to_node":"127.0.0.30"},{"from_node":"127.0.0.20","to_node":"127.0.0.21"},{"from_node":"127.0.0.20","to_node":"243.0.0.2"},{"from_node":"243.0.0.2","to_node":"127.0.0.10"},{"from_node":"127.0.0.20","to_node":"243.0.0.3"},{"from_node":"243.0.0.3","to_node":"127.0.0.50"},{"from_node":"127.0.0.40","to_node":"127.0.0.41"},{"from_node":"127.0.0.40","to_node":"243.0.0.4"},{"from_node":"243.0.0.4","to_node":"127.0.0.30"},{"from_node":"127.0.0.50","to_node":"243.0.0.5"},{"from_node":"243.0.0.5","to_node":"127.0.0.30"},{"from_node":"127.0.0.31","to_node":"127.0.0.30"},{"from_node":"127.0.0.50","to_node":"127.0.0.51"},{"from_node":"127.0.0.20","to_node":"243.0.0.6"},{"from_node":"243.0.0.6","to_node":"127.0.0.30"},{"from_node":"127.0.0.30","to_node":"243.0.0.7"},{"from_node":"243.0.0.7","to_node":"127.0.0.40"},{"from_node":"127.0.0.11","to_node":"127.0.0.10"},{"from_node":"127.0.0.30","to_node":"243.0.0.8"},{"from_node":"243.0.0.8","to_node":"127.0.0.10"},{"from_node":"127.0.0.40","to_node":"243.0.0.9"},{"from_node":"243.0.0.9","to_node":"127.0.0.50"},{"from_node":"127.0.0.30","to_node":"243.0.0.10"},{"from_node":"243.0.0.10","to_node":"127.0.0.50"},{"from_node":"127.0.0.50","to_node":"243.0.0.11"},{"from_node":"243.0.0.11","to_node":"127.0.0.20"},{"from_node":"127.0.0.50","to_node":"243.0.0.12"},{"from_node":"243.0.0.12","to_node":"127.0.0.40"},{"from_node":"127.0.0.21","to_node":"127.0.0.20"},{"from_node":"127.0.0.41","to_node":"127.0.0.40"},{"from_node":"127.0.0.30","to_node":"243.0.0.13"},{"from_node":"243.0.0.13","to_node":"127.0.0.20"},{"from_node":"127.0.0.10","to_node":"127.0.0.11"},{"from_node":"127.0.0.10","to_node":"243.0.0.14"},{"from_node":"243.0.0.14","to_node":"127.0.0.20"}]},"service-file":{"path-request":[{"request-id":2,"bidirectional":false,"dst-tp-id":"ZHN0VHBJZA\u003d\u003d","source":"127.0.0.11","explicit-route-objects":{"route-object-include-exclude":[{"num-unnum-hop":{"node-id":"127.0.0.20","link-tp-id":"1","hop-type":"STRICT"},"index":0,"explicit-route-usage":"gnpy-path-computation-simplified:route-include-ero"},{"num-unnum-hop":{"node-id":"127.0.0.30","link-tp-id":"1","hop-type":"STRICT"},"index":1,"explicit-route-usage":"gnpy-path-computation-simplified:route-include-ero"}]},"path-constraints":{"te-bandwidth":{"spacing":50000000000,"trx_mode":"W100G","trx_type":"openroadm-beta1","effective-freq-slot":[{"N":0,"M":4}],"technology":"flexi-grid","path_bandwidth":100}},"src-tp-id":"c3JjVHBJZA\u003d\u003d","destination":"127.0.0.41"}]}}}
\ No newline at end of file
diff --git a/common/src/test/resources/gnpy_request.json b/common/src/test/resources/gnpy_request.json
new file mode 100644 (file)
index 0000000..0219eef
--- /dev/null
@@ -0,0 +1,630 @@
+{
+  "gnpy-api": {
+    "topology-file": {
+      "elements": [
+        {
+          "uid": "127.0.0.40",
+         "type": "gnpy-network-topology:Roadm",
+          "metadata": {
+            "location": {
+              "region": "OpenROADM-4",
+              "latitude": 0,
+              "longitude": 0
+            }
+          },
+          "params": {
+            "target_pch_out_db": -20
+          }
+        },
+        {
+          "uid": "127.0.0.50",
+         "type": "gnpy-network-topology:Roadm",
+          "metadata": {
+            "location": {
+              "region": "OpenROADM-5",
+              "latitude": 0,
+              "longitude": 0
+            }
+          },
+          "params": {
+            "target_pch_out_db": -20
+          }
+        },
+        {
+          "uid": "127.0.0.20",
+         "type": "gnpy-network-topology:Roadm",
+          "metadata": {
+            "location": {
+              "region": "OpenROADM-2",
+              "latitude": 0,
+              "longitude": 0
+            }
+          },
+          "params": {
+            "target_pch_out_db": -20
+          }
+        },
+        {
+          "uid": "127.0.0.10",
+         "type": "gnpy-network-topology:Roadm",
+          "metadata": {
+            "location": {
+              "region": "OpenROADM-1",
+              "latitude": 0,
+              "longitude": 0
+            }
+          },
+          "params": {
+            "target_pch_out_db": -20
+          }
+        },
+        {
+          "uid": "127.0.0.30",
+         "type": "gnpy-network-topology:Roadm",
+          "metadata": {
+            "location": {
+              "region": "OpenROADM-3",
+              "latitude": 0,
+              "longitude": 0
+            }
+          },
+          "params": {
+            "target_pch_out_db": -20
+          }
+        },
+        {
+          "uid": "127.0.0.11",
+          "type": "gnpy-network-topology:Transceiver",
+          "metadata": {
+            "location": {
+              "region": "XPONDER-1",
+              "latitude": 0,
+              "longitude": 0
+            }
+          }
+        },
+        {
+          "uid": "127.0.0.51",
+          "type": "gnpy-network-topology:Transceiver",
+          "metadata": {
+            "location": {
+              "region": "XPONDER-5",
+              "latitude": 0,
+              "longitude": 0
+            }
+          }
+        },
+        {
+          "uid": "127.0.0.41",
+          "type": "gnpy-network-topology:Transceiver",
+          "metadata": {
+            "location": {
+              "region": "XPONDER-4",
+              "latitude": 0,
+              "longitude": 0
+            }
+          }
+        },
+        {
+          "uid": "127.0.0.31",
+          "type": "gnpy-network-topology:Transceiver",
+          "metadata": {
+            "location": {
+              "region": "XPONDER-3",
+              "latitude": 0,
+              "longitude": 0
+            }
+          }
+        },
+        {
+          "uid": "127.0.0.21",
+          "type": "gnpy-network-topology:Transceiver",
+          "metadata": {
+            "location": {
+              "region": "XPONDER-2",
+              "latitude": 0,
+              "longitude": 0
+            }
+          }
+        },
+        {
+          "uid": "243.0.0.1",
+          "type": "gnpy-network-topology:Fiber",
+          "type_variety": "SSMF",
+          "metadata": {
+            "location": {
+              "city": "N/A",
+              "region": "N/A",
+              "latitude": 0,
+              "longitude": 0
+            }
+          },
+          "params": {
+            "length": 100,
+            "loss_coef": 0.11999999999999999555910790149937383830547332763671875,
+            "length_units": "gnpy-network-topology:km",
+            "att_in": 0,
+            "con_in": 0,
+            "con_out": 0
+          }
+        },
+        {
+          "uid": "243.0.0.2",
+          "type": "gnpy-network-topology:Fiber",
+          "type_variety": "SSMF",
+          "metadata": {
+            "location": {
+              "city": "N/A",
+              "region": "N/A",
+              "latitude": 0,
+              "longitude": 0
+            }
+          },
+          "params": {
+            "length": 100,
+            "loss_coef": 0.29,
+            "length_units": "gnpy-network-topology:km",
+            "att_in": 0,
+            "con_in": 0,
+            "con_out": 0
+          }
+        },
+        {
+          "uid": "243.0.0.3",
+          "type": "gnpy-network-topology:Fiber",
+          "type_variety": "SSMF",
+          "metadata": {
+            "location": {
+              "city": "N/A",
+              "region": "N/A",
+              "latitude": 0,
+              "longitude": 0
+            }
+          },
+          "params": {
+            "length": 60,
+            "loss_coef": 0.12,
+            "length_units": "gnpy-network-topology:km",
+            "att_in": 0,
+            "con_in": 0,
+            "con_out": 0
+          }
+        },
+        {
+          "uid": "243.0.0.4",
+          "type": "gnpy-network-topology:Fiber",
+          "type_variety": "SSMF",
+          "metadata": {
+            "location": {
+              "city": "N/A",
+              "region": "N/A",
+              "latitude": 0,
+              "longitude": 0
+            }
+          },
+          "params": {
+            "length": 100,
+            "loss_coef": 0.29,
+            "length_units": "gnpy-network-topology:km",
+            "att_in": 0,
+            "con_in": 0,
+            "con_out": 0
+          }
+        },
+        {
+          "uid": "243.0.0.5",
+          "type": "gnpy-network-topology:Fiber",
+          "type_variety": "SSMF",
+          "metadata": {
+            "location": {
+              "city": "N/A",
+              "region": "N/A",
+              "latitude": 0,
+              "longitude": 0
+            }
+          },
+          "params": {
+            "length": 60,
+            "loss_coef": 0.12,
+            "length_units": "gnpy-network-topology:km",
+            "att_in": 0,
+            "con_in": 0,
+            "con_out": 0
+          }
+        },
+        {
+          "uid": "243.0.0.6",
+          "type": "gnpy-network-topology:Fiber",
+          "type_variety": "SSMF",
+          "metadata": {
+            "location": {
+              "city": "N/A",
+              "region": "N/A",
+              "latitude": 0,
+              "longitude": 0
+            }
+          },
+          "params": {
+            "length": 140,
+            "loss_coef": 0.2127659574468085124099303584443987347185611724853515625,
+            "length_units": "gnpy-network-topology:km",
+            "att_in": 0,
+            "con_in": 0,
+            "con_out": 0
+          }
+        },
+        {
+          "uid": "243.0.0.7",
+          "type": "gnpy-network-topology:Fiber",
+          "type_variety": "SSMF",
+          "metadata": {
+            "location": {
+              "city": "N/A",
+              "region": "N/A",
+              "latitude": 0,
+              "longitude": 0
+            }
+          },
+          "params": {
+            "length": 100,
+            "loss_coef": 0.29,
+            "length_units": "gnpy-network-topology:km",
+            "att_in": 0,
+            "con_in": 0,
+            "con_out": 0
+          }
+        },
+        {
+          "uid": "243.0.0.8",
+          "type": "gnpy-network-topology:Fiber",
+          "type_variety": "SSMF",
+          "metadata": {
+            "location": {
+              "city": "N/A",
+              "region": "N/A",
+              "latitude": 0,
+              "longitude": 0
+            }
+          },
+          "params": {
+            "length": 100,
+            "loss_coef": 0.11999999999999999555910790149937383830547332763671875,
+            "length_units": "gnpy-network-topology:km",
+            "att_in": 0,
+            "con_in": 0,
+            "con_out": 0
+          }
+        },
+        {
+          "uid": "243.0.0.9",
+          "type": "gnpy-network-topology:Fiber",
+          "type_variety": "SSMF",
+          "metadata": {
+            "location": {
+              "city": "N/A",
+              "region": "N/A",
+              "latitude": 0,
+              "longitude": 0
+            }
+          },
+          "params": {
+            "length": 100,
+            "loss_coef": 0.29,
+            "length_units": "gnpy-network-topology:km",
+            "att_in": 0,
+            "con_in": 0,
+            "con_out": 0
+          }
+        },
+        {
+          "uid": "243.0.0.10",
+          "type": "gnpy-network-topology:Fiber",
+          "type_variety": "SSMF",
+          "metadata": {
+            "location": {
+              "city": "N/A",
+              "region": "N/A",
+              "latitude": 0,
+              "longitude": 0
+            }
+          },
+          "params": {
+            "length": 60,
+            "loss_coef": 0.12,
+            "length_units": "gnpy-network-topology:km",
+            "att_in": 0,
+            "con_in": 0,
+            "con_out": 0
+          }
+        },
+        {
+          "uid": "243.0.0.11",
+          "type": "gnpy-network-topology:Fiber",
+          "type_variety": "SSMF",
+          "metadata": {
+            "location": {
+              "city": "N/A",
+              "region": "N/A",
+              "latitude": 0,
+              "longitude": 0
+            }
+          },
+          "params": {
+            "length": 60,
+            "loss_coef": 0.12,
+            "length_units": "gnpy-network-topology:km",
+            "att_in": 0,
+            "con_in": 0,
+            "con_out": 0
+          }
+        },
+        {
+          "uid": "243.0.0.12",
+          "type": "gnpy-network-topology:Fiber",
+          "type_variety": "SSMF",
+          "metadata": {
+            "location": {
+              "city": "N/A",
+              "region": "N/A",
+              "latitude": 0,
+              "longitude": 0
+            }
+          },
+          "params": {
+            "length": 100,
+            "loss_coef": 0.29,
+            "length_units": "gnpy-network-topology:km",
+            "att_in": 0,
+            "con_in": 0,
+            "con_out": 0
+          }
+        },
+        {
+          "uid": "243.0.0.13",
+          "type": "gnpy-network-topology:Fiber",
+          "type_variety": "SSMF",
+          "metadata": {
+            "location": {
+              "city": "N/A",
+              "region": "N/A",
+              "latitude": 0,
+              "longitude": 0
+            }
+          },
+          "params": {
+            "length": 140,
+            "loss_coef": 0.2127659574468085124099303584443987347185611724853515625,
+            "length_units": "gnpy-network-topology:km",
+            "att_in": 0,
+            "con_in": 0,
+            "con_out": 0
+          }
+        },
+        {
+          "uid": "243.0.0.14",
+          "type": "gnpy-network-topology:Fiber",
+          "type_variety": "SSMF",
+          "metadata": {
+            "location": {
+              "city": "N/A",
+              "region": "N/A",
+              "latitude": 0,
+              "longitude": 0
+            }
+          },
+          "params": {
+            "length": 100,
+            "loss_coef": 0.29,
+            "length_units": "gnpy-network-topology:km",
+            "att_in": 0,
+            "con_in": 0,
+            "con_out": 0
+          }
+        }
+      ],
+      "connections": [
+        {
+          "from_node": "127.0.0.51",
+          "to_node": "127.0.0.50"
+        },
+        {
+          "from_node": "127.0.0.30",
+          "to_node": "127.0.0.31"
+        },
+        {
+          "from_node": "127.0.0.10",
+          "to_node": "243.0.0.1"
+        },
+        {
+          "from_node": "243.0.0.1",
+          "to_node": "127.0.0.30"
+        },
+        {
+          "from_node": "127.0.0.20",
+          "to_node": "127.0.0.21"
+        },
+        {
+          "from_node": "127.0.0.20",
+          "to_node": "243.0.0.2"
+        },
+        {
+          "from_node": "243.0.0.2",
+          "to_node": "127.0.0.10"
+        },
+        {
+          "from_node": "127.0.0.20",
+          "to_node": "243.0.0.3"
+        },
+        {
+          "from_node": "243.0.0.3",
+          "to_node": "127.0.0.50"
+        },
+        {
+          "from_node": "127.0.0.40",
+          "to_node": "127.0.0.41"
+        },
+        {
+          "from_node": "127.0.0.40",
+          "to_node": "243.0.0.4"
+        },
+        {
+          "from_node": "243.0.0.4",
+          "to_node": "127.0.0.30"
+        },
+        {
+          "from_node": "127.0.0.50",
+          "to_node": "243.0.0.5"
+        },
+        {
+          "from_node": "243.0.0.5",
+          "to_node": "127.0.0.30"
+        },
+        {
+          "from_node": "127.0.0.31",
+          "to_node": "127.0.0.30"
+        },
+        {
+          "from_node": "127.0.0.50",
+          "to_node": "127.0.0.51"
+        },
+        {
+          "from_node": "127.0.0.20",
+          "to_node": "243.0.0.6"
+        },
+        {
+          "from_node": "243.0.0.6",
+          "to_node": "127.0.0.30"
+        },
+        {
+          "from_node": "127.0.0.30",
+          "to_node": "243.0.0.7"
+        },
+        {
+          "from_node": "243.0.0.7",
+          "to_node": "127.0.0.40"
+        },
+        {
+          "from_node": "127.0.0.11",
+          "to_node": "127.0.0.10"
+        },
+        {
+          "from_node": "127.0.0.30",
+          "to_node": "243.0.0.8"
+        },
+        {
+          "from_node": "243.0.0.8",
+          "to_node": "127.0.0.10"
+        },
+        {
+          "from_node": "127.0.0.40",
+          "to_node": "243.0.0.9"
+        },
+        {
+          "from_node": "243.0.0.9",
+          "to_node": "127.0.0.50"
+        },
+        {
+          "from_node": "127.0.0.30",
+          "to_node": "243.0.0.10"
+        },
+        {
+          "from_node": "243.0.0.10",
+          "to_node": "127.0.0.50"
+        },
+        {
+          "from_node": "127.0.0.50",
+          "to_node": "243.0.0.11"
+        },
+        {
+          "from_node": "243.0.0.11",
+          "to_node": "127.0.0.20"
+        },
+        {
+          "from_node": "127.0.0.50",
+          "to_node": "243.0.0.12"
+        },
+        {
+          "from_node": "243.0.0.12",
+          "to_node": "127.0.0.40"
+        },
+        {
+          "from_node": "127.0.0.21",
+          "to_node": "127.0.0.20"
+        },
+        {
+          "from_node": "127.0.0.41",
+          "to_node": "127.0.0.40"
+        },
+        {
+          "from_node": "127.0.0.30",
+          "to_node": "243.0.0.13"
+        },
+        {
+          "from_node": "243.0.0.13",
+          "to_node": "127.0.0.20"
+        },
+        {
+          "from_node": "127.0.0.10",
+          "to_node": "127.0.0.11"
+        },
+        {
+          "from_node": "127.0.0.10",
+          "to_node": "243.0.0.14"
+        },
+        {
+          "from_node": "243.0.0.14",
+          "to_node": "127.0.0.20"
+        }
+      ]
+    },
+    "service-file": {
+      "path-request": [
+        {
+          "request-id": 2,
+          "bidirectional": false,
+          "source": "127.0.0.11",
+          "destination": "127.0.0.41",
+          "src-tp-id": "c3JjVHBJZA==",
+          "dst-tp-id": "ZHN0VHBJZA==",
+          "explicit-route-objects": {
+            "route-object-include-exclude": [
+              {
+                "explicit-route-usage": "gnpy-path-computation-simplified:route-include-ero",
+                "index": 0,
+                "num-unnum-hop": {
+                  "node-id": "127.0.0.20",
+                  "link-tp-id": "1",
+                  "hop-type": "STRICT"
+                }
+              },
+              {
+                "explicit-route-usage": "gnpy-path-computation-simplified:route-include-ero",
+                "index": 1,
+                "num-unnum-hop": {
+                  "node-id": "127.0.0.30",
+                  "link-tp-id": "1",
+                  "hop-type": "STRICT"
+                }
+              }
+            ]
+          },
+          "path-constraints": {
+            "te-bandwidth": {
+              "technology": "flexi-grid",
+              "trx_type": "openroadm-beta1",
+              "trx_mode": "W100G",
+              "effective-freq-slot": [
+                {
+                  "N": 0,
+                  "M": 4
+                }
+              ],
+              "spacing": 50000000000,
+              "path_bandwidth": 100
+            }
+          }
+        }
+      ]
+    }
+  }
+}
\ No newline at end of file