blob: 6454f5a4f33cd618950ef92aaffaf1f8f8b93cfc [file] [log] [blame]
Simon Hunt3a6eec02015-02-09 21:16:43 -08001/*
Brian O'Connora09fe5b2017-08-03 21:12:30 -07002 * Copyright 2015-present Open Networking Foundation
Simon Hunt3a6eec02015-02-09 21:16:43 -08003 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17/*
18 ONOS GUI -- Topology Model Module.
19 Auxiliary functions for the model of the topology; that is, our internal
20 representations of devices, hosts, links, etc.
21 */
22
23(function () {
24 'use strict';
25
26 // injected refs
Simon Hunt08f841d02015-02-10 14:39:20 -080027 var $log, fs, rnd;
28
29 // api to topoForce
30 var api;
31 /*
32 projection()
33 network {...}
34 restyleLinkElement( ldata )
35 removeLinkElement( ldata )
36 */
Simon Hunt3a6eec02015-02-09 21:16:43 -080037
Simon Huntdc6adea2015-02-09 22:29:36 -080038 // shorthand
Bri Prebilic Coleaeeb33e2015-07-09 15:15:54 -070039 var lu, rlk, nodes, links, linksByDevice;
Simon Huntdc6adea2015-02-09 22:29:36 -080040
Steven Burrows1c2a9682017-07-14 16:52:46 +010041 var dim; // dimensions of layout [w,h]
Simon Hunt3a6eec02015-02-09 21:16:43 -080042
43 // configuration 'constants'
44 var defaultLinkType = 'direct',
45 nearDist = 15;
46
47
48 function coordFromLngLat(loc) {
49 var p = api.projection();
Simon Huntffbad3b2017-05-16 15:37:51 -070050 return p ? p([loc.longOrX, loc.latOrY]) : [0, 0];
Simon Hunt3a6eec02015-02-09 21:16:43 -080051 }
52
53 function lngLatFromCoord(coord) {
54 var p = api.projection();
55 return p ? p.invert(coord) : [0, 0];
56 }
57
Thomas Vachuskac67a9912018-03-06 14:37:45 -080058 function coordFromXY(loc) {
59 var bgWidth = 1000,
60 bgHeight = 1000;
61
62 var scale = 1000 / bgWidth,
63 yOffset = (1000 - (bgHeight * scale)) / 2;
64
65 // 1000 is a hardcoded HTML value of the SVG element (topo2.html)
66 var x = scale * loc.longOrX,
67 y = (scale * loc.latOrY) + yOffset;
68
69 return [x, y];
70 }
71
Simon Hunt3a6eec02015-02-09 21:16:43 -080072 function positionNode(node, forUpdate) {
73 var meta = node.metaUi,
74 x = meta && meta.x,
75 y = meta && meta.y,
76 xy;
77
Simon Huntfd7106c2016-02-09 15:05:26 -080078 // if the device contains explicit LONG/LAT data, use that to position
79 if (setLongLat(node)) {
80 // indicate we want to update cached meta data...
Thomas Vachuskac616e172018-04-17 16:57:12 -070081 return false;
Simon Huntfd7106c2016-02-09 15:05:26 -080082 }
83
84 // else if we have [x,y] cached in meta data, use that...
Thomas Vachuskac616e172018-04-17 16:57:12 -070085 if (x != undefined && y != undefined) {
Simon Hunt3a6eec02015-02-09 21:16:43 -080086 node.fixed = true;
87 node.px = node.x = x;
88 node.py = node.y = y;
Thomas Vachuskac616e172018-04-17 16:57:12 -070089 return true;
Simon Hunt3a6eec02015-02-09 21:16:43 -080090 }
91
Simon Hunt3a6eec02015-02-09 21:16:43 -080092 // if this is a node update (not a node add).. skip randomizer
Thomas Vachuskac616e172018-04-17 16:57:12 -070093 if (forUpdate && node.x != undefined && node.y != undefined) {
94 return false;
Simon Hunt3a6eec02015-02-09 21:16:43 -080095 }
96
97 // Note: Placing incoming unpinned nodes at exactly the same point
98 // (center of the view) causes them to explode outwards when
99 // the force layout kicks in. So, we spread them out a bit
100 // initially, to provide a more serene layout convergence.
101 // Additionally, if the node is a host, we place it near
102 // the device it is connected to.
103
104 function rand() {
105 return {
106 x: rnd.randDim(dim[0]),
Steven Burrows1c2a9682017-07-14 16:52:46 +0100107 y: rnd.randDim(dim[1]),
Simon Hunt3a6eec02015-02-09 21:16:43 -0800108 };
109 }
110
111 function near(node) {
112 return {
113 x: node.x + nearDist + rnd.spread(nearDist),
Steven Burrows1c2a9682017-07-14 16:52:46 +0100114 y: node.y + nearDist + rnd.spread(nearDist),
Simon Hunt3a6eec02015-02-09 21:16:43 -0800115 };
116 }
117
118 function getDevice(cp) {
Simon Huntdc6adea2015-02-09 22:29:36 -0800119 var d = lu[cp.device];
Simon Hunt3a6eec02015-02-09 21:16:43 -0800120 return d || rand();
121 }
122
Thomas Vachuskac616e172018-04-17 16:57:12 -0700123 node.fixed = false;
Simon Hunt3a6eec02015-02-09 21:16:43 -0800124 xy = (node.class === 'host') ? near(getDevice(node.cp)) : rand();
125 angular.extend(node, xy);
Thomas Vachuskac616e172018-04-17 16:57:12 -0700126 return false;
Simon Hunt3a6eec02015-02-09 21:16:43 -0800127 }
128
Simon Huntfd7106c2016-02-09 15:05:26 -0800129 function setLongLat(node) {
130 var loc = node.location,
131 coord;
132
Thomas Vachuskac616e172018-04-17 16:57:12 -0700133 if (!loc || loc.locType === 'none') {
134 node.fixed = false;
135
136 } else if (loc) {
Thomas Vachuskac67a9912018-03-06 14:37:45 -0800137 coord = loc.locType === 'geo' ? coordFromLngLat(loc) : coordFromXY(loc);
Simon Huntfd7106c2016-02-09 15:05:26 -0800138 node.fixed = true;
139 node.px = node.x = coord[0];
140 node.py = node.y = coord[1];
141 return true;
142 }
Thomas Vachuskac616e172018-04-17 16:57:12 -0700143 return false;
Simon Huntfd7106c2016-02-09 15:05:26 -0800144 }
145
146 function resetAllLocations() {
147 nodes.forEach(function (d) {
148 setLongLat(d);
149 });
150 }
151
Simon Hunt3a6eec02015-02-09 21:16:43 -0800152 function mkSvgCls(dh, t, on) {
153 var ndh = 'node ' + dh,
154 ndht = t ? ndh + ' ' + t : ndh;
155 return on ? ndht + ' online' : ndht;
156 }
157
158 function createDeviceNode(device) {
159 var node = device;
160
161 // Augment as needed...
162 node.class = 'device';
163 node.svgClass = mkSvgCls('device', device.type, device.online);
164 positionNode(node);
165 return node;
166 }
167
168 function createHostNode(host) {
169 var node = host;
170
171 // Augment as needed...
172 node.class = 'host';
173 if (!node.type) {
174 node.type = 'endstation';
175 }
176 node.svgClass = mkSvgCls('host', node.type);
177 positionNode(node);
178 return node;
179 }
180
Thomas Vachuskaf06f5d62021-03-10 14:24:47 -0800181 function createHostLink(hostId, devId, devPort, connectionType) {
Simon Hunt12c79ed2017-09-12 11:58:44 -0700182 var linkKey = hostId + '/0-' + devId + '/' + devPort,
183 lnk = linkEndPoints(hostId, devId);
Simon Hunt3a6eec02015-02-09 21:16:43 -0800184
185 if (!lnk) {
186 return null;
187 }
188
189 // Synthesize link ...
190 angular.extend(lnk, {
Simon Hunt12c79ed2017-09-12 11:58:44 -0700191 key: linkKey,
Simon Hunt3a6eec02015-02-09 21:16:43 -0800192 class: 'link',
Simon Hunt5f361082015-02-25 11:36:38 -0800193 // NOTE: srcPort left undefined (host end of the link)
Simon Hunt12c79ed2017-09-12 11:58:44 -0700194 tgtPort: devPort,
Simon Hunt3a6eec02015-02-09 21:16:43 -0800195
196 type: function () { return 'hostLink'; },
Thomas Vachuskaf06f5d62021-03-10 14:24:47 -0800197 connectionType: function () { return connectionType; },
Simon Hunt219f0772016-02-09 10:58:49 -0800198 expected: function () { return true; },
Simon Hunt3a6eec02015-02-09 21:16:43 -0800199 online: function () {
200 // hostlink target is edge switch
201 return lnk.target.online;
202 },
Steven Burrows1c2a9682017-07-14 16:52:46 +0100203 linkWidth: function () { return 1; },
Simon Hunt3a6eec02015-02-09 21:16:43 -0800204 });
205 return lnk;
206 }
207
208 function createLink(link) {
209 var lnk = linkEndPoints(link.src, link.dst);
210
211 if (!lnk) {
212 return null;
213 }
214
215 angular.extend(lnk, {
216 key: link.id,
217 class: 'link',
218 fromSource: link,
Simon Hunt5f361082015-02-25 11:36:38 -0800219 srcPort: link.srcPort,
220 tgtPort: link.dstPort,
Bri Prebilic Coleaeeb33e2015-07-09 15:15:54 -0700221 position: {
222 x1: 0,
223 y1: 0,
224 x2: 0,
Steven Burrows1c2a9682017-07-14 16:52:46 +0100225 y2: 0,
Bri Prebilic Coleaeeb33e2015-07-09 15:15:54 -0700226 },
Simon Hunt3a6eec02015-02-09 21:16:43 -0800227
228 // functions to aggregate dual link state
229 type: function () {
230 var s = lnk.fromSource,
231 t = lnk.fromTarget;
232 return (s && s.type) || (t && t.type) || defaultLinkType;
233 },
Ray Milkeyb7f0f642016-01-22 16:08:14 -0800234 expected: function () {
235 var s = lnk.fromSource,
236 t = lnk.fromTarget;
237 return (s && s.expected) && (t && t.expected);
238 },
Simon Hunt3a6eec02015-02-09 21:16:43 -0800239 online: function () {
240 var s = lnk.fromSource,
241 t = lnk.fromTarget,
242 both = lnk.source.online && lnk.target.online;
Simon Hunt15813b22016-01-29 08:29:08 -0800243 return both && (s && s.online) && (t && t.online);
Simon Hunt3a6eec02015-02-09 21:16:43 -0800244 },
245 linkWidth: function () {
246 var s = lnk.fromSource,
247 t = lnk.fromTarget,
248 ws = (s && s.linkWidth) || 0,
249 wt = (t && t.linkWidth) || 0;
Bri Prebilic Cole038aedd2015-07-13 15:25:16 -0700250 return lnk.position.multiLink ? 5 : Math.max(ws, wt);
Simon Hunt5c1a9382016-06-01 19:35:35 -0700251 },
Steven Burrows1c2a9682017-07-14 16:52:46 +0100252 extra: link.extra,
Simon Hunt3a6eec02015-02-09 21:16:43 -0800253 });
254 return lnk;
255 }
256
257
258 function linkEndPoints(srcId, dstId) {
Simon Huntdc6adea2015-02-09 22:29:36 -0800259 var srcNode = lu[srcId],
260 dstNode = lu[dstId],
Simon Hunt3a6eec02015-02-09 21:16:43 -0800261 sMiss = !srcNode ? missMsg('src', srcId) : '',
262 dMiss = !dstNode ? missMsg('dst', dstId) : '';
263
264 if (sMiss || dMiss) {
265 $log.error('Node(s) not on map for link:' + sMiss + dMiss);
Steven Burrows1c2a9682017-07-14 16:52:46 +0100266 // logicError('Node(s) not on map for link:\n' + sMiss + dMiss);
Simon Hunt3a6eec02015-02-09 21:16:43 -0800267 return null;
268 }
Steven Burrowsa3fca812016-10-14 15:11:04 -0500269
Simon Hunt3a6eec02015-02-09 21:16:43 -0800270 return {
271 source: srcNode,
Steven Burrows1c2a9682017-07-14 16:52:46 +0100272 target: dstNode,
Simon Hunt3a6eec02015-02-09 21:16:43 -0800273 };
274 }
275
276 function missMsg(what, id) {
277 return '\n[' + what + '] "' + id + '" missing';
278 }
279
Simon Huntdc6adea2015-02-09 22:29:36 -0800280
281 function makeNodeKey(d, what) {
282 var port = what + 'Port';
283 return d[what] + '/' + d[port];
284 }
285
286 function makeLinkKey(d, flipped) {
287 var one = flipped ? makeNodeKey(d, 'dst') : makeNodeKey(d, 'src'),
288 two = flipped ? makeNodeKey(d, 'src') : makeNodeKey(d, 'dst');
289 return one + '-' + two;
290 }
291
292 function findLinkById(id) {
293 // check to see if this is a reverse lookup, else default to given id
294 var key = rlk[id] || id;
295 return key && lu[key];
296 }
297
298 function findLink(linkData, op) {
299 var key = makeLinkKey(linkData),
300 keyrev = makeLinkKey(linkData, 1),
301 link = lu[key],
302 linkRev = lu[keyrev],
303 result = {},
304 ldata = link || linkRev,
305 rawLink;
306
307 if (op === 'add') {
308 if (link) {
309 // trying to add a link that we already know about
310 result.ldata = link;
311 result.badLogic = 'addLink: link already added';
312
313 } else if (linkRev) {
314 // we found the reverse of the link to be added
315 result.ldata = linkRev;
316 if (linkRev.fromTarget) {
317 result.badLogic = 'addLink: link already added';
318 }
319 }
320 } else if (op === 'update') {
321 if (!ldata) {
322 result.badLogic = 'updateLink: link not found';
323 } else {
324 rawLink = link ? ldata.fromSource : ldata.fromTarget;
325 result.updateWith = function (data) {
326 angular.extend(rawLink, data);
327 api.restyleLinkElement(ldata);
Steven Burrows1c2a9682017-07-14 16:52:46 +0100328 };
Simon Huntdc6adea2015-02-09 22:29:36 -0800329 }
330 } else if (op === 'remove') {
331 if (!ldata) {
332 result.badLogic = 'removeLink: link not found';
333 } else {
334 rawLink = link ? ldata.fromSource : ldata.fromTarget;
335
336 if (!rawLink) {
337 result.badLogic = 'removeLink: link not found';
338
339 } else {
340 result.removeRawLink = function () {
Bri Prebilic Coleaeeb33e2015-07-09 15:15:54 -0700341 // remove link out of aggregate linksByDevice list
342 var linksForDevPair = linksByDevice[ldata.devicePair],
343 rmvIdx = fs.find(ldata.key, linksForDevPair, 'key');
Bri Prebilic Cole80401762015-07-16 11:36:18 -0700344 if (rmvIdx >= 0) {
345 linksForDevPair.splice(rmvIdx, 1);
346 }
347 ldata.position.multilink = linksForDevPair.length >= 5;
Bri Prebilic Coleaeeb33e2015-07-09 15:15:54 -0700348
Simon Huntdc6adea2015-02-09 22:29:36 -0800349 if (link) {
350 // remove fromSource
351 ldata.fromSource = null;
352 if (ldata.fromTarget) {
353 // promote target into source position
354 ldata.fromSource = ldata.fromTarget;
355 ldata.fromTarget = null;
356 ldata.key = keyrev;
357 delete lu[key];
358 lu[keyrev] = ldata;
359 delete rlk[keyrev];
360 }
361 } else {
362 // remove fromTarget
363 ldata.fromTarget = null;
364 delete rlk[keyrev];
365 }
366 if (ldata.fromSource) {
367 api.restyleLinkElement(ldata);
368 } else {
369 api.removeLinkElement(ldata);
370 }
Steven Burrows1c2a9682017-07-14 16:52:46 +0100371 };
Simon Huntdc6adea2015-02-09 22:29:36 -0800372 }
373 }
374 }
375 return result;
376 }
377
378 function findDevices(offlineOnly) {
379 var a = [];
380 nodes.forEach(function (d) {
381 if (d.class === 'device' && !(offlineOnly && d.online)) {
382 a.push(d);
383 }
384 });
385 return a;
386 }
387
Simon Hunt10618f62017-06-15 19:30:52 -0700388 function findHosts() {
389 var hosts = [];
390 nodes.forEach(function (d) {
391 if (d.class === 'host') {
392 hosts.push(d);
393 }
394 });
395 return hosts;
396 }
397
Simon Huntdc6adea2015-02-09 22:29:36 -0800398 function findAttachedHosts(devId) {
399 var hosts = [];
400 nodes.forEach(function (d) {
401 if (d.class === 'host' && d.cp.device === devId) {
402 hosts.push(d);
403 }
404 });
405 return hosts;
406 }
407
408 function findAttachedLinks(devId) {
Simon Hunt86b7c882015-04-02 23:06:08 -0700409 var lnks = [];
Simon Huntdc6adea2015-02-09 22:29:36 -0800410 links.forEach(function (d) {
411 if (d.source.id === devId || d.target.id === devId) {
Simon Hunt86b7c882015-04-02 23:06:08 -0700412 lnks.push(d);
Simon Huntdc6adea2015-02-09 22:29:36 -0800413 }
414 });
Simon Hunt86b7c882015-04-02 23:06:08 -0700415 return lnks;
Simon Huntdc6adea2015-02-09 22:29:36 -0800416 }
417
Simon Hunt86b7c882015-04-02 23:06:08 -0700418 // returns one-way links or where the internal link types differ
419 function findBadLinks() {
420 var lnks = [],
421 src, tgt;
422 links.forEach(function (d) {
423 // NOTE: skip edge links, which are synthesized
424 if (d.type() !== 'hostLink') {
425 delete d.bad;
426 src = d.fromSource;
427 tgt = d.fromTarget;
428 if (src && !tgt) {
429 d.bad = 'missing link';
430 } else if (src.type !== tgt.type) {
431 d.bad = 'type mismatch';
432 }
433 if (d.bad) {
434 lnks.push(d);
435 }
436 }
437 });
438 return lnks;
439 }
Simon Huntdc6adea2015-02-09 22:29:36 -0800440
Simon Hunt3a6eec02015-02-09 21:16:43 -0800441 // ==========================
442 // Module definition
443
444 angular.module('ovTopo')
Simon Hunt75ec9692015-02-11 16:40:36 -0800445 .factory('TopoModelService',
Simon Hunt3a6eec02015-02-09 21:16:43 -0800446 ['$log', 'FnService', 'RandomService',
447
448 function (_$log_, _fs_, _rnd_) {
449 $log = _$log_;
450 fs = _fs_;
451 rnd = _rnd_;
452
453 function initModel(_api_, _dim_) {
454 api = _api_;
455 dim = _dim_;
Simon Huntdc6adea2015-02-09 22:29:36 -0800456 lu = api.network.lookup;
457 rlk = api.network.revLinkToKey;
458 nodes = api.network.nodes;
459 links = api.network.links;
Bri Prebilic Coleaeeb33e2015-07-09 15:15:54 -0700460 linksByDevice = api.network.linksByDevice;
Simon Hunt3a6eec02015-02-09 21:16:43 -0800461 }
462
463 function newDim(_dim_) {
464 dim = _dim_;
465 }
466
Simon Huntf542d842015-02-11 16:20:33 -0800467 function destroyModel() { }
468
Simon Hunt3a6eec02015-02-09 21:16:43 -0800469 return {
470 initModel: initModel,
471 newDim: newDim,
Simon Huntf542d842015-02-11 16:20:33 -0800472 destroyModel: destroyModel,
Simon Hunt3a6eec02015-02-09 21:16:43 -0800473
474 positionNode: positionNode,
Simon Huntfd7106c2016-02-09 15:05:26 -0800475 resetAllLocations: resetAllLocations,
Simon Hunt3a6eec02015-02-09 21:16:43 -0800476 createDeviceNode: createDeviceNode,
477 createHostNode: createHostNode,
478 createHostLink: createHostLink,
479 createLink: createLink,
480 coordFromLngLat: coordFromLngLat,
481 lngLatFromCoord: lngLatFromCoord,
Simon Huntdc6adea2015-02-09 22:29:36 -0800482 findLink: findLink,
483 findLinkById: findLinkById,
484 findDevices: findDevices,
Simon Hunt10618f62017-06-15 19:30:52 -0700485 findHosts: findHosts,
Simon Huntdc6adea2015-02-09 22:29:36 -0800486 findAttachedHosts: findAttachedHosts,
Simon Hunt86b7c882015-04-02 23:06:08 -0700487 findAttachedLinks: findAttachedLinks,
Steven Burrows1c2a9682017-07-14 16:52:46 +0100488 findBadLinks: findBadLinks,
489 };
Simon Hunt3a6eec02015-02-09 21:16:43 -0800490 }]);
491}());