blob: 2ef94a611629a4d54d778c77b60e0006917662c6 [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
58 function positionNode(node, forUpdate) {
59 var meta = node.metaUi,
60 x = meta && meta.x,
61 y = meta && meta.y,
62 xy;
63
Simon Huntfd7106c2016-02-09 15:05:26 -080064 // if the device contains explicit LONG/LAT data, use that to position
65 if (setLongLat(node)) {
66 // indicate we want to update cached meta data...
67 return true;
68 }
69
70 // else if we have [x,y] cached in meta data, use that...
Simon Hunta5487ad2016-06-16 13:10:41 -070071 if (x !== undefined && y !== undefined) {
Simon Hunt3a6eec02015-02-09 21:16:43 -080072 node.fixed = true;
73 node.px = node.x = x;
74 node.py = node.y = y;
75 return;
76 }
77
Simon Hunt3a6eec02015-02-09 21:16:43 -080078 // if this is a node update (not a node add).. skip randomizer
79 if (forUpdate) {
80 return;
81 }
82
83 // Note: Placing incoming unpinned nodes at exactly the same point
84 // (center of the view) causes them to explode outwards when
85 // the force layout kicks in. So, we spread them out a bit
86 // initially, to provide a more serene layout convergence.
87 // Additionally, if the node is a host, we place it near
88 // the device it is connected to.
89
90 function rand() {
91 return {
92 x: rnd.randDim(dim[0]),
Steven Burrows1c2a9682017-07-14 16:52:46 +010093 y: rnd.randDim(dim[1]),
Simon Hunt3a6eec02015-02-09 21:16:43 -080094 };
95 }
96
97 function near(node) {
98 return {
99 x: node.x + nearDist + rnd.spread(nearDist),
Steven Burrows1c2a9682017-07-14 16:52:46 +0100100 y: node.y + nearDist + rnd.spread(nearDist),
Simon Hunt3a6eec02015-02-09 21:16:43 -0800101 };
102 }
103
104 function getDevice(cp) {
Simon Huntdc6adea2015-02-09 22:29:36 -0800105 var d = lu[cp.device];
Simon Hunt3a6eec02015-02-09 21:16:43 -0800106 return d || rand();
107 }
108
109 xy = (node.class === 'host') ? near(getDevice(node.cp)) : rand();
110 angular.extend(node, xy);
111 }
112
Simon Huntfd7106c2016-02-09 15:05:26 -0800113 function setLongLat(node) {
114 var loc = node.location,
115 coord;
116
Simon Huntffbad3b2017-05-16 15:37:51 -0700117 if (loc && loc.locType === 'geo') {
Simon Huntfd7106c2016-02-09 15:05:26 -0800118 coord = coordFromLngLat(loc);
119 node.fixed = true;
120 node.px = node.x = coord[0];
121 node.py = node.y = coord[1];
122 return true;
123 }
124 }
125
126 function resetAllLocations() {
127 nodes.forEach(function (d) {
128 setLongLat(d);
129 });
130 }
131
Simon Hunt3a6eec02015-02-09 21:16:43 -0800132 function mkSvgCls(dh, t, on) {
133 var ndh = 'node ' + dh,
134 ndht = t ? ndh + ' ' + t : ndh;
135 return on ? ndht + ' online' : ndht;
136 }
137
138 function createDeviceNode(device) {
139 var node = device;
140
141 // Augment as needed...
142 node.class = 'device';
143 node.svgClass = mkSvgCls('device', device.type, device.online);
144 positionNode(node);
145 return node;
146 }
147
148 function createHostNode(host) {
149 var node = host;
150
151 // Augment as needed...
152 node.class = 'host';
153 if (!node.type) {
154 node.type = 'endstation';
155 }
156 node.svgClass = mkSvgCls('host', node.type);
157 positionNode(node);
158 return node;
159 }
160
Simon Hunt12c79ed2017-09-12 11:58:44 -0700161 function createHostLink(hostId, devId, devPort) {
162 var linkKey = hostId + '/0-' + devId + '/' + devPort,
163 lnk = linkEndPoints(hostId, devId);
Simon Hunt3a6eec02015-02-09 21:16:43 -0800164
165 if (!lnk) {
166 return null;
167 }
168
169 // Synthesize link ...
170 angular.extend(lnk, {
Simon Hunt12c79ed2017-09-12 11:58:44 -0700171 key: linkKey,
Simon Hunt3a6eec02015-02-09 21:16:43 -0800172 class: 'link',
Simon Hunt5f361082015-02-25 11:36:38 -0800173 // NOTE: srcPort left undefined (host end of the link)
Simon Hunt12c79ed2017-09-12 11:58:44 -0700174 tgtPort: devPort,
Simon Hunt3a6eec02015-02-09 21:16:43 -0800175
176 type: function () { return 'hostLink'; },
Simon Hunt219f0772016-02-09 10:58:49 -0800177 expected: function () { return true; },
Simon Hunt3a6eec02015-02-09 21:16:43 -0800178 online: function () {
179 // hostlink target is edge switch
180 return lnk.target.online;
181 },
Steven Burrows1c2a9682017-07-14 16:52:46 +0100182 linkWidth: function () { return 1; },
Simon Hunt3a6eec02015-02-09 21:16:43 -0800183 });
184 return lnk;
185 }
186
187 function createLink(link) {
188 var lnk = linkEndPoints(link.src, link.dst);
189
190 if (!lnk) {
191 return null;
192 }
193
194 angular.extend(lnk, {
195 key: link.id,
196 class: 'link',
197 fromSource: link,
Simon Hunt5f361082015-02-25 11:36:38 -0800198 srcPort: link.srcPort,
199 tgtPort: link.dstPort,
Bri Prebilic Coleaeeb33e2015-07-09 15:15:54 -0700200 position: {
201 x1: 0,
202 y1: 0,
203 x2: 0,
Steven Burrows1c2a9682017-07-14 16:52:46 +0100204 y2: 0,
Bri Prebilic Coleaeeb33e2015-07-09 15:15:54 -0700205 },
Simon Hunt3a6eec02015-02-09 21:16:43 -0800206
207 // functions to aggregate dual link state
208 type: function () {
209 var s = lnk.fromSource,
210 t = lnk.fromTarget;
211 return (s && s.type) || (t && t.type) || defaultLinkType;
212 },
Ray Milkeyb7f0f642016-01-22 16:08:14 -0800213 expected: function () {
214 var s = lnk.fromSource,
215 t = lnk.fromTarget;
216 return (s && s.expected) && (t && t.expected);
217 },
Simon Hunt3a6eec02015-02-09 21:16:43 -0800218 online: function () {
219 var s = lnk.fromSource,
220 t = lnk.fromTarget,
221 both = lnk.source.online && lnk.target.online;
Simon Hunt15813b22016-01-29 08:29:08 -0800222 return both && (s && s.online) && (t && t.online);
Simon Hunt3a6eec02015-02-09 21:16:43 -0800223 },
224 linkWidth: function () {
225 var s = lnk.fromSource,
226 t = lnk.fromTarget,
227 ws = (s && s.linkWidth) || 0,
228 wt = (t && t.linkWidth) || 0;
Bri Prebilic Cole038aedd2015-07-13 15:25:16 -0700229 return lnk.position.multiLink ? 5 : Math.max(ws, wt);
Simon Hunt5c1a9382016-06-01 19:35:35 -0700230 },
Steven Burrows1c2a9682017-07-14 16:52:46 +0100231 extra: link.extra,
Simon Hunt3a6eec02015-02-09 21:16:43 -0800232 });
233 return lnk;
234 }
235
236
237 function linkEndPoints(srcId, dstId) {
Simon Huntdc6adea2015-02-09 22:29:36 -0800238 var srcNode = lu[srcId],
239 dstNode = lu[dstId],
Simon Hunt3a6eec02015-02-09 21:16:43 -0800240 sMiss = !srcNode ? missMsg('src', srcId) : '',
241 dMiss = !dstNode ? missMsg('dst', dstId) : '';
242
243 if (sMiss || dMiss) {
244 $log.error('Node(s) not on map for link:' + sMiss + dMiss);
Steven Burrows1c2a9682017-07-14 16:52:46 +0100245 // logicError('Node(s) not on map for link:\n' + sMiss + dMiss);
Simon Hunt3a6eec02015-02-09 21:16:43 -0800246 return null;
247 }
Steven Burrowsa3fca812016-10-14 15:11:04 -0500248
Simon Hunt3a6eec02015-02-09 21:16:43 -0800249 return {
250 source: srcNode,
Steven Burrows1c2a9682017-07-14 16:52:46 +0100251 target: dstNode,
Simon Hunt3a6eec02015-02-09 21:16:43 -0800252 };
253 }
254
255 function missMsg(what, id) {
256 return '\n[' + what + '] "' + id + '" missing';
257 }
258
Simon Huntdc6adea2015-02-09 22:29:36 -0800259
260 function makeNodeKey(d, what) {
261 var port = what + 'Port';
262 return d[what] + '/' + d[port];
263 }
264
265 function makeLinkKey(d, flipped) {
266 var one = flipped ? makeNodeKey(d, 'dst') : makeNodeKey(d, 'src'),
267 two = flipped ? makeNodeKey(d, 'src') : makeNodeKey(d, 'dst');
268 return one + '-' + two;
269 }
270
271 function findLinkById(id) {
272 // check to see if this is a reverse lookup, else default to given id
273 var key = rlk[id] || id;
274 return key && lu[key];
275 }
276
277 function findLink(linkData, op) {
278 var key = makeLinkKey(linkData),
279 keyrev = makeLinkKey(linkData, 1),
280 link = lu[key],
281 linkRev = lu[keyrev],
282 result = {},
283 ldata = link || linkRev,
284 rawLink;
285
286 if (op === 'add') {
287 if (link) {
288 // trying to add a link that we already know about
289 result.ldata = link;
290 result.badLogic = 'addLink: link already added';
291
292 } else if (linkRev) {
293 // we found the reverse of the link to be added
294 result.ldata = linkRev;
295 if (linkRev.fromTarget) {
296 result.badLogic = 'addLink: link already added';
297 }
298 }
299 } else if (op === 'update') {
300 if (!ldata) {
301 result.badLogic = 'updateLink: link not found';
302 } else {
303 rawLink = link ? ldata.fromSource : ldata.fromTarget;
304 result.updateWith = function (data) {
305 angular.extend(rawLink, data);
306 api.restyleLinkElement(ldata);
Steven Burrows1c2a9682017-07-14 16:52:46 +0100307 };
Simon Huntdc6adea2015-02-09 22:29:36 -0800308 }
309 } else if (op === 'remove') {
310 if (!ldata) {
311 result.badLogic = 'removeLink: link not found';
312 } else {
313 rawLink = link ? ldata.fromSource : ldata.fromTarget;
314
315 if (!rawLink) {
316 result.badLogic = 'removeLink: link not found';
317
318 } else {
319 result.removeRawLink = function () {
Bri Prebilic Coleaeeb33e2015-07-09 15:15:54 -0700320 // remove link out of aggregate linksByDevice list
321 var linksForDevPair = linksByDevice[ldata.devicePair],
322 rmvIdx = fs.find(ldata.key, linksForDevPair, 'key');
Bri Prebilic Cole80401762015-07-16 11:36:18 -0700323 if (rmvIdx >= 0) {
324 linksForDevPair.splice(rmvIdx, 1);
325 }
326 ldata.position.multilink = linksForDevPair.length >= 5;
Bri Prebilic Coleaeeb33e2015-07-09 15:15:54 -0700327
Simon Huntdc6adea2015-02-09 22:29:36 -0800328 if (link) {
329 // remove fromSource
330 ldata.fromSource = null;
331 if (ldata.fromTarget) {
332 // promote target into source position
333 ldata.fromSource = ldata.fromTarget;
334 ldata.fromTarget = null;
335 ldata.key = keyrev;
336 delete lu[key];
337 lu[keyrev] = ldata;
338 delete rlk[keyrev];
339 }
340 } else {
341 // remove fromTarget
342 ldata.fromTarget = null;
343 delete rlk[keyrev];
344 }
345 if (ldata.fromSource) {
346 api.restyleLinkElement(ldata);
347 } else {
348 api.removeLinkElement(ldata);
349 }
Steven Burrows1c2a9682017-07-14 16:52:46 +0100350 };
Simon Huntdc6adea2015-02-09 22:29:36 -0800351 }
352 }
353 }
354 return result;
355 }
356
357 function findDevices(offlineOnly) {
358 var a = [];
359 nodes.forEach(function (d) {
360 if (d.class === 'device' && !(offlineOnly && d.online)) {
361 a.push(d);
362 }
363 });
364 return a;
365 }
366
Simon Hunt10618f62017-06-15 19:30:52 -0700367 function findHosts() {
368 var hosts = [];
369 nodes.forEach(function (d) {
370 if (d.class === 'host') {
371 hosts.push(d);
372 }
373 });
374 return hosts;
375 }
376
Simon Huntdc6adea2015-02-09 22:29:36 -0800377 function findAttachedHosts(devId) {
378 var hosts = [];
379 nodes.forEach(function (d) {
380 if (d.class === 'host' && d.cp.device === devId) {
381 hosts.push(d);
382 }
383 });
384 return hosts;
385 }
386
387 function findAttachedLinks(devId) {
Simon Hunt86b7c882015-04-02 23:06:08 -0700388 var lnks = [];
Simon Huntdc6adea2015-02-09 22:29:36 -0800389 links.forEach(function (d) {
390 if (d.source.id === devId || d.target.id === devId) {
Simon Hunt86b7c882015-04-02 23:06:08 -0700391 lnks.push(d);
Simon Huntdc6adea2015-02-09 22:29:36 -0800392 }
393 });
Simon Hunt86b7c882015-04-02 23:06:08 -0700394 return lnks;
Simon Huntdc6adea2015-02-09 22:29:36 -0800395 }
396
Simon Hunt86b7c882015-04-02 23:06:08 -0700397 // returns one-way links or where the internal link types differ
398 function findBadLinks() {
399 var lnks = [],
400 src, tgt;
401 links.forEach(function (d) {
402 // NOTE: skip edge links, which are synthesized
403 if (d.type() !== 'hostLink') {
404 delete d.bad;
405 src = d.fromSource;
406 tgt = d.fromTarget;
407 if (src && !tgt) {
408 d.bad = 'missing link';
409 } else if (src.type !== tgt.type) {
410 d.bad = 'type mismatch';
411 }
412 if (d.bad) {
413 lnks.push(d);
414 }
415 }
416 });
417 return lnks;
418 }
Simon Huntdc6adea2015-02-09 22:29:36 -0800419
Simon Hunt3a6eec02015-02-09 21:16:43 -0800420 // ==========================
421 // Module definition
422
423 angular.module('ovTopo')
Simon Hunt75ec9692015-02-11 16:40:36 -0800424 .factory('TopoModelService',
Simon Hunt3a6eec02015-02-09 21:16:43 -0800425 ['$log', 'FnService', 'RandomService',
426
427 function (_$log_, _fs_, _rnd_) {
428 $log = _$log_;
429 fs = _fs_;
430 rnd = _rnd_;
431
432 function initModel(_api_, _dim_) {
433 api = _api_;
434 dim = _dim_;
Simon Huntdc6adea2015-02-09 22:29:36 -0800435 lu = api.network.lookup;
436 rlk = api.network.revLinkToKey;
437 nodes = api.network.nodes;
438 links = api.network.links;
Bri Prebilic Coleaeeb33e2015-07-09 15:15:54 -0700439 linksByDevice = api.network.linksByDevice;
Simon Hunt3a6eec02015-02-09 21:16:43 -0800440 }
441
442 function newDim(_dim_) {
443 dim = _dim_;
444 }
445
Simon Huntf542d842015-02-11 16:20:33 -0800446 function destroyModel() { }
447
Simon Hunt3a6eec02015-02-09 21:16:43 -0800448 return {
449 initModel: initModel,
450 newDim: newDim,
Simon Huntf542d842015-02-11 16:20:33 -0800451 destroyModel: destroyModel,
Simon Hunt3a6eec02015-02-09 21:16:43 -0800452
453 positionNode: positionNode,
Simon Huntfd7106c2016-02-09 15:05:26 -0800454 resetAllLocations: resetAllLocations,
Simon Hunt3a6eec02015-02-09 21:16:43 -0800455 createDeviceNode: createDeviceNode,
456 createHostNode: createHostNode,
457 createHostLink: createHostLink,
458 createLink: createLink,
459 coordFromLngLat: coordFromLngLat,
460 lngLatFromCoord: lngLatFromCoord,
Simon Huntdc6adea2015-02-09 22:29:36 -0800461 findLink: findLink,
462 findLinkById: findLinkById,
463 findDevices: findDevices,
Simon Hunt10618f62017-06-15 19:30:52 -0700464 findHosts: findHosts,
Simon Huntdc6adea2015-02-09 22:29:36 -0800465 findAttachedHosts: findAttachedHosts,
Simon Hunt86b7c882015-04-02 23:06:08 -0700466 findAttachedLinks: findAttachedLinks,
Steven Burrows1c2a9682017-07-14 16:52:46 +0100467 findBadLinks: findBadLinks,
468 };
Simon Hunt3a6eec02015-02-09 21:16:43 -0800469 }]);
470}());