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
{
"event": "addLink",
"payload": {
"id": "of:0000ffffffff0008/10-of:0000ffffffff0003/20",
"type": "direct",
"linkWidth": 2,
"src": "of:0000ffffffff0008",
"srcPort": "10",
"dst": "of:0000ffffffff0003",
"dstPort": "20",
"props" : {
"BW": "90 Gb"
}
}
}
{
"event": "addHost",
"payload": {
"id": "0E:2A:69:30:13:88/-1",
"ingress": "0E:2A:69:30:13:88/-1/0-of:0000ffffffff0003/1",
"egress": "of:0000ffffffff0003/1-0E:2A:69:30:13:88/-1/0",
"cp": {
"device": "of:0000ffffffff0003",
"port": 1
},
"labels": [
"Host-A",
"0E:2A:69:30:13:88"
],
"props": {}
}
}
{
"event": "addHost",
"payload": {
"id": "0E:2A:69:30:13:89/-1",
"ingress": "0E:2A:69:30:13:89/-1/0-of:0000ffffffff0007/1",
"egress": "of:0000ffffffff0007/1-0E:2A:69:30:13:89/-1/0",
"cp": {
"device": "of:0000ffffffff0007",
"port": 1
},
"labels": [
"Host-B",
"0E:2A:69:30:13:89"
],
"props": {}
}
}
{
"event": "addHost",
"payload": {
"id": "0E:2A:69:30:13:8A/-1",
"ingress": "0E:2A:69:30:13:8A/-1/0-of:0000ffffffff0008/1",
"egress": "of:0000ffffffff0008/1-0E:2A:69:30:13:8A/-1/0",
"cp": {
"device": "of:0000ffffffff0008",
"port": 1
},
"labels": [
"Host-C",
"0E:2A:69:30:13:8A"
],
"props": {}
}
}
{
"event": "updateLink",
"payload": {
"id": "of:0000ffffffff0007/10-of:0000ffffffff0008/20",
"src": "of:0000ffffffff0007",
"srcPort": "10",
"dst": "of:0000ffffffff0008",
"dstPort": "20",
"type": "direct",
"linkWidth": 2,
"online": true,
"props" : {
"BW": "90 Gb"
}
}
}
{
"event": "updateLink",
"payload": {
"id": "of:0000ffffffff0007/20-of:0000ffffffff0003/10",
"src": "of:0000ffffffff0007",
"srcPort": "20",
"dst": "of:0000ffffffff0003",
"dstPort": "10",
"type": "direct",
"linkWidth": 6,
"online": true,
"props" : {
"BW": "90 Gb"
}
}
}
{
"event": "removeLink",
"payload": {
"id": "of:0000ffffffff0007/20-of:0000ffffffff0003/10",
"type": "direct",
"linkWidth": 2,
"src": "of:0000ffffffff0007",
"srcPort": "20",
"dst": "of:0000ffffffff0003",
"dstPort": "10",
"props" : {
"BW": "90 Gb"
}
}
}
{
"event": "removeLink",
"payload": {
"id": "of:0000ffffffff0003/10-of:0000ffffffff0007/20",
"type": "direct",
"linkWidth": 2,
"src": "of:0000ffffffff0003",
"srcPort": "10",
"dst": "of:0000ffffffff0007",
"dstPort": "20",
"props" : {
"BW": "90 Gb"
}
}
}
{
"event": "addInstance",
"payload": {
"id": "local",
"online": true,
"labels": [
"local",
"127.0.0.1"
]
}
}
{
"event": "addDevice",
"payload": {
"id": "of:0000ffffffff0003",
"type": "switch",
"online": true,
"master": "local",
"labels": [
"0000ffffffff0003",
"FF:FF:FF:FF:00:03",
"sw-3"
],
"metaUi": {
"x": 282,
"y": 503
}
}
}
{
"event": "addDevice",
"payload": {
"id": "of:0000ffffffff0007",
"type": "switch",
"online": true,
"master": "local",
"labels": [
"0000ffffffff0007",
"FF:FF:FF:FF:00:07",
"sw-7"
],
"metaUi": {
"x": 530,
"y": 330
}
}
}
{
"event": "addDevice",
"payload": {
"id": "of:0000ffffffff0008",
"type": "switch",
"online": true,
"master": "local",
"labels": [
"0000ffffffff0008",
"FF:FF:FF:FF:00:08",
"sw-8"
],
"metaUi": {
"x": 734,
"y": 477
}
}
}
{
"event": "addLink",
"payload": {
"id": "of:0000ffffffff0007/10-of:0000ffffffff0008/20",
"type": "direct",
"linkWidth": 2,
"src": "of:0000ffffffff0007",
"srcPort": "10",
"dst": "of:0000ffffffff0008",
"dstPort": "20",
"props" : {
"BW": "90 Gb"
}
}
}
{
"event": "addLink",
"payload": {
"id": "of:0000ffffffff0008/20-of:0000ffffffff0007/10",
"type": "direct",
"linkWidth": 2,
"src": "of:0000ffffffff0008",
"srcPort": "20",
"dst": "of:0000ffffffff0007",
"dstPort": "10",
"props" : {
"BW": "90 Gb"
}
}
}
{
"event": "addLink",
"payload": {
"id": "of:0000ffffffff0003/10-of:0000ffffffff0007/20",
"type": "direct",
"linkWidth": 2,
"src": "of:0000ffffffff0003",
"srcPort": "10",
"dst": "of:0000ffffffff0007",
"dstPort": "20",
"props" : {
"BW": "90 Gb"
}
}
}
{
"event": "addLink",
"payload": {
"id": "of:0000ffffffff0007/20-of:0000ffffffff0003/10",
"type": "direct",
"linkWidth": 2,
"src": "of:0000ffffffff0007",
"srcPort": "20",
"dst": "of:0000ffffffff0003",
"dstPort": "10",
"props" : {
"BW": "90 Gb"
}
}
}
{
"event": "addLink",
"payload": {
"id": "of:0000ffffffff0003/20-of:0000ffffffff0008/10",
"type": "direct",
"linkWidth": 2,
"src": "of:0000ffffffff0003",
"srcPort": "20",
"dst": "of:0000ffffffff0008",
"dstPort": "10",
"props" : {
"BW": "90 Gb"
}
}
}
{
"comments": [
"Stepping through link events"
],
"title": "Process Link Events Scenario",
"params": {
"lastAuto": 13
},
"description": [
"Develop link event handling.",
"",
"Press 'S' to load initial events.",
"",
"Press spacebar to complete the scenario..."
]
}
......@@ -262,6 +262,7 @@
}
#topo-oibox .onosInst.mastership.affinity {
opacity: 1.0;
box-shadow: 0px 2px 8px #33e;
}
......
......@@ -339,13 +339,16 @@
link: {
hostLink: 'pkt',
direct: 'pkt',
indirect: '',
tunnel: '',
optical: 'opt'
}
};
function inLayer(d, layer) {
var look = layerLookup[d.class],
lyr = look && look[d.type];
var type = d.class === 'link' ? d.type() : d.type,
look = layerLookup[d.class],
lyr = look && look[type];
return lyr === layer;
}
......@@ -408,6 +411,115 @@
});
}
function makeNodeKey(d, what) {
var port = what + 'Port';
return d[what] + '/' + d[port];
}
function makeLinkKey(d, flipped) {
var one = flipped ? makeNodeKey(d, 'dst') : makeNodeKey(d, 'src'),
two = flipped ? makeNodeKey(d, 'src') : makeNodeKey(d, 'dst');
return one + '-' + two;
}
function findLink(linkData, op) {
var key = makeLinkKey(linkData),
keyrev = makeLinkKey(linkData, 1),
link = network.lookup[key],
linkRev = network.lookup[keyrev],
result = {},
ldata = link || linkRev,
rawLink;
if (op === 'add') {
if (link) {
// trying to add a link that we already know about
result.ldata = link;
result.badLogic = 'addLink: link already added';
} else if (linkRev) {
// we found the reverse of the link to be added
result.ldata = linkRev;
if (linkRev.fromTarget) {
result.badLogic = 'addLink: link already added';
}
}
} else if (op === 'update') {
if (!ldata) {
result.badLogic = 'updateLink: link not found';
} else {
rawLink = link ? ldata.fromSource : ldata.fromTarget;
result.updateWith = function (data) {
$.extend(rawLink, data);
restyleLinkElement(ldata);
}
}
} else if (op === 'remove') {
if (!ldata) {
result.badLogic = 'removeLink: link not found';
} else {
rawLink = link ? ldata.fromSource : ldata.fromTarget;
if (!rawLink) {
result.badLogic = 'removeLink: link not found';
} else {
result.removeRawLink = function () {
if (link) {
// remove fromSource
ldata.fromSource = null;
if (ldata.fromTarget) {
// promote target into source position
ldata.fromSource = ldata.fromTarget;
ldata.fromTarget = null;
ldata.key = keyrev;
delete network.lookup[key];
network.lookup[keyrev] = ldata;
}
} else {
// remove fromTarget
ldata.fromTarget = null;
}
if (ldata.fromSource) {
restyleLinkElement(ldata);
} else {
removeLinkElement(ldata);
}
}
}
}
}
return result;
}
function addLinkUpdate(ldata, link) {
// add link event, but we already have the reverse link installed
ldata.fromTarget = link;
restyleLinkElement(ldata);
}
var allLinkTypes = 'direct indirect optical tunnel',
defaultLinkType = 'direct';
function restyleLinkElement(ldata) {
// this fn's job is to look at raw links and decide what svg classes
// need to be applied to the line element in the DOM
var el = ldata.el,
type = ldata.type(),
lw = ldata.linkWidth(),
online = ldata.online();
el.classed('link', true);
el.classed('inactive', !online);
el.classed(allLinkTypes, false);
if (type) {
el.classed(type, true);
}
el.transition()
.duration(1000)
.attr('stroke-width', linkScale(lw))
.attr('stroke', '#666'); // TODO: remove explicit stroke (use CSS)
}
// ==============================
// Event handlers for server-pushed events
......@@ -465,10 +577,26 @@
function addLink(data) {
evTrace(data);
var link = data.payload,
lnk = createLink(link);
if (lnk) {
network.links.push(lnk);
network.lookup[lnk.id] = lnk;
result = findLink(link, 'add'),
bad = result.badLogic,
ldata = result.ldata;
if (bad) {
logicError(bad + ': ' + link.id);
return;
}
if (ldata) {
// we already have a backing store link for src/dst nodes
addLinkUpdate(ldata, link);
return;
}
// no backing store link yet
ldata = createLink(link);
if (ldata) {
network.links.push(ldata);
network.lookup[ldata.key] = ldata;
updateLinks();
network.force.start();
}
......@@ -511,14 +639,13 @@
function updateLink(data) {
evTrace(data);
var link = data.payload,
id = link.id,
linkData = network.lookup[id];
if (linkData) {
$.extend(linkData, link);
updateLinkState(linkData);
} else {
logicError('updateLink lookup fail. ID = "' + id + '"');
result = findLink(link, 'update'),
bad = result.badLogic;
if (bad) {
logicError(bad + ': ' + link.id);
return;
}
result.updateWith(link);
}
function updateHost(data) {
......@@ -538,13 +665,13 @@
function removeLink(data) {
evTrace(data);
var link = data.payload,
id = link.id,
linkData = network.lookup[id];
if (linkData) {
removeLinkElement(linkData);
} else {
logicError('removeLink lookup fail. ID = "' + id + '"');
result = findLink(link, 'remove'),
bad = result.badLogic;
if (bad) {
logicError(bad + ': ' + link.id);
return;
}
result.removeRawLink();
}
function removeHost(data) {
......@@ -805,11 +932,13 @@
// Synthesize link ...
$.extend(lnk, {
id: id,
key: id,
class: 'link',
type: 'hostLink',
svgClass: 'link hostLink',
linkWidth: 1
type: function () { return 'hostLink'; },
// TODO: ideally, we should see if our edge switch is online...
online: function () { return true; },
linkWidth: function () { return 1; }
});
return lnk;
}
......@@ -822,10 +951,29 @@
return null;
}
// merge in remaining data
$.extend(lnk, link, {
$.extend(lnk, {
key: link.id,
class: 'link',
svgClass: (type ? 'link ' + type : 'link')
fromSource: link,
// functions to aggregate dual link state
type: function () {
var s = lnk.fromSource,
t = lnk.fromTarget;
return (s && s.type) || (t && t.type) || defaultLinkType;
},
online: function () {
var s = lnk.fromSource,
t = lnk.fromTarget;
return (s && s.online) || (t && t.online);
},
linkWidth: function () {
var s = lnk.fromSource,
t = lnk.fromTarget,
ws = (s && s.linkWidth) || 0,
wt = (t && t.linkWidth) || 0;
return Math.max(ws, wt);
}
});
return lnk;
}
......@@ -836,17 +984,9 @@
.range([widthRatio, 12 * widthRatio])
.clamp(true);
function updateLinkWidth (d) {
// TODO: watch out for .showPath/.showTraffic classes
d.el.transition()
.duration(1000)
.attr('stroke-width', linkScale(d.linkWidth));
}
function updateLinks() {
link = linkG.selectAll('.link')
.data(network.links, function (d) { return d.id; });
.data(network.links, function (d) { return d.key; });
// operate on existing links, if necessary
// link .foo() .bar() ...
......@@ -855,19 +995,12 @@
var entering = link.enter()
.append('line')
.attr({
class: function (d) { return d.svgClass; },
x1: function (d) { return d.x1; },
y1: function (d) { return d.y1; },
x2: function (d) { return d.x2; },
y2: function (d) { return d.y2; },
stroke: config.topo.linkInColor,
'stroke-width': config.topo.linkInWidth
})
.classed('inactive', function(d) { return !d.online; })
.transition().duration(1000)
.attr({
'stroke-width': function (d) { return linkScale(d.linkWidth); },
stroke: '#666' // TODO: remove explicit stroke, rather...
});
// augment links
......@@ -875,6 +1008,7 @@
var link = d3.select(this);
// provide ref to element selection from backing data....
d.el = link;
restyleLinkElement(d);
// TODO: add src/dst port labels etc.
});
......@@ -1240,9 +1374,9 @@
// TODO: device node exits
}
function find(id, array) {
function find(key, array) {
for (var idx = 0, n = array.length; idx < n; idx++) {
if (array[idx].id === id) {
if (array[idx].key === key) {
return idx;
}
}
......@@ -1250,14 +1384,16 @@
}
function removeLinkElement(linkData) {
// remove from lookup cache
delete network.lookup[linkData.id];
// remove from links array
var idx = find(linkData.id, network.links);
network.links.splice(idx, 1);
// remove from SVG
updateLinks();
network.force.resume();
var idx = find(linkData.key, network.links),
removed;
if (idx >=0) {
// remove from links array
removed = network.links.splice(idx, 1);
// remove from lookup cache
delete network.lookup[removed[0].key];
updateLinks();
network.force.resume();
}
}
function removeHostElement(hostData) {
......