| /* |
| * Copyright 2015-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. |
| */ |
| |
| /* |
| ONOS GUI -- Topology Model Module. |
| Auxiliary functions for the model of the topology; that is, our internal |
| representations of devices, hosts, links, etc. |
| */ |
| |
| (function () { |
| 'use strict'; |
| |
| // injected refs |
| var $log, fs, rnd; |
| |
| // api to topoForce |
| var api; |
| /* |
| projection() |
| network {...} |
| restyleLinkElement( ldata ) |
| removeLinkElement( ldata ) |
| */ |
| |
| // shorthand |
| var lu, rlk, nodes, links, linksByDevice; |
| |
| var dim; // dimensions of layout [w,h] |
| |
| // configuration 'constants' |
| var defaultLinkType = 'direct', |
| nearDist = 15; |
| |
| |
| function coordFromLngLat(loc) { |
| var p = api.projection(); |
| return p ? p([loc.longOrX, loc.latOrY]) : [0, 0]; |
| } |
| |
| function lngLatFromCoord(coord) { |
| var p = api.projection(); |
| return p ? p.invert(coord) : [0, 0]; |
| } |
| |
| function positionNode(node, forUpdate) { |
| var meta = node.metaUi, |
| x = meta && meta.x, |
| y = meta && meta.y, |
| xy; |
| |
| // if the device contains explicit LONG/LAT data, use that to position |
| if (setLongLat(node)) { |
| // indicate we want to update cached meta data... |
| return true; |
| } |
| |
| // else if we have [x,y] cached in meta data, use that... |
| if (x !== undefined && y !== undefined) { |
| node.fixed = true; |
| node.px = node.x = x; |
| node.py = node.y = y; |
| return; |
| } |
| |
| // if this is a node update (not a node add).. skip randomizer |
| if (forUpdate) { |
| return; |
| } |
| |
| // Note: Placing incoming unpinned nodes at exactly the same point |
| // (center of the view) causes them to explode outwards when |
| // the force layout kicks in. So, we spread them out a bit |
| // initially, to provide a more serene layout convergence. |
| // Additionally, if the node is a host, we place it near |
| // the device it is connected to. |
| |
| function rand() { |
| return { |
| x: rnd.randDim(dim[0]), |
| y: rnd.randDim(dim[1]), |
| }; |
| } |
| |
| function near(node) { |
| return { |
| x: node.x + nearDist + rnd.spread(nearDist), |
| y: node.y + nearDist + rnd.spread(nearDist), |
| }; |
| } |
| |
| function getDevice(cp) { |
| var d = lu[cp.device]; |
| return d || rand(); |
| } |
| |
| xy = (node.class === 'host') ? near(getDevice(node.cp)) : rand(); |
| angular.extend(node, xy); |
| } |
| |
| function setLongLat(node) { |
| var loc = node.location, |
| coord; |
| |
| if (loc && loc.locType === 'geo') { |
| coord = coordFromLngLat(loc); |
| node.fixed = true; |
| node.px = node.x = coord[0]; |
| node.py = node.y = coord[1]; |
| return true; |
| } |
| } |
| |
| function resetAllLocations() { |
| nodes.forEach(function (d) { |
| setLongLat(d); |
| }); |
| } |
| |
| function mkSvgCls(dh, t, on) { |
| var ndh = 'node ' + dh, |
| ndht = t ? ndh + ' ' + t : ndh; |
| return on ? ndht + ' online' : ndht; |
| } |
| |
| function createDeviceNode(device) { |
| var node = device; |
| |
| // Augment as needed... |
| node.class = 'device'; |
| node.svgClass = mkSvgCls('device', device.type, device.online); |
| positionNode(node); |
| return node; |
| } |
| |
| function createHostNode(host) { |
| var node = host; |
| |
| // Augment as needed... |
| node.class = 'host'; |
| if (!node.type) { |
| node.type = 'endstation'; |
| } |
| node.svgClass = mkSvgCls('host', node.type); |
| positionNode(node); |
| return node; |
| } |
| |
| function createHostLink(hostId, devId, devPort) { |
| var linkKey = hostId + '/0-' + devId + '/' + devPort, |
| lnk = linkEndPoints(hostId, devId); |
| |
| if (!lnk) { |
| return null; |
| } |
| |
| // Synthesize link ... |
| angular.extend(lnk, { |
| key: linkKey, |
| class: 'link', |
| // NOTE: srcPort left undefined (host end of the link) |
| tgtPort: devPort, |
| |
| type: function () { return 'hostLink'; }, |
| expected: function () { return true; }, |
| online: function () { |
| // hostlink target is edge switch |
| return lnk.target.online; |
| }, |
| linkWidth: function () { return 1; }, |
| }); |
| return lnk; |
| } |
| |
| function createLink(link) { |
| var lnk = linkEndPoints(link.src, link.dst); |
| |
| if (!lnk) { |
| return null; |
| } |
| |
| angular.extend(lnk, { |
| key: link.id, |
| class: 'link', |
| fromSource: link, |
| srcPort: link.srcPort, |
| tgtPort: link.dstPort, |
| position: { |
| x1: 0, |
| y1: 0, |
| x2: 0, |
| y2: 0, |
| }, |
| |
| // functions to aggregate dual link state |
| type: function () { |
| var s = lnk.fromSource, |
| t = lnk.fromTarget; |
| return (s && s.type) || (t && t.type) || defaultLinkType; |
| }, |
| expected: function () { |
| var s = lnk.fromSource, |
| t = lnk.fromTarget; |
| return (s && s.expected) && (t && t.expected); |
| }, |
| online: function () { |
| var s = lnk.fromSource, |
| t = lnk.fromTarget, |
| both = lnk.source.online && lnk.target.online; |
| return both && (s && s.online) && (t && t.online); |
| }, |
| linkWidth: function () { |
| var s = lnk.fromSource, |
| t = lnk.fromTarget, |
| ws = (s && s.linkWidth) || 0, |
| wt = (t && t.linkWidth) || 0; |
| return lnk.position.multiLink ? 5 : Math.max(ws, wt); |
| }, |
| extra: link.extra, |
| }); |
| return lnk; |
| } |
| |
| |
| function linkEndPoints(srcId, dstId) { |
| var srcNode = lu[srcId], |
| dstNode = lu[dstId], |
| sMiss = !srcNode ? missMsg('src', srcId) : '', |
| dMiss = !dstNode ? missMsg('dst', dstId) : ''; |
| |
| if (sMiss || dMiss) { |
| $log.error('Node(s) not on map for link:' + sMiss + dMiss); |
| // logicError('Node(s) not on map for link:\n' + sMiss + dMiss); |
| return null; |
| } |
| |
| return { |
| source: srcNode, |
| target: dstNode, |
| }; |
| } |
| |
| function missMsg(what, id) { |
| return '\n[' + what + '] "' + id + '" missing'; |
| } |
| |
| |
| function makeNodeKey(d, what) { |
| var port = what + 'Port'; |
| return d[what] + '/' + d[port]; |
| } |
| |
| function makeLinkKey(d, flipped) { |
| var one = flipped ? makeNodeKey(d, 'dst') : makeNodeKey(d, 'src'), |
| two = flipped ? makeNodeKey(d, 'src') : makeNodeKey(d, 'dst'); |
| return one + '-' + two; |
| } |
| |
| function findLinkById(id) { |
| // check to see if this is a reverse lookup, else default to given id |
| var key = rlk[id] || id; |
| return key && lu[key]; |
| } |
| |
| function findLink(linkData, op) { |
| var key = makeLinkKey(linkData), |
| keyrev = makeLinkKey(linkData, 1), |
| link = lu[key], |
| linkRev = lu[keyrev], |
| result = {}, |
| ldata = link || linkRev, |
| rawLink; |
| |
| if (op === 'add') { |
| if (link) { |
| // trying to add a link that we already know about |
| result.ldata = link; |
| result.badLogic = 'addLink: link already added'; |
| |
| } else if (linkRev) { |
| // we found the reverse of the link to be added |
| result.ldata = linkRev; |
| if (linkRev.fromTarget) { |
| result.badLogic = 'addLink: link already added'; |
| } |
| } |
| } else if (op === 'update') { |
| if (!ldata) { |
| result.badLogic = 'updateLink: link not found'; |
| } else { |
| rawLink = link ? ldata.fromSource : ldata.fromTarget; |
| result.updateWith = function (data) { |
| angular.extend(rawLink, data); |
| api.restyleLinkElement(ldata); |
| }; |
| } |
| } else if (op === 'remove') { |
| if (!ldata) { |
| result.badLogic = 'removeLink: link not found'; |
| } else { |
| rawLink = link ? ldata.fromSource : ldata.fromTarget; |
| |
| if (!rawLink) { |
| result.badLogic = 'removeLink: link not found'; |
| |
| } else { |
| result.removeRawLink = function () { |
| // remove link out of aggregate linksByDevice list |
| var linksForDevPair = linksByDevice[ldata.devicePair], |
| rmvIdx = fs.find(ldata.key, linksForDevPair, 'key'); |
| if (rmvIdx >= 0) { |
| linksForDevPair.splice(rmvIdx, 1); |
| } |
| ldata.position.multilink = linksForDevPair.length >= 5; |
| |
| if (link) { |
| // remove fromSource |
| ldata.fromSource = null; |
| if (ldata.fromTarget) { |
| // promote target into source position |
| ldata.fromSource = ldata.fromTarget; |
| ldata.fromTarget = null; |
| ldata.key = keyrev; |
| delete lu[key]; |
| lu[keyrev] = ldata; |
| delete rlk[keyrev]; |
| } |
| } else { |
| // remove fromTarget |
| ldata.fromTarget = null; |
| delete rlk[keyrev]; |
| } |
| if (ldata.fromSource) { |
| api.restyleLinkElement(ldata); |
| } else { |
| api.removeLinkElement(ldata); |
| } |
| }; |
| } |
| } |
| } |
| return result; |
| } |
| |
| function findDevices(offlineOnly) { |
| var a = []; |
| nodes.forEach(function (d) { |
| if (d.class === 'device' && !(offlineOnly && d.online)) { |
| a.push(d); |
| } |
| }); |
| return a; |
| } |
| |
| function findHosts() { |
| var hosts = []; |
| nodes.forEach(function (d) { |
| if (d.class === 'host') { |
| hosts.push(d); |
| } |
| }); |
| return hosts; |
| } |
| |
| function findAttachedHosts(devId) { |
| var hosts = []; |
| nodes.forEach(function (d) { |
| if (d.class === 'host' && d.cp.device === devId) { |
| hosts.push(d); |
| } |
| }); |
| return hosts; |
| } |
| |
| function findAttachedLinks(devId) { |
| var lnks = []; |
| links.forEach(function (d) { |
| if (d.source.id === devId || d.target.id === devId) { |
| lnks.push(d); |
| } |
| }); |
| return lnks; |
| } |
| |
| // returns one-way links or where the internal link types differ |
| function findBadLinks() { |
| var lnks = [], |
| src, tgt; |
| links.forEach(function (d) { |
| // NOTE: skip edge links, which are synthesized |
| if (d.type() !== 'hostLink') { |
| delete d.bad; |
| src = d.fromSource; |
| tgt = d.fromTarget; |
| if (src && !tgt) { |
| d.bad = 'missing link'; |
| } else if (src.type !== tgt.type) { |
| d.bad = 'type mismatch'; |
| } |
| if (d.bad) { |
| lnks.push(d); |
| } |
| } |
| }); |
| return lnks; |
| } |
| |
| // ========================== |
| // Module definition |
| |
| angular.module('ovTopo') |
| .factory('TopoModelService', |
| ['$log', 'FnService', 'RandomService', |
| |
| function (_$log_, _fs_, _rnd_) { |
| $log = _$log_; |
| fs = _fs_; |
| rnd = _rnd_; |
| |
| function initModel(_api_, _dim_) { |
| api = _api_; |
| dim = _dim_; |
| lu = api.network.lookup; |
| rlk = api.network.revLinkToKey; |
| nodes = api.network.nodes; |
| links = api.network.links; |
| linksByDevice = api.network.linksByDevice; |
| } |
| |
| function newDim(_dim_) { |
| dim = _dim_; |
| } |
| |
| function destroyModel() { } |
| |
| return { |
| initModel: initModel, |
| newDim: newDim, |
| destroyModel: destroyModel, |
| |
| positionNode: positionNode, |
| resetAllLocations: resetAllLocations, |
| createDeviceNode: createDeviceNode, |
| createHostNode: createHostNode, |
| createHostLink: createHostLink, |
| createLink: createLink, |
| coordFromLngLat: coordFromLngLat, |
| lngLatFromCoord: lngLatFromCoord, |
| findLink: findLink, |
| findLinkById: findLinkById, |
| findDevices: findDevices, |
| findHosts: findHosts, |
| findAttachedHosts: findAttachedHosts, |
| findAttachedLinks: findAttachedLinks, |
| findBadLinks: findBadLinks, |
| }; |
| }]); |
| }()); |