blob: 4754f60f09fdcf7654922861089ccdc4225d5aa2 [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
27 var $log, fs, rnd, api;
28
Simon Huntdc6adea2015-02-09 22:29:36 -080029 // shorthand
30 var lu, rlk, nodes, links;
31
32 // api:
33 // projection: func()
34 // network {...}
35 // restyleLinkElement: func(ldata)
36 // removeLinkElement: func(ldata)
37
Simon Hunt3a6eec02015-02-09 21:16:43 -080038 var dim; // dimensions of layout, as [w,h]
39
40 // configuration 'constants'
41 var defaultLinkType = 'direct',
42 nearDist = 15;
43
44
45 function coordFromLngLat(loc) {
46 var p = api.projection();
47 return p ? p([loc.lng, loc.lat]) : [0, 0];
48 }
49
50 function lngLatFromCoord(coord) {
51 var p = api.projection();
52 return p ? p.invert(coord) : [0, 0];
53 }
54
55 function positionNode(node, forUpdate) {
56 var meta = node.metaUi,
57 x = meta && meta.x,
58 y = meta && meta.y,
59 xy;
60
61 // If we have [x,y] already, use that...
62 if (x && y) {
63 node.fixed = true;
64 node.px = node.x = x;
65 node.py = node.y = y;
66 return;
67 }
68
69 var location = node.location,
70 coord;
71
72 if (location && location.type === 'latlng') {
73 coord = coordFromLngLat(location);
74 node.fixed = true;
75 node.px = node.x = coord[0];
76 node.py = node.y = coord[1];
77 return true;
78 }
79
80 // if this is a node update (not a node add).. skip randomizer
81 if (forUpdate) {
82 return;
83 }
84
85 // Note: Placing incoming unpinned nodes at exactly the same point
86 // (center of the view) causes them to explode outwards when
87 // the force layout kicks in. So, we spread them out a bit
88 // initially, to provide a more serene layout convergence.
89 // Additionally, if the node is a host, we place it near
90 // the device it is connected to.
91
92 function rand() {
93 return {
94 x: rnd.randDim(dim[0]),
95 y: rnd.randDim(dim[1])
96 };
97 }
98
99 function near(node) {
100 return {
101 x: node.x + nearDist + rnd.spread(nearDist),
102 y: node.y + nearDist + rnd.spread(nearDist)
103 };
104 }
105
106 function getDevice(cp) {
Simon Huntdc6adea2015-02-09 22:29:36 -0800107 var d = lu[cp.device];
Simon Hunt3a6eec02015-02-09 21:16:43 -0800108 return d || rand();
109 }
110
111 xy = (node.class === 'host') ? near(getDevice(node.cp)) : rand();
112 angular.extend(node, xy);
113 }
114
115 function mkSvgCls(dh, t, on) {
116 var ndh = 'node ' + dh,
117 ndht = t ? ndh + ' ' + t : ndh;
118 return on ? ndht + ' online' : ndht;
119 }
120
121 function createDeviceNode(device) {
122 var node = device;
123
124 // Augment as needed...
125 node.class = 'device';
126 node.svgClass = mkSvgCls('device', device.type, device.online);
127 positionNode(node);
128 return node;
129 }
130
131 function createHostNode(host) {
132 var node = host;
133
134 // Augment as needed...
135 node.class = 'host';
136 if (!node.type) {
137 node.type = 'endstation';
138 }
139 node.svgClass = mkSvgCls('host', node.type);
140 positionNode(node);
141 return node;
142 }
143
144 function createHostLink(host) {
145 var src = host.id,
146 dst = host.cp.device,
147 id = host.ingress,
148 lnk = linkEndPoints(src, dst);
149
150 if (!lnk) {
151 return null;
152 }
153
154 // Synthesize link ...
155 angular.extend(lnk, {
156 key: id,
157 class: 'link',
158
159 type: function () { return 'hostLink'; },
160 online: function () {
161 // hostlink target is edge switch
162 return lnk.target.online;
163 },
164 linkWidth: function () { return 1; }
165 });
166 return lnk;
167 }
168
169 function createLink(link) {
170 var lnk = linkEndPoints(link.src, link.dst);
171
172 if (!lnk) {
173 return null;
174 }
175
176 angular.extend(lnk, {
177 key: link.id,
178 class: 'link',
179 fromSource: link,
180
181 // functions to aggregate dual link state
182 type: function () {
183 var s = lnk.fromSource,
184 t = lnk.fromTarget;
185 return (s && s.type) || (t && t.type) || defaultLinkType;
186 },
187 online: function () {
188 var s = lnk.fromSource,
189 t = lnk.fromTarget,
190 both = lnk.source.online && lnk.target.online;
191 return both && ((s && s.online) || (t && t.online));
192 },
193 linkWidth: function () {
194 var s = lnk.fromSource,
195 t = lnk.fromTarget,
196 ws = (s && s.linkWidth) || 0,
197 wt = (t && t.linkWidth) || 0;
198 return Math.max(ws, wt);
199 }
200 });
201 return lnk;
202 }
203
204
205 function linkEndPoints(srcId, dstId) {
Simon Huntdc6adea2015-02-09 22:29:36 -0800206 var srcNode = lu[srcId],
207 dstNode = lu[dstId],
Simon Hunt3a6eec02015-02-09 21:16:43 -0800208 sMiss = !srcNode ? missMsg('src', srcId) : '',
209 dMiss = !dstNode ? missMsg('dst', dstId) : '';
210
211 if (sMiss || dMiss) {
212 $log.error('Node(s) not on map for link:' + sMiss + dMiss);
213 //logicError('Node(s) not on map for link:\n' + sMiss + dMiss);
214 return null;
215 }
216 return {
217 source: srcNode,
218 target: dstNode,
219 x1: srcNode.x,
220 y1: srcNode.y,
221 x2: dstNode.x,
222 y2: dstNode.y
223 };
224 }
225
226 function missMsg(what, id) {
227 return '\n[' + what + '] "' + id + '" missing';
228 }
229
Simon Huntdc6adea2015-02-09 22:29:36 -0800230
231 function makeNodeKey(d, what) {
232 var port = what + 'Port';
233 return d[what] + '/' + d[port];
234 }
235
236 function makeLinkKey(d, flipped) {
237 var one = flipped ? makeNodeKey(d, 'dst') : makeNodeKey(d, 'src'),
238 two = flipped ? makeNodeKey(d, 'src') : makeNodeKey(d, 'dst');
239 return one + '-' + two;
240 }
241
242 function findLinkById(id) {
243 // check to see if this is a reverse lookup, else default to given id
244 var key = rlk[id] || id;
245 return key && lu[key];
246 }
247
248 function findLink(linkData, op) {
249 var key = makeLinkKey(linkData),
250 keyrev = makeLinkKey(linkData, 1),
251 link = lu[key],
252 linkRev = lu[keyrev],
253 result = {},
254 ldata = link || linkRev,
255 rawLink;
256
257 if (op === 'add') {
258 if (link) {
259 // trying to add a link that we already know about
260 result.ldata = link;
261 result.badLogic = 'addLink: link already added';
262
263 } else if (linkRev) {
264 // we found the reverse of the link to be added
265 result.ldata = linkRev;
266 if (linkRev.fromTarget) {
267 result.badLogic = 'addLink: link already added';
268 }
269 }
270 } else if (op === 'update') {
271 if (!ldata) {
272 result.badLogic = 'updateLink: link not found';
273 } else {
274 rawLink = link ? ldata.fromSource : ldata.fromTarget;
275 result.updateWith = function (data) {
276 angular.extend(rawLink, data);
277 api.restyleLinkElement(ldata);
278 }
279 }
280 } else if (op === 'remove') {
281 if (!ldata) {
282 result.badLogic = 'removeLink: link not found';
283 } else {
284 rawLink = link ? ldata.fromSource : ldata.fromTarget;
285
286 if (!rawLink) {
287 result.badLogic = 'removeLink: link not found';
288
289 } else {
290 result.removeRawLink = function () {
291 if (link) {
292 // remove fromSource
293 ldata.fromSource = null;
294 if (ldata.fromTarget) {
295 // promote target into source position
296 ldata.fromSource = ldata.fromTarget;
297 ldata.fromTarget = null;
298 ldata.key = keyrev;
299 delete lu[key];
300 lu[keyrev] = ldata;
301 delete rlk[keyrev];
302 }
303 } else {
304 // remove fromTarget
305 ldata.fromTarget = null;
306 delete rlk[keyrev];
307 }
308 if (ldata.fromSource) {
309 api.restyleLinkElement(ldata);
310 } else {
311 api.removeLinkElement(ldata);
312 }
313 }
314 }
315 }
316 }
317 return result;
318 }
319
320 function findDevices(offlineOnly) {
321 var a = [];
322 nodes.forEach(function (d) {
323 if (d.class === 'device' && !(offlineOnly && d.online)) {
324 a.push(d);
325 }
326 });
327 return a;
328 }
329
330 function findAttachedHosts(devId) {
331 var hosts = [];
332 nodes.forEach(function (d) {
333 if (d.class === 'host' && d.cp.device === devId) {
334 hosts.push(d);
335 }
336 });
337 return hosts;
338 }
339
340 function findAttachedLinks(devId) {
341 var links = [];
342 links.forEach(function (d) {
343 if (d.source.id === devId || d.target.id === devId) {
344 links.push(d);
345 }
346 });
347 return links;
348 }
349
350
Simon Hunt3a6eec02015-02-09 21:16:43 -0800351 // ==========================
352 // Module definition
353
354 angular.module('ovTopo')
355 .factory('TopoModelService',
356 ['$log', 'FnService', 'RandomService',
357
358 function (_$log_, _fs_, _rnd_) {
359 $log = _$log_;
360 fs = _fs_;
361 rnd = _rnd_;
362
363 function initModel(_api_, _dim_) {
364 api = _api_;
365 dim = _dim_;
Simon Huntdc6adea2015-02-09 22:29:36 -0800366 lu = api.network.lookup;
367 rlk = api.network.revLinkToKey;
368 nodes = api.network.nodes;
369 links = api.network.links;
Simon Hunt3a6eec02015-02-09 21:16:43 -0800370 }
371
372 function newDim(_dim_) {
373 dim = _dim_;
374 }
375
376 return {
377 initModel: initModel,
378 newDim: newDim,
379
380 positionNode: positionNode,
381 createDeviceNode: createDeviceNode,
382 createHostNode: createHostNode,
383 createHostLink: createHostLink,
384 createLink: createLink,
385 coordFromLngLat: coordFromLngLat,
386 lngLatFromCoord: lngLatFromCoord,
Simon Huntdc6adea2015-02-09 22:29:36 -0800387 findLink: findLink,
388 findLinkById: findLinkById,
389 findDevices: findDevices,
390 findAttachedHosts: findAttachedHosts,
391 findAttachedLinks: findAttachedLinks
Simon Hunt3a6eec02015-02-09 21:16:43 -0800392 }
393 }]);
394}());