summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--actn-topo-generator/ongen.js636
-rw-r--r--actn-topo-generator/readme.txt16
2 files changed, 652 insertions, 0 deletions
diff --git a/actn-topo-generator/ongen.js b/actn-topo-generator/ongen.js
new file mode 100644
index 0000000..bcbd6d6
--- /dev/null
+++ b/actn-topo-generator/ongen.js
@@ -0,0 +1,636 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ACTN tool - Topo Generator
+ * ================================================================================
+ * Copyright (C) 2021 Huawei Technologies Co., Ltd. All rights reserved.
+ * ================================================================================
+ * 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.
+ * ============LICENSE_END=========================================================
+ */
+"use strict";
+Draw.loadPlugin(function (ui) {
+ const { app, dialog, BrowserWindow } = require("electron").remote;
+ const win = BrowserWindow.getFocusedWindow();
+ const fs = require("fs");
+ const path = require("path");
+ const ongenDir = path.dirname(app.getPath("exe")) + "/ongen/";
+ var graph = ui.editor.graph;
+ var model = ui.editor.graph.model;
+ const tsNumbers = new Map([
+ ["ODU1", 2],
+ ["ODU2", 8],
+ ["ODU3", 32],
+ ["ODU4", 80]
+ ]);
+ const getBitmap = (ts) =>
+ Buffer.alloc(Math.ceil(ts / 8), 255).toString("base64");
+ const btoa = (b) => Buffer.from(b, "utf8").toString("base64");
+ const http = require("http");
+
+ // Adds resource and menu for action
+ mxResources.parse("actnJSON=Export to ACTN JSON files");
+ mxResources.parse("initPNC=Initialize PNC Simulators");
+ mxResources.parse("collapseAll=Collapse Containers");
+ mxResources.parse("expandAll=Expand Containers");
+ mxResources.parse("assignNodeId=Auto-assign Node IDs");
+ mxResources.parse("loadSetting=Load Default Settings");
+ mxResources.parse("saveSetting=Save Current Settings");
+ var menu = ui.menus.get("extras");
+ var oldFunct = menu.funct;
+ menu.funct = function (menu, parent) {
+ oldFunct(menu, parent);
+ ui.menus.addMenuItems(
+ menu,
+ [
+ "-",
+ "actnJSON",
+ "initPNC",
+ "collapseAll",
+ "expandAll",
+ "assignNodeId",
+ "loadSetting"
+ ],
+ parent
+ );
+ };
+
+ // Adds action of collpaseAll
+ ui.actions.addAction(
+ "collapseAll",
+ function () {
+ var cells = model.filterDescendants(
+ (cell) => !!cell.getAttribute("te_node_id")
+ );
+ var nodeAllCollapsed = true;
+ cells.forEach((cell) => (nodeAllCollapsed &= cell.collapsed));
+ if (!nodeAllCollapsed) {
+ graph.cellsFolded(cells, true, false, true);
+ } else {
+ graph.selectVertices(null, true);
+ graph.cellsFolded(graph.getSelectionCells(), true, false, true);
+ graph.clearSelection();
+ }
+ },
+ null,
+ null,
+ "Ctrl+J"
+ );
+ ui.keyHandler.bindAction(74, !0, "collapseAll", !1);
+
+ // Adds action of expandAll
+ ui.actions.addAction(
+ "expandAll",
+ function () {
+ graph.selectVertices(null, true);
+ graph.cellsFolded(graph.getSelectionCells(), false, false, true);
+ graph.clearSelection();
+ },
+ null,
+ null,
+ "Ctrl+K"
+ );
+ ui.keyHandler.bindAction(75, !0, "expandAll", !1);
+
+ // Adds action of loadSetting
+ ui.actions.addAction("loadSetting", function () {
+ var plugins = mxSettings.getPlugins();
+ mxSettings.parse(DEFAULT_SETTING);
+ mxSettings.setPlugins(plugins);
+ mxSettings.save();
+
+ ui.setDatabaseItem(
+ null,
+ [
+ {
+ title: ".scratchpad",
+ size: SCRATCHPAD.length,
+ lastModified: Date.now(),
+ type: "L"
+ },
+ { title: ".scratchpad", data: SCRATCHPAD }
+ ],
+ null,
+ null,
+ ["filesInfo", "files"]
+ );
+ notify("Restart the App to take effect.");
+ });
+
+ // Adds hidden action with CTRL+H
+ ui.actions.addAction("saveSetting", function () {
+ win.webContents.toggleDevTools();
+ fs.writeFileSync(
+ ongenDir + "default-settings.json",
+ JSON.stringify(mxSettings.settings)
+ );
+ });
+ ui.keyHandler.bindAction(72, !0, "saveSetting", !1);
+
+ // Adds action for actn JSON
+ ui.actions.addAction("actnJSON", function () {
+ var domains = generateJson();
+ if (!domains) {
+ return;
+ }
+ //Get file base name
+ var fileDir = dialog.showOpenDialogSync(win, {
+ title: "Choose A Directory to Save ACTN JSON Data Files",
+ buttonLabel: "Yes",
+ properties: ["openDirectory"]
+ });
+ if (!fileDir) {
+ return;
+ }
+
+ domains.forEach(function (domain) {
+ var fileName = fileDir + "/" + (domain._port_ || domain.te_name);
+ fs.writeFileSync(fileName + "{otn-topo}.json", domain._data_);
+ });
+
+ dialog.showOpenDialogSync(win, {
+ title: "Generated ACTN JSON Data Files",
+ defaultPath: fileDir + "/*",
+ buttonLabel: "Ok",
+ filters: [{ name: "DATA", extensions: ["json", "xlsx"] }],
+ properties: ["multiSelections", "dontAddToRecent"]
+ });
+ });
+
+ // Adds action for initializing PNC simulators
+ ui.actions.addAction("initPNC", function () {
+ var domains = generateJson();
+ if (!domains) {
+ return;
+ }
+ //Send PUT messages to each PNC simulator
+ var reqNum = 0;
+ domains.forEach(function (domain) {
+ reqNum++;
+ const options = {
+ host: domain._ip_,
+ port: parseInt(domain._port_),
+ path: "/pncsimu/v1/reload-data",
+ method: "POST",
+ headers: { "Content-Type": "application/json" }
+ };
+ const req = http.request(options, function (res) {
+ if (res.statusCode != 201 && res.statusCode != 200) {
+ notify(
+ `${res.statusCode} Error from ${options.host}:${options.port}`
+ );
+ return res.resume();
+ }
+ if (!--reqNum) {
+ notify(`${domains.length} PNC simulators initialized`);
+ }
+ });
+ req.write(domain._data_);
+ req.end();
+ });
+ });
+
+ // Adds action: Auto assign node id of all nodes
+ ui.actions.addAction("assignNodeId", function () {
+ var domains = model.filterDescendants(
+ (cell) => !!cell.getAttribute("network_types")
+ );
+ var i = 1;
+ graph.clearSelection();
+ model.beginUpdate();
+ domains.forEach(function (domain) {
+ var nodes = model.getChildVertices(domain);
+ var j = 1;
+ var k = 1;
+ nodes.forEach(function (node) {
+ if (!node.hasAttribute("te_node_id")) {
+ return;
+ }
+ var newXmlValue = node.value.cloneNode(false);
+ newXmlValue.setAttribute("te_node_id", "10." + i + "." + j + "." + k);
+ model.setValue(node, newXmlValue);
+ graph.addSelectionCell(node);
+ if (k < 254) {
+ k++;
+ } else {
+ k = 1;
+ j++;
+ }
+ });
+ i++;
+ });
+ model.endUpdate();
+ });
+
+ function generateJson() {
+ //prepare js object arrays for domains, nodes, TP&links
+ var domainNetworks = [];
+ var domains = model.filterDescendants(
+ (cell) => !!cell.getAttribute("network_types")
+ );
+ var domainMap = new Map();
+ graph.clearSelection();
+ try {
+ domains.forEach(function (domain) {
+ checkObjId("Domain", "te_name", domainMap, domain);
+ var otnTopo = xml2js(domain.value);
+ var ethTopo = { ...otnTopo };
+ ethTopo["network_types"] = "eth-tran-topology";
+
+ var nodes = model.getChildVertices(domain);
+ var nodeMap = new Map();
+ (otnTopo.nodes = []), (ethTopo.nodes = []);
+ (otnTopo.links = []), (ethTopo.links = []);
+ nodes.forEach(function (node) {
+ checkObjId("Node", "te_node_id", nodeMap, node);
+ let otnNode = xml2js(node.value);
+ let ethNode = { ...otnNode };
+ (otnNode.tps = []), (ethNode.tps = []);
+ otnTopo.nodes.push(otnNode);
+ ethTopo.nodes.push(ethNode);
+
+ let tps = model.getChildVertices(node);
+ let tpMap = new Map();
+ tps.forEach(function (tp) {
+ checkObjId("TP", "te_tp_id", tpMap, tp);
+ let newTpLink = xml2js(tp.value);
+ newTpLink["te_node_id"] = otnNode["te_node_id"];
+ if (newTpLink["otn_odu_type"])
+ newTpLink.ts_number =
+ tsNumbers.get(newTpLink["otn_odu_type"]) || 0;
+ let links = model.getConnections(tp);
+ let inniLinkNumber = 0,
+ enniLinkNumber = 0;
+ for (let link of links) {
+ if (link.hasAttribute("te_inter_domain_plug_id")) {
+ if (!link.getAttribute("te_inter_domain_plug_id")) {
+ link.setAttribute(
+ "te_inter_domain_plug_id",
+ link.getId().substr(-8)
+ );
+ }
+ enniLinkNumber++;
+ } else if (link.hasAttribute("te_delay_metric")) {
+ inniLinkNumber++;
+ } else {
+ notify("unknown link!");
+ graph.addSelectionCell(link);
+ return;
+ }
+ }
+ if (inniLinkNumber + enniLinkNumber > 1) {
+ notify("NNI TP has more than one link!");
+ graph.addSelectionCell(tp);
+ return;
+ }
+ if (newTpLink["otn_odu_type"] && links.length == 1) {
+ xml2js(links[0].value, newTpLink);
+ newTpLink.bitmap = getBitmap(newTpLink.ts_number);
+ let dest_tp = model.getOpposites(links, tp, true, true);
+ if (dest_tp.length != 0) {
+ if (
+ newTpLink["otn_odu_type"] !=
+ dest_tp[0].getAttribute("otn_odu_type")
+ ) {
+ notify(
+ "The two TPs of the link don't have the same odu_type!"
+ );
+ graph.addSelectionCell(tp);
+ graph.addSelectionCell(links[0]);
+ graph.addSelectionCell(dest_tp[0]);
+ return;
+ }
+ let dest_node = dest_tp[0].parent;
+ if (
+ dest_node != null &&
+ dest_node != undefined &&
+ node.parent == dest_node.parent
+ ) {
+ newTpLink["dest_node"] = dest_node.getAttribute("te_node_id");
+ newTpLink["dest_tp"] = dest_tp[0].getAttribute("te_tp_id");
+ }
+ }
+ }
+
+ if (newTpLink["otn_odu_type"]) {
+ otnNode.tps.push(newTpLink);
+ otnTopo.links.push(newTpLink);
+ } else if (newTpLink["eth_bandwidth"]) {
+ ethNode.tps.push(newTpLink);
+ ethTopo.links.push(newTpLink);
+ }
+ });
+ });
+
+ //generate JSON data for each domain
+ let jsonData = jjsRender(ONGEN_TMPL, {
+ networks: [otnTopo, ethTopo],
+ btoa: btoa
+ });
+ otnTopo._data_ = JSON.stringify(JSON.parse(jsonData), null, 3);
+ domainNetworks.push(otnTopo);
+ });
+ } catch (e) {
+ return notify(e.message);
+ }
+ if (domainNetworks.length == 0) return notify("No domain network exists");
+ return domainNetworks;
+ }
+
+ function notify(message) {
+ dialog.showMessageBoxSync(win, { title: "ongen", message: message });
+ }
+
+ //convert data from xml node to js object
+ function xml2js(xmlNode, jsObj = {}) {
+ for (let attr of xmlNode.attributes) {
+ if (attr.name != "label" && attr.name != "placeholders") {
+ attr.value = attr.value.replace(/(^\s*)|(\s*$)/gm, "");
+ jsObj[attr.name] = attr.value;
+ }
+ }
+ return jsObj;
+ }
+
+ //check the id of a new object: null, undefined or conflicts with other's
+ function checkObjId(objName, objIdName, objMap, newObj) {
+ let newObjValue = newObj.getAttribute(objIdName);
+ if (!newObjValue) {
+ graph.addSelectionCell(newObj);
+ throw new Error(objName + " " + objIdName + " is missing!");
+ }
+ if (objMap.has(newObjValue)) {
+ graph.addSelectionCell(newObj);
+ graph.addSelectionCell(objMap.get(newObjValue));
+ throw new Error("Same " + objName + " " + objIdName + "!");
+ }
+ objMap.set(newObjValue, newObj);
+ return true;
+ }
+
+ function jjsRender(tmpl, data, debug = false) {
+ // generate variable part
+ var i = 2,
+ res = [""];
+ Object.keys(data).forEach((k) => (res[0] += `var ${k} = __data['${k}'];`));
+ res[1] = "var __i = 0, __res = []\n__res[__i++] = `";
+
+ // generate body part
+ var sRegex = /<</g,
+ eRegex = />>/g;
+ while (sRegex.test(tmpl)) {
+ res[i++] = tmpl.slice(eRegex.lastIndex, sRegex.lastIndex - 2);
+ eRegex.lastIndex = sRegex.lastIndex;
+ if (!eRegex.test(tmpl)) throw new Error(`>> not found.`);
+ res[++i] = tmpl.slice(sRegex.lastIndex, eRegex.lastIndex - 2);
+ res[i - 1] = /[\{\}]/.test(res[i++]) ? "`\n" : "`\n__res[__i++] = ";
+ res[i++] = "\n__res[__i++] = `";
+ sRegex.lastIndex = eRegex.lastIndex;
+ }
+ res[i++] = tmpl.slice(eRegex.lastIndex);
+
+ // generate return part
+ res[i] = "`\nreturn __res.join('')";
+ if (debug) return res.join("");
+ return Function("__data", res.join(""))(data);
+ }
+});
+
+const DEFAULT_SETTING = `{"language":"","configVersion":null,"customFonts":[],"libraries":"","customLibraries":["L.scratchpad"],"recentColors":["999999","B3B3B3"],"formatWidth":240,"createTarget":false,"pageFormat":{"x":0,"y":0,"width":1169,"height":827},"search":true,"showStartScreen":true,"gridColor":"#d0d0d0","darkGridColor":"#6e6e6e","autosave":false,"resizeImages":null,"openCounter":272,"version":18,"unit":1,"isRulerOn":false,"ui":"dark"}`;
+const SCRATCHPAD = `<mxlibrary>[{"xml":"dVTBbtswDP0aHzvIctI0xyXtusM2FN1hR0O2OVurLAoyUyf7+lGynDjBmiAAxfdIPlJUsmLfH5+9ct13bMBkxVNW7D0iTVZ/3IMxmRS6yYrHTErBv0x++QDNIyqc8mDpPwFY/YGamGFUFYo9RnBNUFrVA1uZvDccuKs8W22w2Nlgr7QtuUgghAJzQEzw9aBG0Hc/gEb0b0mBUTV0aBrww5U0O7FKOjlICJK9I3RosD1NpNKhp3KWFzwXCVMykYjaJVouN58Ef1OZmRjDr2c10Mkk5UOnXDB5XsoYYAFe9dyzA697IPC32MsF2I2dJvjpuE9mjXyD7OuoD1PN2fytjdlzTyGHRcuk3aD/Bi7LvF+vN8VmI1bb/EEU8oHBd/Cka2U+G91aZlVIhEFMvKoXHDRpDEDNVxsFzBHfbgg8S0ZVynOm12iJZxibyqez5WVQVZyGYA93pp4aTYngoT74Qb/DK8zKQxPk8Q1+6Ya6xJs8c6+ZLLbxE3cn3EXQCccPFzRfXNEzIIvwvAZiTBUYXRVp7TrQbZfC5CrtgEpr1J5jz+leQ3+25QalSAK2Keo0L1I6L8qdfVflbqopw1O1imCHB9sMy0fGxqKTiyvu33xML3E+Xl78FL38Q/gH","w":435,"h":240,"aspect":"fixed","title":"Network"},{"xml":"dVPRbsIwDPyavKKQCtBeKYyXTZrYB6C08dqgNKlSA2VfP6dJoUwgtZJ9d87VdsqyvOl3Xrb1p1NgWLZlWe6dwxg1fQ7GMMG1YtmGCcHpZeL9BTsfWN5KDxafFLjiCCWSwsgimG0GcoFwsLIBiphYGipcF56iKkSJpo87kEtQDA5GllA7o8B3D9YTbYT5LD13PlgNZFBGeJSLkD721uHVJD0Buu0oWV9qjfDd0kcQc6HxEVZjE1qaU/ijjcmdcZ5y62yoKJ1FqS34JPFQnnynz7CHTv+GY3hUWZqQLMyINIByqzSmMus+0ugCOYzxSyqlbZUg6FtpVUpic2fwCP3LDc0nPe/AkaG/kuSiFdZR8RaXyGvQVY2PmEzzr26Vt8P2oRNbUSuCj/arVHZN817MVmmlE7/lEz/xz08aBG8lwtqdrOqmt4yCSSd3aFjomKarOKb3Kx+rp3/EHw==","w":90,"h":90,"aspect":"fixed","title":"Node"},{"xml":"dVJNb4MwDP01OXYK4aPsuEHX07TDtDNKwZRsAaPgjvLvF0jaUmklQkree36OHbMwa897I/vmHSvQLNyxMDOI5HbtOQOtmeCqYmHOhOD2Z+LtARssLO+lgY7+CcDDN5RkFVoe5mT5QsYEBfWFNRGxN9CyhAZ1BWa4c74qHSjWaCdb8PjGL0cjdQVWp4Km3gs+8q/oxpVa2esWtSxVd3SCWuoBnOKSbMl1X/ZAk/aOBk9dBbM0YOHr2CiCz94WYYHRdtdiDbXa07XSOkONZokNEy6fg63FBzL4AytG5NuE8zkCO1rh9fJZvAWSu0qRN0abUtFkT/EcJbXG8cUYHOcuzogr6RcMwfnhkwWrSveANoeZrGRybPyUpFEYBWma8NQuAZvAN3NUFTVOFfqnaUAdG+8r+FPkldI/6/FqfxsSu/Edvhz92FyOt/FcpHfT+wc=","w":32,"h":20.44,"aspect":"fixed","title":"OTN NNI"},{"xml":"dVJNb4MwDP01uVZ8qEM9rtD2NGm3bacqEAuyBVIFt4V/PyeEQrQVCcnPz89fMUvzdjgZfmnetADF0gNLc6M1TlY75KAUSyIpWFqwJInoZ8nxCRs7NrpwAx3+I9DlN1RIEYqXtljhSCdRvIJGKwGmD3IhnKkvPp5bQCOrgJvLuhxhRz2OCiYWOvFqjL4T6nRHzn2DrS0ek0nkUSqLIkLV1dxAeKpHo3/gQwpsyEMF9tQCPwiJPhrI5KUr41INEj9X9pcN22w9KoZZZcHowTQHiBqeLjBeDXcCbdcwUsjd90Xsdtpx1ICsGwx9hnaH8ham537F9SPdo8K7lp19oNG/3ibLdsuXJVmYoddXU4EXrQ9jzjNMcS+7UIfc1IB/dGSshlxc7lFn6I9ohsuxTur1Lf8C","w":69,"h":1,"aspect":"fixed","title":"INNI Link"},{"xml":"dVNNb4MwDP01XCcKarXrSj9Ok3Zbd0KBGMgWSGXcFv79TEjaoK0HJD8/28+xTZRm7XBEcW7ejQQdpfsozdAYmq12yEDrKImVjNJdlCQxf1FyeMKuLBufBUJH/ySY4htK4ggtiklsZ0mbokUJjdESsF/UIshVR4C5NK1QXX7Wlzr3enOIRxYuG+tp1DCz0Mk3RHNj1JmOnduG2qmHFZtMHpSeUMyovOAVpKN6QvMDn0pSwx4W2LZAYi8VuWhgUxRWxpYaFJ0C+2sKe1k7tBt81gRGByrWzow2aFtNq6pKyvKuHTByU2zWG9sw4XjytSYQ6EzwIWSRV5onBrKGpxtbBWM8guHX4sghNzcBZtfzUuMGVN3Q0oegBanrsrxwO63v5e4KH4bXG4T05oIlOG94aj5wcJWT12VtElgD/UlkI3jGw2UPxEN3lx4+7n/ODn+PXw==","w":128,"h":1,"aspect":"fixed","title":"ENNI Link"},{"xml":"dVJNb8IwDP01OW5KEzF2XmGcph2mnavQuDQsjatgBv33c5sARRqNKsXvPdvxh9Bld95E07cfaMELvRa6jIiUbt25BO+Fks4KvRJKSf6Fen/AFhMrexMh0D8OuN1DTazwZjsmW03kgqCivuIgapEDeFNDi95CPNxFvionUBVqDgfTQVY/5ZNopFChPVY09FnwufpWN672jt9bNaZ2YZcEFI+QBNdko3lf9oEGnwNGPAYLo7QQ+u3UOoKvnotg4MTdZaylzme6cd6X6DFOvvrFSNk0jB8o4g/MGL2UsixHDww0w5vpY7wDMmvrKAdGTuloYGsh2awxBO632U6vHJFU0i9EgvPDkRWzSjeAnCMOLDk5S21S6DRV2YLbtdlLyefXZYJNHtru6nxbAb7k/l3MvBQX87Z8k/RuN/8A","w":30,"h":20.87,"aspect":"fixed","title":"OTN UNI"},{"xml":"dVLbbsIwDP2aPIJyaVFfRwc87WkfgFJiSLc0qYI36N/PTQMUaSSKFB8fH8eOmaq76y7q3n4EA46pDVN1DAGnW3etwTkmeWuYemdScjpMbl94RfLyXkfw+E9AaL7ggMRwuhmTvSdnibDHfk8isswCTh/ABmcgnp+U78wElkLOYa87yOxF3pMb0O4b7c2lNWgzg09rItwEk9xzaWccXFaN4ccbGKmCqfXFtgifPT2UgAt1kDCLncvuY+tcHVyIKVaJRgsg7fUZY/iGmYfz1eZtO0YEjzP8mBbhHaDemBazcKCULQ5klZzMQ/Ceeqqb9MoRmUr6hYhwffktYlbpDgLliANRhtzY5aoqVCGqasUr2hIWophEZj1UuXsW2pPNupIvi8zU+etOd/nHINAld/hm5tG4mY8RTNSnCf0D","w":30,"h":20.44,"aspect":"fixed","title":"ETH UNI"}]</mxlibrary>`;
+
+const ONGEN_TMPL = `
+{
+ "ietf-network:networks": {
+ "network": [
+ << networks.forEach((network, i)=> { >>
+ << i ? ',' : '' >>
+ {
+ "network-id": "providerId-<< network.domain_id >>-clientId-0-topologyId-<< i+1 >>",
+ "ietf-te-topology:te": {
+ "name": "<< network.te_name >>"
+ },
+ "ietf-te-topology:provider-id": << network.domain_id >>,
+ "ietf-te-topology:client-id": 0,
+ "ietf-te-topology:te-topology-id": "<< i+1 >>",
+ "network-types": {
+ "ietf-te-topology:te-topology": {
+ << if (network.network_types == "otn-topology") { >>
+ "ietf-otn-topology:<< network.network_types >>": {}
+ << } else if (network.network_types == "eth-tran-topology") { >>
+ "ietf-eth-te-topology:<< network.network_types >>": {}
+ << } >>
+ }
+ },
+ "node": [
+ << network.nodes.forEach((node, i)=> { >>
+ << i ? ',' : '' >>
+ {
+ "node-id": "<< node.te_node_id >>",
+ "ietf-te-topology:te-node-id": "<< node.te_node_id >>",
+ "ietf-network-topology:termination-point": [
+ << node.tps.forEach((tp, i)=> { >>
+ << i ? ',' : '' >>
+ {
+ "tp-id": "<< tp.te_tp_id >>",
+ "ietf-te-topology:te-tp-id": << tp.te_tp_id >>,
+ << if (tp.eth_bandwidth) { >>
+ "ietf-eth-te-topology:svc": {
+ "client-facing": true,
+ "supported-classification": {
+ "port-classification": true,
+ "vlan-classification": {
+ "outer-tag": {
+ "supported-tag-types": [
+ "ietf-eth-tran-types:classify-c-vlan",
+ "ietf-eth-tran-types:classify-s-vlan"
+ ],
+ "vlan-bundling": false,
+ "vlan-range": "1-4094"
+ }
+ }
+ }
+ },
+ << } >>
+ "ietf-te-topology:te": {
+ "name": "<< tp.te_tp_name >>",
+ "admin-status": "up",
+ "oper-status": "up",
+ << if (tp.te_inter_domain_plug_id) { >>
+ "inter-domain-plug-id": "<< btoa(tp.te_inter_domain_plug_id) >>",
+ << } >>
+ << if (tp.otn_odu_type) { >>
+ "ietf-otn-topology:client-facing": << tp.otn_client_facing >>,
+ "interface-switching-capability": [
+ {
+ "encoding": "ietf-te-types:lsp-encoding-oduk",
+ "switching-capability": "ietf-te-types:switching-otn",
+ "max-lsp-bandwidth": [
+ {
+ "priority": 7,
+ "te-bandwidth": {
+ "ietf-otn-topology:odu-type": "ietf-otn-types:prot-<< tp.otn_odu_type>>"
+ }
+ }
+ ]
+ }
+ ]
+ << } >>
+ << if (tp.eth_bandwidth) { >>
+ "interface-switching-capability": [
+ {
+ "encoding": "ietf-te-types:lsp-encoding-ethernet",
+ "switching-capability": "ietf-te-types:switching-l2sc",
+ "max-lsp-bandwidth": [
+ {
+ "priority": 7,
+ "te-bandwidth": {
+ "ietf-eth-te-topology:eth-bandwidth": << tp.eth_bandwidth >>
+ }
+ }
+ ]
+ }
+ ]
+ << } >>
+ }
+ }
+ << }) >>
+ ],
+ "ietf-te-topology:te": {
+ "oper-status": "up",
+ "te-node-attributes": {
+ "admin-status": "up",
+ "name": "<< node.te_name >>"
+ }
+ << if (network.network_types == "otn-topology") { >>,
+ "tunnel-termination-point": [
+ << let $i = 0; node.tps.forEach((tp, i)=> { >>
+ << if (tp.otn_client_facing == "false") { >>
+ << $i ? ',' : ''; $i = 1 >>
+ {
+ "tunnel-tp-id": "<< btoa(tp.te_tp_id) >>",
+ "admin-status": "up",
+ "oper-status": "up",
+ "encoding": "ietf-te-types:lsp-encoding-oduk",
+ "name": "<< tp.te_tp_name >>",
+ "protection-type": "ietf-te-types:lsp-protection-unprotected",
+ "switching-capability": "ietf-te-types:switching-otn",
+ "local-link-connectivities": {
+ "local-link-connectivity": [
+ {
+ "is-allowed": true,
+ "link-tp-ref": "<< tp.te_tp_id >>"
+ }
+ ]
+ }
+ }
+ << } }) >>
+ ]
+ << } >>
+ }
+ }
+ << }) >>
+ ],
+ "ietf-network-topology:link": [
+ << network.links.forEach((link, i)=> { >>
+ << i ? ',' : '' >>
+ {
+ "link-id": "<< link.te_node_id >>-<< link.te_tp_id >>",
+ "source": {
+ "source-node": "<< link.te_node_id >>",
+ "source-tp": "<< link.te_tp_id >>"
+ },
+ << if (link.dest_node) { >>
+ "destination": {
+ "dest-node": "<< link.dest_node >>",
+ "dest-tp": "<< link.dest_tp >>"
+ },
+ << } >>
+ "ietf-te-topology:te": {
+ "oper-status": "up",
+ "te-link-attributes": {
+ "access-type": "point-to-point",
+ "admin-status": "up",
+ "name": "<< link.te_node_id >>-<< link.te_tp_id >>",
+ << if (link.te_delay_metric) { >>
+ "te-delay-metric": << link.te_delay_metric >>,
+ << } >>
+ << if (link.ts_number) { >>
+ << if (link.te_inter_domain_plug_id) { >>
+ "label-restriction": [
+ {
+ "inclusive-exclusive": "inclusive",
+ "index": 1,
+ "label-end": {
+ "te-label": {
+ "ietf-otn-topology:tpn": << link.ts_number >>
+ }
+ },
+ "label-start": {
+ "te-label": {
+ "ietf-otn-topology:tpn": 1
+ }
+ },
+ "label-step": 1,
+ "range-bitmap": "<< link.bitmap >>"
+ }
+ ],
+ << } >>
+ "max-link-bandwidth": {
+ "te-bandwidth": {
+ "ietf-otn-topology:odulist": [
+ {
+ "odu-type": "ietf-otn-types:prot-ODU0",
+ "number": << link.ts_number >>
+ }
+ ]
+ }
+ },
+ "unreserved-bandwidth": [
+ {
+ "priority": 7,
+ "te-bandwidth": {
+ "ietf-otn-topology:odulist": [
+ {
+ "number": << link.ts_number >>,
+ "odu-type": "ietf-otn-types:prot-ODU0"
+ }
+ ]
+ }
+ }
+ ]
+ << } >>
+ << if (link.eth_bandwidth) { >>
+ "max-link-bandwidth": {
+ "te-bandwidth": {
+ "ietf-eth-te-topology:eth-bandwidth": << link.eth_bandwidth >>
+ }
+ },
+ "unreserved-bandwidth": [
+ {
+ "priority": 7,
+ "te-bandwidth": {
+ "ietf-eth-te-topology:eth-bandwidth": << link.eth_bandwidth >>
+ }
+ }
+ ]
+ << } >>
+ }
+ }
+ }
+ << }) >>
+ ]
+ }
+ << }) >>
+ ]
+ }
+}`;
diff --git a/actn-topo-generator/readme.txt b/actn-topo-generator/readme.txt
new file mode 100644
index 0000000..32640ce
--- /dev/null
+++ b/actn-topo-generator/readme.txt
@@ -0,0 +1,16 @@
+Introduction:
+This tool is a plugin of Draw.io desktop application.
+It can automatically generate ACTN topology JSON data from graph.
+
+Installation and initialization:
+1) Extract the draw.io.7z file
+2) Run draw.io.exe in folder draw.io
+3) Click menu Extras->Plugins…
+4) Import the plugin file named "ongen.js"
+5) Click menu Extras->Load Default Settings
+
+Design ACTN multi-domain OTN topology and generate JSON data.
+1) Add network, node, port or link via drag-and-drop or copy-and-paste.
+2) Collapse or expand containers by shortcut "CTRL+J" and "CTRL+K"
+3) Assign the node id of all nodes automatically by clicking the menu Extras->'Auto-assign Node Id'
+4) Export to ACTN JSON data by clicking the menu Extras->'Export as ACTN JSON'.