blob: 887635dc5f94504096ba5ba1b15b0df8b4d05467 [file] [log] [blame]
Simon Hunt3a6eec02015-02-09 21:16:43 -08001/*
Brian O'Connor5ab426f2016-04-09 01:19:45 -07002 * Copyright 2015-present Open Networking Laboratory
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
Simon Hunt08f841d02015-02-10 14:39:20 -080041 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();
Bri Prebilic Cole6b95a3f2015-06-04 09:15:00 -070050 // suspected cause of ONOS-2109
Simon Hunt3a6eec02015-02-09 21:16:43 -080051 return p ? p([loc.lng, loc.lat]) : [0, 0];
52 }
53
54 function lngLatFromCoord(coord) {
55 var p = api.projection();
56 return p ? p.invert(coord) : [0, 0];
57 }
58
59 function positionNode(node, forUpdate) {
60 var meta = node.metaUi,
61 x = meta && meta.x,
62 y = meta && meta.y,
63 xy;
64
Simon Huntfd7106c2016-02-09 15:05:26 -080065 // if the device contains explicit LONG/LAT data, use that to position
66 if (setLongLat(node)) {
67 // indicate we want to update cached meta data...
68 return true;
69 }
70
71 // else if we have [x,y] cached in meta data, use that...
Simon Hunta5487ad2016-06-16 13:10:41 -070072 if (x !== undefined && y !== undefined) {
Simon Hunt3a6eec02015-02-09 21:16:43 -080073 node.fixed = true;
74 node.px = node.x = x;
75 node.py = node.y = y;
76 return;
77 }
78
Simon Hunt3a6eec02015-02-09 21:16:43 -080079 // if this is a node update (not a node add).. skip randomizer
80 if (forUpdate) {
81 return;
82 }
83
84 // Note: Placing incoming unpinned nodes at exactly the same point
85 // (center of the view) causes them to explode outwards when
86 // the force layout kicks in. So, we spread them out a bit
87 // initially, to provide a more serene layout convergence.
88 // Additionally, if the node is a host, we place it near
89 // the device it is connected to.
90
91 function rand() {
92 return {
93 x: rnd.randDim(dim[0]),
94 y: rnd.randDim(dim[1])
95 };
96 }
97
98 function near(node) {
99 return {
100 x: node.x + nearDist + rnd.spread(nearDist),
101 y: node.y + nearDist + rnd.spread(nearDist)
102 };
103 }
104
105 function getDevice(cp) {
Simon Huntdc6adea2015-02-09 22:29:36 -0800106 var d = lu[cp.device];
Simon Hunt3a6eec02015-02-09 21:16:43 -0800107 return d || rand();
108 }
109
110 xy = (node.class === 'host') ? near(getDevice(node.cp)) : rand();
111 angular.extend(node, xy);
112 }
113
Simon Huntfd7106c2016-02-09 15:05:26 -0800114 function setLongLat(node) {
115 var loc = node.location,
116 coord;
117
Simon Huntbc30e682017-02-15 18:39:23 -0800118 if (loc && loc.type === 'geo') {
Simon Huntfd7106c2016-02-09 15:05:26 -0800119 coord = coordFromLngLat(loc);
120 node.fixed = true;
121 node.px = node.x = coord[0];
122 node.py = node.y = coord[1];
123 return true;
124 }
125 }
126
127 function resetAllLocations() {
128 nodes.forEach(function (d) {
129 setLongLat(d);
130 });
131 }
132
Simon Hunt3a6eec02015-02-09 21:16:43 -0800133 function mkSvgCls(dh, t, on) {
134 var ndh = 'node ' + dh,
135 ndht = t ? ndh + ' ' + t : ndh;
136 return on ? ndht + ' online' : ndht;
137 }
138
139 function createDeviceNode(device) {
140 var node = device;
141
142 // Augment as needed...
143 node.class = 'device';
144 node.svgClass = mkSvgCls('device', device.type, device.online);
145 positionNode(node);
146 return node;
147 }
148
149 function createHostNode(host) {
150 var node = host;
151
152 // Augment as needed...
153 node.class = 'host';
154 if (!node.type) {
155 node.type = 'endstation';
156 }
157 node.svgClass = mkSvgCls('host', node.type);
158 positionNode(node);
159 return node;
160 }
161
162 function createHostLink(host) {
163 var src = host.id,
164 dst = host.cp.device,
165 id = host.ingress,
166 lnk = linkEndPoints(src, dst);
167
168 if (!lnk) {
169 return null;
170 }
171
172 // Synthesize link ...
173 angular.extend(lnk, {
174 key: id,
175 class: 'link',
Simon Hunt5f361082015-02-25 11:36:38 -0800176 // NOTE: srcPort left undefined (host end of the link)
177 tgtPort: host.cp.port,
Simon Hunt3a6eec02015-02-09 21:16:43 -0800178
179 type: function () { return 'hostLink'; },
Simon Hunt219f0772016-02-09 10:58:49 -0800180 expected: function () { return true; },
Simon Hunt3a6eec02015-02-09 21:16:43 -0800181 online: function () {
182 // hostlink target is edge switch
183 return lnk.target.online;
184 },
185 linkWidth: function () { return 1; }
186 });
187 return lnk;
188 }
189
190 function createLink(link) {
191 var lnk = linkEndPoints(link.src, link.dst);
192
193 if (!lnk) {
194 return null;
195 }
196
197 angular.extend(lnk, {
198 key: link.id,
199 class: 'link',
200 fromSource: link,
Simon Hunt5f361082015-02-25 11:36:38 -0800201 srcPort: link.srcPort,
202 tgtPort: link.dstPort,
Bri Prebilic Coleaeeb33e2015-07-09 15:15:54 -0700203 position: {
204 x1: 0,
205 y1: 0,
206 x2: 0,
207 y2: 0
208 },
Simon Hunt3a6eec02015-02-09 21:16:43 -0800209
210 // functions to aggregate dual link state
211 type: function () {
212 var s = lnk.fromSource,
213 t = lnk.fromTarget;
214 return (s && s.type) || (t && t.type) || defaultLinkType;
215 },
Ray Milkeyb7f0f642016-01-22 16:08:14 -0800216 expected: function () {
217 var s = lnk.fromSource,
218 t = lnk.fromTarget;
219 return (s && s.expected) && (t && t.expected);
220 },
Simon Hunt3a6eec02015-02-09 21:16:43 -0800221 online: function () {
222 var s = lnk.fromSource,
223 t = lnk.fromTarget,
224 both = lnk.source.online && lnk.target.online;
Simon Hunt15813b22016-01-29 08:29:08 -0800225 return both && (s && s.online) && (t && t.online);
Simon Hunt3a6eec02015-02-09 21:16:43 -0800226 },
227 linkWidth: function () {
228 var s = lnk.fromSource,
229 t = lnk.fromTarget,
230 ws = (s && s.linkWidth) || 0,
231 wt = (t && t.linkWidth) || 0;
Bri Prebilic Cole038aedd2015-07-13 15:25:16 -0700232 return lnk.position.multiLink ? 5 : Math.max(ws, wt);
Simon Hunt5c1a9382016-06-01 19:35:35 -0700233 },
234 extra: link.extra
Simon Hunt3a6eec02015-02-09 21:16:43 -0800235 });
236 return lnk;
237 }
238
239
240 function linkEndPoints(srcId, dstId) {
Simon Huntdc6adea2015-02-09 22:29:36 -0800241 var srcNode = lu[srcId],
242 dstNode = lu[dstId],
Simon Hunt3a6eec02015-02-09 21:16:43 -0800243 sMiss = !srcNode ? missMsg('src', srcId) : '',
244 dMiss = !dstNode ? missMsg('dst', dstId) : '';
245
246 if (sMiss || dMiss) {
247 $log.error('Node(s) not on map for link:' + sMiss + dMiss);
248 //logicError('Node(s) not on map for link:\n' + sMiss + dMiss);
249 return null;
250 }
Steven Burrowsa3fca812016-10-14 15:11:04 -0500251
Simon Hunt3a6eec02015-02-09 21:16:43 -0800252 return {
253 source: srcNode,
Simon Hunt5f361082015-02-25 11:36:38 -0800254 target: dstNode
Simon Hunt3a6eec02015-02-09 21:16:43 -0800255 };
256 }
257
258 function missMsg(what, id) {
259 return '\n[' + what + '] "' + id + '" missing';
260 }
261
Simon Huntdc6adea2015-02-09 22:29:36 -0800262
263 function makeNodeKey(d, what) {
264 var port = what + 'Port';
265 return d[what] + '/' + d[port];
266 }
267
268 function makeLinkKey(d, flipped) {
269 var one = flipped ? makeNodeKey(d, 'dst') : makeNodeKey(d, 'src'),
270 two = flipped ? makeNodeKey(d, 'src') : makeNodeKey(d, 'dst');
271 return one + '-' + two;
272 }
273
274 function findLinkById(id) {
275 // check to see if this is a reverse lookup, else default to given id
276 var key = rlk[id] || id;
277 return key && lu[key];
278 }
279
280 function findLink(linkData, op) {
281 var key = makeLinkKey(linkData),
282 keyrev = makeLinkKey(linkData, 1),
283 link = lu[key],
284 linkRev = lu[keyrev],
285 result = {},
286 ldata = link || linkRev,
287 rawLink;
288
289 if (op === 'add') {
290 if (link) {
291 // trying to add a link that we already know about
292 result.ldata = link;
293 result.badLogic = 'addLink: link already added';
294
295 } else if (linkRev) {
296 // we found the reverse of the link to be added
297 result.ldata = linkRev;
298 if (linkRev.fromTarget) {
299 result.badLogic = 'addLink: link already added';
300 }
301 }
302 } else if (op === 'update') {
303 if (!ldata) {
304 result.badLogic = 'updateLink: link not found';
305 } else {
306 rawLink = link ? ldata.fromSource : ldata.fromTarget;
307 result.updateWith = function (data) {
308 angular.extend(rawLink, data);
309 api.restyleLinkElement(ldata);
310 }
311 }
312 } else if (op === 'remove') {
313 if (!ldata) {
314 result.badLogic = 'removeLink: link not found';
315 } else {
316 rawLink = link ? ldata.fromSource : ldata.fromTarget;
317
318 if (!rawLink) {
319 result.badLogic = 'removeLink: link not found';
320
321 } else {
322 result.removeRawLink = function () {
Bri Prebilic Coleaeeb33e2015-07-09 15:15:54 -0700323 // remove link out of aggregate linksByDevice list
324 var linksForDevPair = linksByDevice[ldata.devicePair],
325 rmvIdx = fs.find(ldata.key, linksForDevPair, 'key');
Bri Prebilic Cole80401762015-07-16 11:36:18 -0700326 if (rmvIdx >= 0) {
327 linksForDevPair.splice(rmvIdx, 1);
328 }
329 ldata.position.multilink = linksForDevPair.length >= 5;
Bri Prebilic Coleaeeb33e2015-07-09 15:15:54 -0700330
Simon Huntdc6adea2015-02-09 22:29:36 -0800331 if (link) {
332 // remove fromSource
333 ldata.fromSource = null;
334 if (ldata.fromTarget) {
335 // promote target into source position
336 ldata.fromSource = ldata.fromTarget;
337 ldata.fromTarget = null;
338 ldata.key = keyrev;
339 delete lu[key];
340 lu[keyrev] = ldata;
341 delete rlk[keyrev];
342 }
343 } else {
344 // remove fromTarget
345 ldata.fromTarget = null;
346 delete rlk[keyrev];
347 }
348 if (ldata.fromSource) {
349 api.restyleLinkElement(ldata);
350 } else {
351 api.removeLinkElement(ldata);
352 }
353 }
354 }
355 }
356 }
357 return result;
358 }
359
360 function findDevices(offlineOnly) {
361 var a = [];
362 nodes.forEach(function (d) {
363 if (d.class === 'device' && !(offlineOnly && d.online)) {
364 a.push(d);
365 }
366 });
367 return a;
368 }
369
370 function findAttachedHosts(devId) {
371 var hosts = [];
372 nodes.forEach(function (d) {
373 if (d.class === 'host' && d.cp.device === devId) {
374 hosts.push(d);
375 }
376 });
377 return hosts;
378 }
379
380 function findAttachedLinks(devId) {
Simon Hunt86b7c882015-04-02 23:06:08 -0700381 var lnks = [];
Simon Huntdc6adea2015-02-09 22:29:36 -0800382 links.forEach(function (d) {
383 if (d.source.id === devId || d.target.id === devId) {
Simon Hunt86b7c882015-04-02 23:06:08 -0700384 lnks.push(d);
Simon Huntdc6adea2015-02-09 22:29:36 -0800385 }
386 });
Simon Hunt86b7c882015-04-02 23:06:08 -0700387 return lnks;
Simon Huntdc6adea2015-02-09 22:29:36 -0800388 }
389
Simon Hunt86b7c882015-04-02 23:06:08 -0700390 // returns one-way links or where the internal link types differ
391 function findBadLinks() {
392 var lnks = [],
393 src, tgt;
394 links.forEach(function (d) {
395 // NOTE: skip edge links, which are synthesized
396 if (d.type() !== 'hostLink') {
397 delete d.bad;
398 src = d.fromSource;
399 tgt = d.fromTarget;
400 if (src && !tgt) {
401 d.bad = 'missing link';
402 } else if (src.type !== tgt.type) {
403 d.bad = 'type mismatch';
404 }
405 if (d.bad) {
406 lnks.push(d);
407 }
408 }
409 });
410 return lnks;
411 }
Simon Huntdc6adea2015-02-09 22:29:36 -0800412
Simon Hunt3a6eec02015-02-09 21:16:43 -0800413 // ==========================
414 // Module definition
415
416 angular.module('ovTopo')
Simon Hunt75ec9692015-02-11 16:40:36 -0800417 .factory('TopoModelService',
Simon Hunt3a6eec02015-02-09 21:16:43 -0800418 ['$log', 'FnService', 'RandomService',
419
420 function (_$log_, _fs_, _rnd_) {
421 $log = _$log_;
422 fs = _fs_;
423 rnd = _rnd_;
424
425 function initModel(_api_, _dim_) {
426 api = _api_;
427 dim = _dim_;
Simon Huntdc6adea2015-02-09 22:29:36 -0800428 lu = api.network.lookup;
429 rlk = api.network.revLinkToKey;
430 nodes = api.network.nodes;
431 links = api.network.links;
Bri Prebilic Coleaeeb33e2015-07-09 15:15:54 -0700432 linksByDevice = api.network.linksByDevice;
Simon Hunt3a6eec02015-02-09 21:16:43 -0800433 }
434
435 function newDim(_dim_) {
436 dim = _dim_;
437 }
438
Simon Huntf542d842015-02-11 16:20:33 -0800439 function destroyModel() { }
440
Simon Hunt3a6eec02015-02-09 21:16:43 -0800441 return {
442 initModel: initModel,
443 newDim: newDim,
Simon Huntf542d842015-02-11 16:20:33 -0800444 destroyModel: destroyModel,
Simon Hunt3a6eec02015-02-09 21:16:43 -0800445
446 positionNode: positionNode,
Simon Huntfd7106c2016-02-09 15:05:26 -0800447 resetAllLocations: resetAllLocations,
Simon Hunt3a6eec02015-02-09 21:16:43 -0800448 createDeviceNode: createDeviceNode,
449 createHostNode: createHostNode,
450 createHostLink: createHostLink,
451 createLink: createLink,
452 coordFromLngLat: coordFromLngLat,
453 lngLatFromCoord: lngLatFromCoord,
Simon Huntdc6adea2015-02-09 22:29:36 -0800454 findLink: findLink,
455 findLinkById: findLinkById,
456 findDevices: findDevices,
457 findAttachedHosts: findAttachedHosts,
Simon Hunt86b7c882015-04-02 23:06:08 -0700458 findAttachedLinks: findAttachedLinks,
459 findBadLinks: findBadLinks
Simon Hunt3a6eec02015-02-09 21:16:43 -0800460 }
461 }]);
462}());