Simon Hunt

GUI -- Major rework to link processing so that we consolidate links A->B and B->…

…A into a single backing object.
- added blue glow to ONOS instance when showing switch affinity.

Change-Id: Ia2a52d9d0571bc8c5eed964c85862f5798c7c5db
1 +{
2 + "event": "addLink",
3 + "payload": {
4 + "id": "of:0000ffffffff0008/10-of:0000ffffffff0003/20",
5 + "type": "direct",
6 + "linkWidth": 2,
7 + "src": "of:0000ffffffff0008",
8 + "srcPort": "10",
9 + "dst": "of:0000ffffffff0003",
10 + "dstPort": "20",
11 + "props" : {
12 + "BW": "90 Gb"
13 + }
14 + }
15 +}
1 +{
2 + "event": "addHost",
3 + "payload": {
4 + "id": "0E:2A:69:30:13:88/-1",
5 + "ingress": "0E:2A:69:30:13:88/-1/0-of:0000ffffffff0003/1",
6 + "egress": "of:0000ffffffff0003/1-0E:2A:69:30:13:88/-1/0",
7 + "cp": {
8 + "device": "of:0000ffffffff0003",
9 + "port": 1
10 + },
11 + "labels": [
12 + "Host-A",
13 + "0E:2A:69:30:13:88"
14 + ],
15 + "props": {}
16 + }
17 +}
1 +{
2 + "event": "addHost",
3 + "payload": {
4 + "id": "0E:2A:69:30:13:89/-1",
5 + "ingress": "0E:2A:69:30:13:89/-1/0-of:0000ffffffff0007/1",
6 + "egress": "of:0000ffffffff0007/1-0E:2A:69:30:13:89/-1/0",
7 + "cp": {
8 + "device": "of:0000ffffffff0007",
9 + "port": 1
10 + },
11 + "labels": [
12 + "Host-B",
13 + "0E:2A:69:30:13:89"
14 + ],
15 + "props": {}
16 + }
17 +}
1 +{
2 + "event": "addHost",
3 + "payload": {
4 + "id": "0E:2A:69:30:13:8A/-1",
5 + "ingress": "0E:2A:69:30:13:8A/-1/0-of:0000ffffffff0008/1",
6 + "egress": "of:0000ffffffff0008/1-0E:2A:69:30:13:8A/-1/0",
7 + "cp": {
8 + "device": "of:0000ffffffff0008",
9 + "port": 1
10 + },
11 + "labels": [
12 + "Host-C",
13 + "0E:2A:69:30:13:8A"
14 + ],
15 + "props": {}
16 + }
17 +}
1 +{
2 + "event": "updateLink",
3 + "payload": {
4 + "id": "of:0000ffffffff0007/10-of:0000ffffffff0008/20",
5 + "src": "of:0000ffffffff0007",
6 + "srcPort": "10",
7 + "dst": "of:0000ffffffff0008",
8 + "dstPort": "20",
9 +
10 + "type": "direct",
11 + "linkWidth": 2,
12 + "online": true,
13 + "props" : {
14 + "BW": "90 Gb"
15 + }
16 + }
17 +}
1 +{
2 + "event": "updateLink",
3 + "payload": {
4 + "id": "of:0000ffffffff0007/20-of:0000ffffffff0003/10",
5 + "src": "of:0000ffffffff0007",
6 + "srcPort": "20",
7 + "dst": "of:0000ffffffff0003",
8 + "dstPort": "10",
9 +
10 + "type": "direct",
11 + "linkWidth": 6,
12 + "online": true,
13 + "props" : {
14 + "BW": "90 Gb"
15 + }
16 + }
17 +}
1 +{
2 + "event": "removeLink",
3 + "payload": {
4 + "id": "of:0000ffffffff0007/20-of:0000ffffffff0003/10",
5 + "type": "direct",
6 + "linkWidth": 2,
7 + "src": "of:0000ffffffff0007",
8 + "srcPort": "20",
9 + "dst": "of:0000ffffffff0003",
10 + "dstPort": "10",
11 + "props" : {
12 + "BW": "90 Gb"
13 + }
14 + }
15 +}
1 +{
2 + "event": "removeLink",
3 + "payload": {
4 + "id": "of:0000ffffffff0003/10-of:0000ffffffff0007/20",
5 + "type": "direct",
6 + "linkWidth": 2,
7 + "src": "of:0000ffffffff0003",
8 + "srcPort": "10",
9 + "dst": "of:0000ffffffff0007",
10 + "dstPort": "20",
11 + "props" : {
12 + "BW": "90 Gb"
13 + }
14 + }
15 +}
1 +{
2 + "event": "addInstance",
3 + "payload": {
4 + "id": "local",
5 + "online": true,
6 + "labels": [
7 + "local",
8 + "127.0.0.1"
9 + ]
10 + }
11 +}
1 +{
2 + "event": "addDevice",
3 + "payload": {
4 + "id": "of:0000ffffffff0003",
5 + "type": "switch",
6 + "online": true,
7 + "master": "local",
8 + "labels": [
9 + "0000ffffffff0003",
10 + "FF:FF:FF:FF:00:03",
11 + "sw-3"
12 + ],
13 + "metaUi": {
14 + "x": 282,
15 + "y": 503
16 + }
17 + }
18 +}
1 +{
2 + "event": "addDevice",
3 + "payload": {
4 + "id": "of:0000ffffffff0007",
5 + "type": "switch",
6 + "online": true,
7 + "master": "local",
8 + "labels": [
9 + "0000ffffffff0007",
10 + "FF:FF:FF:FF:00:07",
11 + "sw-7"
12 + ],
13 + "metaUi": {
14 + "x": 530,
15 + "y": 330
16 + }
17 + }
18 +}
1 +{
2 + "event": "addDevice",
3 + "payload": {
4 + "id": "of:0000ffffffff0008",
5 + "type": "switch",
6 + "online": true,
7 + "master": "local",
8 + "labels": [
9 + "0000ffffffff0008",
10 + "FF:FF:FF:FF:00:08",
11 + "sw-8"
12 + ],
13 + "metaUi": {
14 + "x": 734,
15 + "y": 477
16 + }
17 + }
18 +}
1 +{
2 + "event": "addLink",
3 + "payload": {
4 + "id": "of:0000ffffffff0007/10-of:0000ffffffff0008/20",
5 + "type": "direct",
6 + "linkWidth": 2,
7 + "src": "of:0000ffffffff0007",
8 + "srcPort": "10",
9 + "dst": "of:0000ffffffff0008",
10 + "dstPort": "20",
11 + "props" : {
12 + "BW": "90 Gb"
13 + }
14 + }
15 +}
1 +{
2 + "event": "addLink",
3 + "payload": {
4 + "id": "of:0000ffffffff0008/20-of:0000ffffffff0007/10",
5 + "type": "direct",
6 + "linkWidth": 2,
7 + "src": "of:0000ffffffff0008",
8 + "srcPort": "20",
9 + "dst": "of:0000ffffffff0007",
10 + "dstPort": "10",
11 + "props" : {
12 + "BW": "90 Gb"
13 + }
14 + }
15 +}
1 +{
2 + "event": "addLink",
3 + "payload": {
4 + "id": "of:0000ffffffff0003/10-of:0000ffffffff0007/20",
5 + "type": "direct",
6 + "linkWidth": 2,
7 + "src": "of:0000ffffffff0003",
8 + "srcPort": "10",
9 + "dst": "of:0000ffffffff0007",
10 + "dstPort": "20",
11 + "props" : {
12 + "BW": "90 Gb"
13 + }
14 + }
15 +}
1 +{
2 + "event": "addLink",
3 + "payload": {
4 + "id": "of:0000ffffffff0007/20-of:0000ffffffff0003/10",
5 + "type": "direct",
6 + "linkWidth": 2,
7 + "src": "of:0000ffffffff0007",
8 + "srcPort": "20",
9 + "dst": "of:0000ffffffff0003",
10 + "dstPort": "10",
11 + "props" : {
12 + "BW": "90 Gb"
13 + }
14 + }
15 +}
1 +{
2 + "event": "addLink",
3 + "payload": {
4 + "id": "of:0000ffffffff0003/20-of:0000ffffffff0008/10",
5 + "type": "direct",
6 + "linkWidth": 2,
7 + "src": "of:0000ffffffff0003",
8 + "srcPort": "20",
9 + "dst": "of:0000ffffffff0008",
10 + "dstPort": "10",
11 + "props" : {
12 + "BW": "90 Gb"
13 + }
14 + }
15 +}
1 +{
2 + "comments": [
3 + "Stepping through link events"
4 + ],
5 + "title": "Process Link Events Scenario",
6 + "params": {
7 + "lastAuto": 13
8 + },
9 + "description": [
10 + "Develop link event handling.",
11 + "",
12 + "Press 'S' to load initial events.",
13 + "",
14 + "Press spacebar to complete the scenario..."
15 + ]
16 +}
...@@ -262,6 +262,7 @@ ...@@ -262,6 +262,7 @@
262 } 262 }
263 #topo-oibox .onosInst.mastership.affinity { 263 #topo-oibox .onosInst.mastership.affinity {
264 opacity: 1.0; 264 opacity: 1.0;
265 + box-shadow: 0px 2px 8px #33e;
265 } 266 }
266 267
267 268
......
...@@ -339,13 +339,16 @@ ...@@ -339,13 +339,16 @@
339 link: { 339 link: {
340 hostLink: 'pkt', 340 hostLink: 'pkt',
341 direct: 'pkt', 341 direct: 'pkt',
342 + indirect: '',
343 + tunnel: '',
342 optical: 'opt' 344 optical: 'opt'
343 } 345 }
344 }; 346 };
345 347
346 function inLayer(d, layer) { 348 function inLayer(d, layer) {
347 - var look = layerLookup[d.class], 349 + var type = d.class === 'link' ? d.type() : d.type,
348 - lyr = look && look[d.type]; 350 + look = layerLookup[d.class],
351 + lyr = look && look[type];
349 return lyr === layer; 352 return lyr === layer;
350 } 353 }
351 354
...@@ -408,6 +411,115 @@ ...@@ -408,6 +411,115 @@
408 }); 411 });
409 } 412 }
410 413
414 + function makeNodeKey(d, what) {
415 + var port = what + 'Port';
416 + return d[what] + '/' + d[port];
417 + }
418 +
419 + function makeLinkKey(d, flipped) {
420 + var one = flipped ? makeNodeKey(d, 'dst') : makeNodeKey(d, 'src'),
421 + two = flipped ? makeNodeKey(d, 'src') : makeNodeKey(d, 'dst');
422 + return one + '-' + two;
423 + }
424 +
425 + function findLink(linkData, op) {
426 + var key = makeLinkKey(linkData),
427 + keyrev = makeLinkKey(linkData, 1),
428 + link = network.lookup[key],
429 + linkRev = network.lookup[keyrev],
430 + result = {},
431 + ldata = link || linkRev,
432 + rawLink;
433 +
434 + if (op === 'add') {
435 + if (link) {
436 + // trying to add a link that we already know about
437 + result.ldata = link;
438 + result.badLogic = 'addLink: link already added';
439 +
440 + } else if (linkRev) {
441 + // we found the reverse of the link to be added
442 + result.ldata = linkRev;
443 + if (linkRev.fromTarget) {
444 + result.badLogic = 'addLink: link already added';
445 + }
446 + }
447 + } else if (op === 'update') {
448 + if (!ldata) {
449 + result.badLogic = 'updateLink: link not found';
450 + } else {
451 + rawLink = link ? ldata.fromSource : ldata.fromTarget;
452 + result.updateWith = function (data) {
453 + $.extend(rawLink, data);
454 + restyleLinkElement(ldata);
455 + }
456 + }
457 + } else if (op === 'remove') {
458 + if (!ldata) {
459 + result.badLogic = 'removeLink: link not found';
460 + } else {
461 + rawLink = link ? ldata.fromSource : ldata.fromTarget;
462 +
463 + if (!rawLink) {
464 + result.badLogic = 'removeLink: link not found';
465 +
466 + } else {
467 + result.removeRawLink = function () {
468 + if (link) {
469 + // remove fromSource
470 + ldata.fromSource = null;
471 + if (ldata.fromTarget) {
472 + // promote target into source position
473 + ldata.fromSource = ldata.fromTarget;
474 + ldata.fromTarget = null;
475 + ldata.key = keyrev;
476 + delete network.lookup[key];
477 + network.lookup[keyrev] = ldata;
478 + }
479 + } else {
480 + // remove fromTarget
481 + ldata.fromTarget = null;
482 + }
483 + if (ldata.fromSource) {
484 + restyleLinkElement(ldata);
485 + } else {
486 + removeLinkElement(ldata);
487 + }
488 + }
489 + }
490 + }
491 + }
492 + return result;
493 + }
494 +
495 + function addLinkUpdate(ldata, link) {
496 + // add link event, but we already have the reverse link installed
497 + ldata.fromTarget = link;
498 + restyleLinkElement(ldata);
499 + }
500 +
501 + var allLinkTypes = 'direct indirect optical tunnel',
502 + defaultLinkType = 'direct';
503 +
504 + function restyleLinkElement(ldata) {
505 + // this fn's job is to look at raw links and decide what svg classes
506 + // need to be applied to the line element in the DOM
507 + var el = ldata.el,
508 + type = ldata.type(),
509 + lw = ldata.linkWidth(),
510 + online = ldata.online();
511 +
512 + el.classed('link', true);
513 + el.classed('inactive', !online);
514 + el.classed(allLinkTypes, false);
515 + if (type) {
516 + el.classed(type, true);
517 + }
518 + el.transition()
519 + .duration(1000)
520 + .attr('stroke-width', linkScale(lw))
521 + .attr('stroke', '#666'); // TODO: remove explicit stroke (use CSS)
522 + }
411 523
412 // ============================== 524 // ==============================
413 // Event handlers for server-pushed events 525 // Event handlers for server-pushed events
...@@ -465,10 +577,26 @@ ...@@ -465,10 +577,26 @@
465 function addLink(data) { 577 function addLink(data) {
466 evTrace(data); 578 evTrace(data);
467 var link = data.payload, 579 var link = data.payload,
468 - lnk = createLink(link); 580 + result = findLink(link, 'add'),
469 - if (lnk) { 581 + bad = result.badLogic,
470 - network.links.push(lnk); 582 + ldata = result.ldata;
471 - network.lookup[lnk.id] = lnk; 583 +
584 + if (bad) {
585 + logicError(bad + ': ' + link.id);
586 + return;
587 + }
588 +
589 + if (ldata) {
590 + // we already have a backing store link for src/dst nodes
591 + addLinkUpdate(ldata, link);
592 + return;
593 + }
594 +
595 + // no backing store link yet
596 + ldata = createLink(link);
597 + if (ldata) {
598 + network.links.push(ldata);
599 + network.lookup[ldata.key] = ldata;
472 updateLinks(); 600 updateLinks();
473 network.force.start(); 601 network.force.start();
474 } 602 }
...@@ -511,14 +639,13 @@ ...@@ -511,14 +639,13 @@
511 function updateLink(data) { 639 function updateLink(data) {
512 evTrace(data); 640 evTrace(data);
513 var link = data.payload, 641 var link = data.payload,
514 - id = link.id, 642 + result = findLink(link, 'update'),
515 - linkData = network.lookup[id]; 643 + bad = result.badLogic;
516 - if (linkData) { 644 + if (bad) {
517 - $.extend(linkData, link); 645 + logicError(bad + ': ' + link.id);
518 - updateLinkState(linkData); 646 + return;
519 - } else {
520 - logicError('updateLink lookup fail. ID = "' + id + '"');
521 } 647 }
648 + result.updateWith(link);
522 } 649 }
523 650
524 function updateHost(data) { 651 function updateHost(data) {
...@@ -538,13 +665,13 @@ ...@@ -538,13 +665,13 @@
538 function removeLink(data) { 665 function removeLink(data) {
539 evTrace(data); 666 evTrace(data);
540 var link = data.payload, 667 var link = data.payload,
541 - id = link.id, 668 + result = findLink(link, 'remove'),
542 - linkData = network.lookup[id]; 669 + bad = result.badLogic;
543 - if (linkData) { 670 + if (bad) {
544 - removeLinkElement(linkData); 671 + logicError(bad + ': ' + link.id);
545 - } else { 672 + return;
546 - logicError('removeLink lookup fail. ID = "' + id + '"');
547 } 673 }
674 + result.removeRawLink();
548 } 675 }
549 676
550 function removeHost(data) { 677 function removeHost(data) {
...@@ -805,11 +932,13 @@ ...@@ -805,11 +932,13 @@
805 932
806 // Synthesize link ... 933 // Synthesize link ...
807 $.extend(lnk, { 934 $.extend(lnk, {
808 - id: id, 935 + key: id,
809 class: 'link', 936 class: 'link',
810 - type: 'hostLink', 937 +
811 - svgClass: 'link hostLink', 938 + type: function () { return 'hostLink'; },
812 - linkWidth: 1 939 + // TODO: ideally, we should see if our edge switch is online...
940 + online: function () { return true; },
941 + linkWidth: function () { return 1; }
813 }); 942 });
814 return lnk; 943 return lnk;
815 } 944 }
...@@ -822,10 +951,29 @@ ...@@ -822,10 +951,29 @@
822 return null; 951 return null;
823 } 952 }
824 953
825 - // merge in remaining data 954 + $.extend(lnk, {
826 - $.extend(lnk, link, { 955 + key: link.id,
827 class: 'link', 956 class: 'link',
828 - svgClass: (type ? 'link ' + type : 'link') 957 + fromSource: link,
958 +
959 + // functions to aggregate dual link state
960 + type: function () {
961 + var s = lnk.fromSource,
962 + t = lnk.fromTarget;
963 + return (s && s.type) || (t && t.type) || defaultLinkType;
964 + },
965 + online: function () {
966 + var s = lnk.fromSource,
967 + t = lnk.fromTarget;
968 + return (s && s.online) || (t && t.online);
969 + },
970 + linkWidth: function () {
971 + var s = lnk.fromSource,
972 + t = lnk.fromTarget,
973 + ws = (s && s.linkWidth) || 0,
974 + wt = (t && t.linkWidth) || 0;
975 + return Math.max(ws, wt);
976 + }
829 }); 977 });
830 return lnk; 978 return lnk;
831 } 979 }
...@@ -836,17 +984,9 @@ ...@@ -836,17 +984,9 @@
836 .range([widthRatio, 12 * widthRatio]) 984 .range([widthRatio, 12 * widthRatio])
837 .clamp(true); 985 .clamp(true);
838 986
839 - function updateLinkWidth (d) {
840 - // TODO: watch out for .showPath/.showTraffic classes
841 - d.el.transition()
842 - .duration(1000)
843 - .attr('stroke-width', linkScale(d.linkWidth));
844 - }
845 -
846 -
847 function updateLinks() { 987 function updateLinks() {
848 link = linkG.selectAll('.link') 988 link = linkG.selectAll('.link')
849 - .data(network.links, function (d) { return d.id; }); 989 + .data(network.links, function (d) { return d.key; });
850 990
851 // operate on existing links, if necessary 991 // operate on existing links, if necessary
852 // link .foo() .bar() ... 992 // link .foo() .bar() ...
...@@ -855,19 +995,12 @@ ...@@ -855,19 +995,12 @@
855 var entering = link.enter() 995 var entering = link.enter()
856 .append('line') 996 .append('line')
857 .attr({ 997 .attr({
858 - class: function (d) { return d.svgClass; },
859 x1: function (d) { return d.x1; }, 998 x1: function (d) { return d.x1; },
860 y1: function (d) { return d.y1; }, 999 y1: function (d) { return d.y1; },
861 x2: function (d) { return d.x2; }, 1000 x2: function (d) { return d.x2; },
862 y2: function (d) { return d.y2; }, 1001 y2: function (d) { return d.y2; },
863 stroke: config.topo.linkInColor, 1002 stroke: config.topo.linkInColor,
864 'stroke-width': config.topo.linkInWidth 1003 'stroke-width': config.topo.linkInWidth
865 - })
866 - .classed('inactive', function(d) { return !d.online; })
867 - .transition().duration(1000)
868 - .attr({
869 - 'stroke-width': function (d) { return linkScale(d.linkWidth); },
870 - stroke: '#666' // TODO: remove explicit stroke, rather...
871 }); 1004 });
872 1005
873 // augment links 1006 // augment links
...@@ -875,6 +1008,7 @@ ...@@ -875,6 +1008,7 @@
875 var link = d3.select(this); 1008 var link = d3.select(this);
876 // provide ref to element selection from backing data.... 1009 // provide ref to element selection from backing data....
877 d.el = link; 1010 d.el = link;
1011 + restyleLinkElement(d);
878 1012
879 // TODO: add src/dst port labels etc. 1013 // TODO: add src/dst port labels etc.
880 }); 1014 });
...@@ -1240,9 +1374,9 @@ ...@@ -1240,9 +1374,9 @@
1240 // TODO: device node exits 1374 // TODO: device node exits
1241 } 1375 }
1242 1376
1243 - function find(id, array) { 1377 + function find(key, array) {
1244 for (var idx = 0, n = array.length; idx < n; idx++) { 1378 for (var idx = 0, n = array.length; idx < n; idx++) {
1245 - if (array[idx].id === id) { 1379 + if (array[idx].key === key) {
1246 return idx; 1380 return idx;
1247 } 1381 }
1248 } 1382 }
...@@ -1250,15 +1384,17 @@ ...@@ -1250,15 +1384,17 @@
1250 } 1384 }
1251 1385
1252 function removeLinkElement(linkData) { 1386 function removeLinkElement(linkData) {
1253 - // remove from lookup cache 1387 + var idx = find(linkData.key, network.links),
1254 - delete network.lookup[linkData.id]; 1388 + removed;
1389 + if (idx >=0) {
1255 // remove from links array 1390 // remove from links array
1256 - var idx = find(linkData.id, network.links); 1391 + removed = network.links.splice(idx, 1);
1257 - network.links.splice(idx, 1); 1392 + // remove from lookup cache
1258 - // remove from SVG 1393 + delete network.lookup[removed[0].key];
1259 updateLinks(); 1394 updateLinks();
1260 network.force.resume(); 1395 network.force.resume();
1261 } 1396 }
1397 + }
1262 1398
1263 function removeHostElement(hostData) { 1399 function removeHostElement(hostData) {
1264 // first, remove associated hostLink... 1400 // first, remove associated hostLink...
......