blob: a21fc9379cc6962a0ed6decf1477af808e57f8f6 [file] [log] [blame]
Simon Hunt3a6eec02015-02-09 21:16:43 -08001/*
2 * Copyright 2015 Open Networking Laboratory
3 *
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
65 // If we have [x,y] already, use that...
66 if (x && y) {
67 node.fixed = true;
68 node.px = node.x = x;
69 node.py = node.y = y;
70 return;
71 }
72
73 var location = node.location,
74 coord;
75
76 if (location && location.type === 'latlng') {
77 coord = coordFromLngLat(location);
78 node.fixed = true;
79 node.px = node.x = coord[0];
80 node.py = node.y = coord[1];
81 return true;
82 }
83
84 // if this is a node update (not a node add).. skip randomizer
85 if (forUpdate) {
86 return;
87 }
88
89 // Note: Placing incoming unpinned nodes at exactly the same point
90 // (center of the view) causes them to explode outwards when
91 // the force layout kicks in. So, we spread them out a bit
92 // initially, to provide a more serene layout convergence.
93 // Additionally, if the node is a host, we place it near
94 // the device it is connected to.
95
96 function rand() {
97 return {
98 x: rnd.randDim(dim[0]),
99 y: rnd.randDim(dim[1])
100 };
101 }
102
103 function near(node) {
104 return {
105 x: node.x + nearDist + rnd.spread(nearDist),
106 y: node.y + nearDist + rnd.spread(nearDist)
107 };
108 }
109
110 function getDevice(cp) {
Simon Huntdc6adea2015-02-09 22:29:36 -0800111 var d = lu[cp.device];
Simon Hunt3a6eec02015-02-09 21:16:43 -0800112 return d || rand();
113 }
114
115 xy = (node.class === 'host') ? near(getDevice(node.cp)) : rand();
116 angular.extend(node, xy);
117 }
118
119 function mkSvgCls(dh, t, on) {
120 var ndh = 'node ' + dh,
121 ndht = t ? ndh + ' ' + t : ndh;
122 return on ? ndht + ' online' : ndht;
123 }
124
125 function createDeviceNode(device) {
126 var node = device;
127
128 // Augment as needed...
129 node.class = 'device';
130 node.svgClass = mkSvgCls('device', device.type, device.online);
131 positionNode(node);
132 return node;
133 }
134
135 function createHostNode(host) {
136 var node = host;
137
138 // Augment as needed...
139 node.class = 'host';
140 if (!node.type) {
141 node.type = 'endstation';
142 }
143 node.svgClass = mkSvgCls('host', node.type);
144 positionNode(node);
145 return node;
146 }
147
148 function createHostLink(host) {
149 var src = host.id,
150 dst = host.cp.device,
151 id = host.ingress,
152 lnk = linkEndPoints(src, dst);
153
154 if (!lnk) {
155 return null;
156 }
157
158 // Synthesize link ...
159 angular.extend(lnk, {
160 key: id,
161 class: 'link',
Simon Hunt5f361082015-02-25 11:36:38 -0800162 // NOTE: srcPort left undefined (host end of the link)
163 tgtPort: host.cp.port,
Simon Hunt3a6eec02015-02-09 21:16:43 -0800164
165 type: function () { return 'hostLink'; },
Simon Hunt219f0772016-02-09 10:58:49 -0800166 expected: function () { return true; },
Simon Hunt3a6eec02015-02-09 21:16:43 -0800167 online: function () {
168 // hostlink target is edge switch
169 return lnk.target.online;
170 },
171 linkWidth: function () { return 1; }
172 });
173 return lnk;
174 }
175
176 function createLink(link) {
177 var lnk = linkEndPoints(link.src, link.dst);
178
179 if (!lnk) {
180 return null;
181 }
182
183 angular.extend(lnk, {
184 key: link.id,
185 class: 'link',
186 fromSource: link,
Simon Hunt5f361082015-02-25 11:36:38 -0800187 srcPort: link.srcPort,
188 tgtPort: link.dstPort,
Bri Prebilic Coleaeeb33e2015-07-09 15:15:54 -0700189 position: {
190 x1: 0,
191 y1: 0,
192 x2: 0,
193 y2: 0
194 },
Simon Hunt3a6eec02015-02-09 21:16:43 -0800195
196 // functions to aggregate dual link state
197 type: function () {
198 var s = lnk.fromSource,
199 t = lnk.fromTarget;
200 return (s && s.type) || (t && t.type) || defaultLinkType;
201 },
Ray Milkeyb7f0f642016-01-22 16:08:14 -0800202 expected: function () {
203 var s = lnk.fromSource,
204 t = lnk.fromTarget;
205 return (s && s.expected) && (t && t.expected);
206 },
Simon Hunt3a6eec02015-02-09 21:16:43 -0800207 online: function () {
208 var s = lnk.fromSource,
209 t = lnk.fromTarget,
210 both = lnk.source.online && lnk.target.online;
Simon Hunt15813b22016-01-29 08:29:08 -0800211 return both && (s && s.online) && (t && t.online);
Simon Hunt3a6eec02015-02-09 21:16:43 -0800212 },
213 linkWidth: function () {
214 var s = lnk.fromSource,
215 t = lnk.fromTarget,
216 ws = (s && s.linkWidth) || 0,
217 wt = (t && t.linkWidth) || 0;
Bri Prebilic Cole038aedd2015-07-13 15:25:16 -0700218 return lnk.position.multiLink ? 5 : Math.max(ws, wt);
Simon Hunt3a6eec02015-02-09 21:16:43 -0800219 }
220 });
221 return lnk;
222 }
223
224
225 function linkEndPoints(srcId, dstId) {
Simon Huntdc6adea2015-02-09 22:29:36 -0800226 var srcNode = lu[srcId],
227 dstNode = lu[dstId],
Simon Hunt3a6eec02015-02-09 21:16:43 -0800228 sMiss = !srcNode ? missMsg('src', srcId) : '',
229 dMiss = !dstNode ? missMsg('dst', dstId) : '';
230
231 if (sMiss || dMiss) {
232 $log.error('Node(s) not on map for link:' + sMiss + dMiss);
233 //logicError('Node(s) not on map for link:\n' + sMiss + dMiss);
234 return null;
235 }
236 return {
237 source: srcNode,
Simon Hunt5f361082015-02-25 11:36:38 -0800238 target: dstNode
Simon Hunt3a6eec02015-02-09 21:16:43 -0800239 };
240 }
241
242 function missMsg(what, id) {
243 return '\n[' + what + '] "' + id + '" missing';
244 }
245
Simon Huntdc6adea2015-02-09 22:29:36 -0800246
247 function makeNodeKey(d, what) {
248 var port = what + 'Port';
249 return d[what] + '/' + d[port];
250 }
251
252 function makeLinkKey(d, flipped) {
253 var one = flipped ? makeNodeKey(d, 'dst') : makeNodeKey(d, 'src'),
254 two = flipped ? makeNodeKey(d, 'src') : makeNodeKey(d, 'dst');
255 return one + '-' + two;
256 }
257
258 function findLinkById(id) {
259 // check to see if this is a reverse lookup, else default to given id
260 var key = rlk[id] || id;
261 return key && lu[key];
262 }
263
264 function findLink(linkData, op) {
265 var key = makeLinkKey(linkData),
266 keyrev = makeLinkKey(linkData, 1),
267 link = lu[key],
268 linkRev = lu[keyrev],
269 result = {},
270 ldata = link || linkRev,
271 rawLink;
272
273 if (op === 'add') {
274 if (link) {
275 // trying to add a link that we already know about
276 result.ldata = link;
277 result.badLogic = 'addLink: link already added';
278
279 } else if (linkRev) {
280 // we found the reverse of the link to be added
281 result.ldata = linkRev;
282 if (linkRev.fromTarget) {
283 result.badLogic = 'addLink: link already added';
284 }
285 }
286 } else if (op === 'update') {
287 if (!ldata) {
288 result.badLogic = 'updateLink: link not found';
289 } else {
290 rawLink = link ? ldata.fromSource : ldata.fromTarget;
291 result.updateWith = function (data) {
292 angular.extend(rawLink, data);
293 api.restyleLinkElement(ldata);
294 }
295 }
296 } else if (op === 'remove') {
297 if (!ldata) {
298 result.badLogic = 'removeLink: link not found';
299 } else {
300 rawLink = link ? ldata.fromSource : ldata.fromTarget;
301
302 if (!rawLink) {
303 result.badLogic = 'removeLink: link not found';
304
305 } else {
306 result.removeRawLink = function () {
Bri Prebilic Coleaeeb33e2015-07-09 15:15:54 -0700307 // remove link out of aggregate linksByDevice list
308 var linksForDevPair = linksByDevice[ldata.devicePair],
309 rmvIdx = fs.find(ldata.key, linksForDevPair, 'key');
Bri Prebilic Cole80401762015-07-16 11:36:18 -0700310 if (rmvIdx >= 0) {
311 linksForDevPair.splice(rmvIdx, 1);
312 }
313 ldata.position.multilink = linksForDevPair.length >= 5;
Bri Prebilic Coleaeeb33e2015-07-09 15:15:54 -0700314
Simon Huntdc6adea2015-02-09 22:29:36 -0800315 if (link) {
316 // remove fromSource
317 ldata.fromSource = null;
318 if (ldata.fromTarget) {
319 // promote target into source position
320 ldata.fromSource = ldata.fromTarget;
321 ldata.fromTarget = null;
322 ldata.key = keyrev;
323 delete lu[key];
324 lu[keyrev] = ldata;
325 delete rlk[keyrev];
326 }
327 } else {
328 // remove fromTarget
329 ldata.fromTarget = null;
330 delete rlk[keyrev];
331 }
332 if (ldata.fromSource) {
333 api.restyleLinkElement(ldata);
334 } else {
335 api.removeLinkElement(ldata);
336 }
337 }
338 }
339 }
340 }
341 return result;
342 }
343
344 function findDevices(offlineOnly) {
345 var a = [];
346 nodes.forEach(function (d) {
347 if (d.class === 'device' && !(offlineOnly && d.online)) {
348 a.push(d);
349 }
350 });
351 return a;
352 }
353
354 function findAttachedHosts(devId) {
355 var hosts = [];
356 nodes.forEach(function (d) {
357 if (d.class === 'host' && d.cp.device === devId) {
358 hosts.push(d);
359 }
360 });
361 return hosts;
362 }
363
364 function findAttachedLinks(devId) {
Simon Hunt86b7c882015-04-02 23:06:08 -0700365 var lnks = [];
Simon Huntdc6adea2015-02-09 22:29:36 -0800366 links.forEach(function (d) {
367 if (d.source.id === devId || d.target.id === devId) {
Simon Hunt86b7c882015-04-02 23:06:08 -0700368 lnks.push(d);
Simon Huntdc6adea2015-02-09 22:29:36 -0800369 }
370 });
Simon Hunt86b7c882015-04-02 23:06:08 -0700371 return lnks;
Simon Huntdc6adea2015-02-09 22:29:36 -0800372 }
373
Simon Hunt86b7c882015-04-02 23:06:08 -0700374 // returns one-way links or where the internal link types differ
375 function findBadLinks() {
376 var lnks = [],
377 src, tgt;
378 links.forEach(function (d) {
379 // NOTE: skip edge links, which are synthesized
380 if (d.type() !== 'hostLink') {
381 delete d.bad;
382 src = d.fromSource;
383 tgt = d.fromTarget;
384 if (src && !tgt) {
385 d.bad = 'missing link';
386 } else if (src.type !== tgt.type) {
387 d.bad = 'type mismatch';
388 }
389 if (d.bad) {
390 lnks.push(d);
391 }
392 }
393 });
394 return lnks;
395 }
Simon Huntdc6adea2015-02-09 22:29:36 -0800396
Simon Hunt3a6eec02015-02-09 21:16:43 -0800397 // ==========================
398 // Module definition
399
400 angular.module('ovTopo')
Simon Hunt75ec9692015-02-11 16:40:36 -0800401 .factory('TopoModelService',
Simon Hunt3a6eec02015-02-09 21:16:43 -0800402 ['$log', 'FnService', 'RandomService',
403
404 function (_$log_, _fs_, _rnd_) {
405 $log = _$log_;
406 fs = _fs_;
407 rnd = _rnd_;
408
409 function initModel(_api_, _dim_) {
410 api = _api_;
411 dim = _dim_;
Simon Huntdc6adea2015-02-09 22:29:36 -0800412 lu = api.network.lookup;
413 rlk = api.network.revLinkToKey;
414 nodes = api.network.nodes;
415 links = api.network.links;
Bri Prebilic Coleaeeb33e2015-07-09 15:15:54 -0700416 linksByDevice = api.network.linksByDevice;
Simon Hunt3a6eec02015-02-09 21:16:43 -0800417 }
418
419 function newDim(_dim_) {
420 dim = _dim_;
421 }
422
Simon Huntf542d842015-02-11 16:20:33 -0800423 function destroyModel() { }
424
Simon Hunt3a6eec02015-02-09 21:16:43 -0800425 return {
426 initModel: initModel,
427 newDim: newDim,
Simon Huntf542d842015-02-11 16:20:33 -0800428 destroyModel: destroyModel,
Simon Hunt3a6eec02015-02-09 21:16:43 -0800429
430 positionNode: positionNode,
431 createDeviceNode: createDeviceNode,
432 createHostNode: createHostNode,
433 createHostLink: createHostLink,
434 createLink: createLink,
435 coordFromLngLat: coordFromLngLat,
436 lngLatFromCoord: lngLatFromCoord,
Simon Huntdc6adea2015-02-09 22:29:36 -0800437 findLink: findLink,
438 findLinkById: findLinkById,
439 findDevices: findDevices,
440 findAttachedHosts: findAttachedHosts,
Simon Hunt86b7c882015-04-02 23:06:08 -0700441 findAttachedLinks: findAttachedLinks,
442 findBadLinks: findBadLinks
Simon Hunt3a6eec02015-02-09 21:16:43 -0800443 }
444 }]);
445}());