diff options
Diffstat (limited to 'actn-topo-generator')
-rw-r--r-- | actn-topo-generator/ongen.js | 636 | ||||
-rw-r--r-- | actn-topo-generator/readme.txt | 16 |
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'. |