blob: 6bec7e8b188efef6a5136e2e0eaed5313697280b [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
39 var lu, rlk, nodes, links;
40
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();
50 return p ? p([loc.lng, loc.lat]) : [0, 0];
51 }
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
64 // If we have [x,y] already, use that...
65 if (x && y) {
66 node.fixed = true;
67 node.px = node.x = x;
68 node.py = node.y = y;
69 return;
70 }
71
72 var location = node.location,
73 coord;
74
75 if (location && location.type === 'latlng') {
76 coord = coordFromLngLat(location);
77 node.fixed = true;
78 node.px = node.x = coord[0];
79 node.py = node.y = coord[1];
80 return true;
81 }
82
83 // if this is a node update (not a node add).. skip randomizer
84 if (forUpdate) {
85 return;
86 }
87
88 // Note: Placing incoming unpinned nodes at exactly the same point
89 // (center of the view) causes them to explode outwards when
90 // the force layout kicks in. So, we spread them out a bit
91 // initially, to provide a more serene layout convergence.
92 // Additionally, if the node is a host, we place it near
93 // the device it is connected to.
94
95 function rand() {
96 return {
97 x: rnd.randDim(dim[0]),
98 y: rnd.randDim(dim[1])
99 };
100 }
101
102 function near(node) {
103 return {
104 x: node.x + nearDist + rnd.spread(nearDist),
105 y: node.y + nearDist + rnd.spread(nearDist)
106 };
107 }
108
109 function getDevice(cp) {
Simon Huntdc6adea2015-02-09 22:29:36 -0800110 var d = lu[cp.device];
Simon Hunt3a6eec02015-02-09 21:16:43 -0800111 return d || rand();
112 }
113
114 xy = (node.class === 'host') ? near(getDevice(node.cp)) : rand();
115 angular.extend(node, xy);
116 }
117
118 function mkSvgCls(dh, t, on) {
119 var ndh = 'node ' + dh,
120 ndht = t ? ndh + ' ' + t : ndh;
121 return on ? ndht + ' online' : ndht;
122 }
123
124 function createDeviceNode(device) {
125 var node = device;
126
127 // Augment as needed...
128 node.class = 'device';
129 node.svgClass = mkSvgCls('device', device.type, device.online);
130 positionNode(node);
131 return node;
132 }
133
134 function createHostNode(host) {
135 var node = host;
136
137 // Augment as needed...
138 node.class = 'host';
139 if (!node.type) {
140 node.type = 'endstation';
141 }
142 node.svgClass = mkSvgCls('host', node.type);
143 positionNode(node);
144 return node;
145 }
146
147 function createHostLink(host) {
148 var src = host.id,
149 dst = host.cp.device,
150 id = host.ingress,
151 lnk = linkEndPoints(src, dst);
152
153 if (!lnk) {
154 return null;
155 }
156
157 // Synthesize link ...
158 angular.extend(lnk, {
159 key: id,
160 class: 'link',
Simon Hunt5f361082015-02-25 11:36:38 -0800161 // NOTE: srcPort left undefined (host end of the link)
162 tgtPort: host.cp.port,
Simon Hunt3a6eec02015-02-09 21:16:43 -0800163
164 type: function () { return 'hostLink'; },
165 online: function () {
166 // hostlink target is edge switch
167 return lnk.target.online;
168 },
169 linkWidth: function () { return 1; }
170 });
171 return lnk;
172 }
173
174 function createLink(link) {
175 var lnk = linkEndPoints(link.src, link.dst);
176
177 if (!lnk) {
178 return null;
179 }
180
181 angular.extend(lnk, {
182 key: link.id,
183 class: 'link',
184 fromSource: link,
Simon Hunt5f361082015-02-25 11:36:38 -0800185 srcPort: link.srcPort,
186 tgtPort: link.dstPort,
Simon Hunt3a6eec02015-02-09 21:16:43 -0800187
188 // functions to aggregate dual link state
189 type: function () {
190 var s = lnk.fromSource,
191 t = lnk.fromTarget;
192 return (s && s.type) || (t && t.type) || defaultLinkType;
193 },
194 online: function () {
195 var s = lnk.fromSource,
196 t = lnk.fromTarget,
197 both = lnk.source.online && lnk.target.online;
198 return both && ((s && s.online) || (t && t.online));
199 },
200 linkWidth: function () {
201 var s = lnk.fromSource,
202 t = lnk.fromTarget,
203 ws = (s && s.linkWidth) || 0,
204 wt = (t && t.linkWidth) || 0;
205 return Math.max(ws, wt);
206 }
207 });
208 return lnk;
209 }
210
211
212 function linkEndPoints(srcId, dstId) {
Simon Huntdc6adea2015-02-09 22:29:36 -0800213 var srcNode = lu[srcId],
214 dstNode = lu[dstId],
Simon Hunt3a6eec02015-02-09 21:16:43 -0800215 sMiss = !srcNode ? missMsg('src', srcId) : '',
216 dMiss = !dstNode ? missMsg('dst', dstId) : '';
217
218 if (sMiss || dMiss) {
219 $log.error('Node(s) not on map for link:' + sMiss + dMiss);
220 //logicError('Node(s) not on map for link:\n' + sMiss + dMiss);
221 return null;
222 }
223 return {
224 source: srcNode,
Simon Hunt5f361082015-02-25 11:36:38 -0800225 target: dstNode
Simon Hunt3a6eec02015-02-09 21:16:43 -0800226 };
227 }
228
229 function missMsg(what, id) {
230 return '\n[' + what + '] "' + id + '" missing';
231 }
232
Simon Huntdc6adea2015-02-09 22:29:36 -0800233
234 function makeNodeKey(d, what) {
235 var port = what + 'Port';
236 return d[what] + '/' + d[port];
237 }
238
239 function makeLinkKey(d, flipped) {
240 var one = flipped ? makeNodeKey(d, 'dst') : makeNodeKey(d, 'src'),
241 two = flipped ? makeNodeKey(d, 'src') : makeNodeKey(d, 'dst');
242 return one + '-' + two;
243 }
244
245 function findLinkById(id) {
246 // check to see if this is a reverse lookup, else default to given id
247 var key = rlk[id] || id;
248 return key && lu[key];
249 }
250
251 function findLink(linkData, op) {
252 var key = makeLinkKey(linkData),
253 keyrev = makeLinkKey(linkData, 1),
254 link = lu[key],
255 linkRev = lu[keyrev],
256 result = {},
257 ldata = link || linkRev,
258 rawLink;
259
260 if (op === 'add') {
261 if (link) {
262 // trying to add a link that we already know about
263 result.ldata = link;
264 result.badLogic = 'addLink: link already added';
265
266 } else if (linkRev) {
267 // we found the reverse of the link to be added
268 result.ldata = linkRev;
269 if (linkRev.fromTarget) {
270 result.badLogic = 'addLink: link already added';
271 }
272 }
273 } else if (op === 'update') {
274 if (!ldata) {
275 result.badLogic = 'updateLink: link not found';
276 } else {
277 rawLink = link ? ldata.fromSource : ldata.fromTarget;
278 result.updateWith = function (data) {
279 angular.extend(rawLink, data);
280 api.restyleLinkElement(ldata);
281 }
282 }
283 } else if (op === 'remove') {
284 if (!ldata) {
285 result.badLogic = 'removeLink: link not found';
286 } else {
287 rawLink = link ? ldata.fromSource : ldata.fromTarget;
288
289 if (!rawLink) {
290 result.badLogic = 'removeLink: link not found';
291
292 } else {
293 result.removeRawLink = function () {
294 if (link) {
295 // remove fromSource
296 ldata.fromSource = null;
297 if (ldata.fromTarget) {
298 // promote target into source position
299 ldata.fromSource = ldata.fromTarget;
300 ldata.fromTarget = null;
301 ldata.key = keyrev;
302 delete lu[key];
303 lu[keyrev] = ldata;
304 delete rlk[keyrev];
305 }
306 } else {
307 // remove fromTarget
308 ldata.fromTarget = null;
309 delete rlk[keyrev];
310 }
311 if (ldata.fromSource) {
312 api.restyleLinkElement(ldata);
313 } else {
314 api.removeLinkElement(ldata);
315 }
316 }
317 }
318 }
319 }
320 return result;
321 }
322
323 function findDevices(offlineOnly) {
324 var a = [];
325 nodes.forEach(function (d) {
326 if (d.class === 'device' && !(offlineOnly && d.online)) {
327 a.push(d);
328 }
329 });
330 return a;
331 }
332
333 function findAttachedHosts(devId) {
334 var hosts = [];
335 nodes.forEach(function (d) {
336 if (d.class === 'host' && d.cp.device === devId) {
337 hosts.push(d);
338 }
339 });
340 return hosts;
341 }
342
343 function findAttachedLinks(devId) {
Simon Hunt86b7c882015-04-02 23:06:08 -0700344 var lnks = [];
Simon Huntdc6adea2015-02-09 22:29:36 -0800345 links.forEach(function (d) {
346 if (d.source.id === devId || d.target.id === devId) {
Simon Hunt86b7c882015-04-02 23:06:08 -0700347 lnks.push(d);
Simon Huntdc6adea2015-02-09 22:29:36 -0800348 }
349 });
Simon Hunt86b7c882015-04-02 23:06:08 -0700350 return lnks;
Simon Huntdc6adea2015-02-09 22:29:36 -0800351 }
352
Simon Hunt86b7c882015-04-02 23:06:08 -0700353 // returns one-way links or where the internal link types differ
354 function findBadLinks() {
355 var lnks = [],
356 src, tgt;
357 links.forEach(function (d) {
358 // NOTE: skip edge links, which are synthesized
359 if (d.type() !== 'hostLink') {
360 delete d.bad;
361 src = d.fromSource;
362 tgt = d.fromTarget;
363 if (src && !tgt) {
364 d.bad = 'missing link';
365 } else if (src.type !== tgt.type) {
366 d.bad = 'type mismatch';
367 }
368 if (d.bad) {
369 lnks.push(d);
370 }
371 }
372 });
373 return lnks;
374 }
Simon Huntdc6adea2015-02-09 22:29:36 -0800375
Simon Hunt3a6eec02015-02-09 21:16:43 -0800376 // ==========================
377 // Module definition
378
379 angular.module('ovTopo')
Simon Hunt75ec9692015-02-11 16:40:36 -0800380 .factory('TopoModelService',
Simon Hunt3a6eec02015-02-09 21:16:43 -0800381 ['$log', 'FnService', 'RandomService',
382
383 function (_$log_, _fs_, _rnd_) {
384 $log = _$log_;
385 fs = _fs_;
386 rnd = _rnd_;
387
388 function initModel(_api_, _dim_) {
389 api = _api_;
390 dim = _dim_;
Simon Huntdc6adea2015-02-09 22:29:36 -0800391 lu = api.network.lookup;
392 rlk = api.network.revLinkToKey;
393 nodes = api.network.nodes;
394 links = api.network.links;
Simon Hunt3a6eec02015-02-09 21:16:43 -0800395 }
396
397 function newDim(_dim_) {
398 dim = _dim_;
399 }
400
Simon Huntf542d842015-02-11 16:20:33 -0800401 function destroyModel() { }
402
Simon Hunt3a6eec02015-02-09 21:16:43 -0800403 return {
404 initModel: initModel,
405 newDim: newDim,
Simon Huntf542d842015-02-11 16:20:33 -0800406 destroyModel: destroyModel,
Simon Hunt3a6eec02015-02-09 21:16:43 -0800407
408 positionNode: positionNode,
409 createDeviceNode: createDeviceNode,
410 createHostNode: createHostNode,
411 createHostLink: createHostLink,
412 createLink: createLink,
413 coordFromLngLat: coordFromLngLat,
414 lngLatFromCoord: lngLatFromCoord,
Simon Huntdc6adea2015-02-09 22:29:36 -0800415 findLink: findLink,
416 findLinkById: findLinkById,
417 findDevices: findDevices,
418 findAttachedHosts: findAttachedHosts,
Simon Hunt86b7c882015-04-02 23:06:08 -0700419 findAttachedLinks: findAttachedLinks,
420 findBadLinks: findBadLinks
Simon Hunt3a6eec02015-02-09 21:16:43 -0800421 }
422 }]);
423}());