blob: e1f89e9730dc54d5a8c81a5d97dbed8f344fa97e [file] [log] [blame]
Paul Greyson127d7fb2013-03-25 23:39:20 -07001/*global d3, document∆*/
Paul Greyson740bdaf2013-03-18 16:10:48 -07002
Paul Greyson6d9ed862013-03-23 17:37:15 -07003d3.selection.prototype.moveToFront = function() {
4 return this.each(function(){
5 this.parentNode.appendChild(this);
6 });
7};
Paul Greysond1a22d92013-03-19 12:15:19 -07008
Paul Greyson127d7fb2013-03-25 23:39:20 -07009var line = d3.svg.line()
10 .x(function(d) {
11 return d.x;
12 })
13 .y(function(d) {
14 return d.y;
15 });
16
Paul Greyson56378ed2013-03-26 23:17:36 -070017var model;
Paul Greyson127d7fb2013-03-25 23:39:20 -070018var svg, selectedFlowsView;
Paul Greyson56378ed2013-03-26 23:17:36 -070019var updateTopology;
Paul Greyson40c8a592013-03-27 14:10:33 -070020var pendingLinks = {};
Paul Greyson127d7fb2013-03-25 23:39:20 -070021
Paul Greysond1a22d92013-03-19 12:15:19 -070022var colors = [
Paul Greyson3e142162013-03-19 13:56:17 -070023 'color1',
24 'color2',
25 'color3',
26 'color4',
27 'color5',
28 'color6',
29 'color7',
30 'color8',
31 'color9',
32 'color10',
33 'color11',
Paul Greyson127d7fb2013-03-25 23:39:20 -070034 'color12'
35];
Paul Greyson01a5dff2013-03-19 15:50:14 -070036colors.reverse();
Paul Greysond1a22d92013-03-19 12:15:19 -070037
38var controllerColorMap = {};
39
Paul Greyson084779b2013-03-27 13:55:49 -070040function setPending(selection) {
41 selection.classed('pending', false);
42 setTimeout(function () {
43 selection.classed('pending', true);
44 })
45}
Paul Greysond1a22d92013-03-19 12:15:19 -070046
Paul Greyson740bdaf2013-03-18 16:10:48 -070047function createTopologyView() {
Paul Greyson56378ed2013-03-26 23:17:36 -070048
49 window.addEventListener('resize', function () {
50 // this is too slow. instead detect first resize event and hide the paths that have explicit matrix applied
51 // either that or is it possible to position the paths so they get the automatic transform as well?
52// updateTopology(svg, model);
53 });
54
Paul Greysonb367de22013-03-23 11:09:11 -070055 var svg = d3.select('#svg-container').append('svg:svg');
56
57 svg.append("svg:defs").append("svg:marker")
58 .attr("id", "arrow")
59 .attr("viewBox", "0 -5 10 10")
60 .attr("refX", -1)
61 .attr("markerWidth", 5)
62 .attr("markerHeight", 5)
63 .attr("orient", "auto")
64 .append("svg:path")
Paul Greyson45303ac2013-03-23 16:44:01 -070065 .attr("d", "M0,-3L10,0L0,3");
Paul Greysonb367de22013-03-23 11:09:11 -070066
67 return svg.append('svg:svg').attr('id', 'viewBox').attr('viewBox', '0 0 1000 1000').attr('preserveAspectRatio', 'none').
Paul Greyson952ccb62013-03-18 22:22:08 -070068 attr('id', 'viewbox').append('svg:g').attr('transform', 'translate(500 500)');
Paul Greyson740bdaf2013-03-18 16:10:48 -070069}
70
Paul Greyson127d7fb2013-03-25 23:39:20 -070071var selectedFlowsData = [
72 {selected: false, flow: null},
73 {selected: false, flow: null},
Paul Greyson127d7fb2013-03-25 23:39:20 -070074 {selected: false, flow: null}
75];
76
77function drawFlows() {
78 // DRAW THE FLOWS
79 var flows = d3.select('svg').selectAll('.flow').data(selectedFlowsData, function (d) {
80 return d.flow ? d.flow.flowId.value : null;
81 });
82
83 flows.enter().append("svg:path")
84 .attr('class', 'flow')
85 .attr('d', function (d) {
86 if (!d.flow) {
87 return;
88 }
89 var pts = [];
90 d.flow.dataPath.flowEntries.forEach(function (flowEntry) {
91 var s = d3.select(document.getElementById(flowEntry.dpid.value));
92 var pt = document.querySelector('svg').createSVGPoint();
93 pt.x = s.attr('x');
94 pt.y = s.attr('y');
95 pt = pt.matrixTransform(s[0][0].getCTM());
96 pts.push(pt);
97 });
98 return line(pts);
99 })
Paul Greyson421bfcd2013-03-27 22:22:09 -0700100 .attr('stroke-dasharray', '4, 10')
Paul Greyson127d7fb2013-03-25 23:39:20 -0700101 .append('svg:animate')
102 .attr('attributeName', 'stroke-dashoffset')
103 .attr('attributeType', 'xml')
104 .attr('from', '500')
105 .attr('to', '-500')
106 .attr('dur', '20s')
107 .attr('repeatCount', 'indefinite');
108
109 flows.style('visibility', function (d) {
110 if (d) {
111 return d.selected ? '' : 'hidden';
112 }
113 })
114
Paul Greyson56378ed2013-03-26 23:17:36 -0700115 // "marching ants"
116 // TODO: this will only be true if there's an iperf session running
Paul Greyson127d7fb2013-03-25 23:39:20 -0700117 flows.select('animate').attr('from', function (d) {
118 if (d.flow) {
119 if (d.selected) {
120 return '500';
121 } else {
122 return '-500';
123 }
124 }
125 });
126}
127
128function updateFlowView() {
129 selectedFlowsView.data(selectedFlowsData);
130
131 selectedFlowsView.classed('selected', function (d) {
132 if (d.flow) {
133 return d.selected;
134 }
135 });
136
137 selectedFlowsView.select('.flowId')
138 .text(function (d) {
139 if (d.flow) {
140 return d.flow.flowId.value;
141 }
142 });
143
144 selectedFlowsView.select('.srcDPID')
145 .text(function (d) {
146 if (d.flow) {
147 return d.flow.dataPath.srcPort.dpid.value;
148 }
149 });
150
151 selectedFlowsView.select('.dstDPID')
152 .text(function (d) {
153 if (d.flow) {
154 return d.flow.dataPath.dstPort.dpid.value;
155 }
156 });
157}
158
159function createFlowView() {
160 function rowEnter(d, i) {
161 var row = d3.select(this);
162
163 row.on('click', function () {
164 selectedFlowsData[i].selected = !selectedFlowsData[i].selected;
165 updateFlowView();
166 drawFlows();
167 });
168
169 row.append('div')
170 .classed('flowIndex', true)
171 .text(function () {
172 return i+1;
173 });
174
175 row.append('div')
176 .classed('flowId', true);
177
178 row.append('div')
179 .classed('srcDPID', true);
180
181 row.append('div')
182 .classed('dstDPID', true);
183
184 row.append('div')
185 .classed('iperf', true);
186 }
187
188 var flows = d3.select('#selectedFlows')
189 .selectAll('.selectedFlow')
190 .data(selectedFlowsData)
191 .enter()
192 .append('div')
193 .classed('selectedFlow', true)
194 .each(rowEnter);
195
196
197 return flows;
198}
199
Paul Greysond1a22d92013-03-19 12:15:19 -0700200function updateHeader(model) {
Paul Greysonb48943b2013-03-19 13:27:57 -0700201 d3.select('#lastUpdate').text(new Date());
Paul Greyson952ccb62013-03-18 22:22:08 -0700202 d3.select('#activeSwitches').text(model.edgeSwitches.length + model.aggregationSwitches.length + model.coreSwitches.length);
203 d3.select('#activeFlows').text(model.flows.length);
204}
205
206function toRadians (angle) {
207 return angle * (Math.PI / 180);
Paul Greyson740bdaf2013-03-18 16:10:48 -0700208}
209
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700210var widths = {
211 edge: 6,
212 aggregation: 12,
213 core: 18
214}
215
Paul Greysonc17278a2013-03-23 10:17:12 -0700216function createRingsFromModel(model) {
Paul Greyson740bdaf2013-03-18 16:10:48 -0700217 var rings = [{
218 radius: 3,
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700219 width: widths.edge,
Paul Greyson952ccb62013-03-18 22:22:08 -0700220 switches: model.edgeSwitches,
Paul Greysonde7fad52013-03-19 12:47:32 -0700221 className: 'edge',
222 angles: []
Paul Greyson740bdaf2013-03-18 16:10:48 -0700223 }, {
Paul Greysond1a22d92013-03-19 12:15:19 -0700224 radius: 2.25,
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700225 width: widths.aggregation,
Paul Greyson952ccb62013-03-18 22:22:08 -0700226 switches: model.aggregationSwitches,
Paul Greysonde7fad52013-03-19 12:47:32 -0700227 className: 'aggregation',
228 angles: []
Paul Greyson740bdaf2013-03-18 16:10:48 -0700229 }, {
Paul Greyson127d7fb2013-03-25 23:39:20 -0700230 radius: 0.75,
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700231 width: widths.core,
Paul Greyson952ccb62013-03-18 22:22:08 -0700232 switches: model.coreSwitches,
Paul Greysonde7fad52013-03-19 12:47:32 -0700233 className: 'core',
234 angles: []
Paul Greyson740bdaf2013-03-18 16:10:48 -0700235 }];
236
Paul Greysonde7fad52013-03-19 12:47:32 -0700237
238 var aggRanges = {};
239
240 // arrange edge switches at equal increments
241 var k = 360 / rings[0].switches.length;
242 rings[0].switches.forEach(function (s, i) {
243 var angle = k * i;
244
245 rings[0].angles[i] = angle;
246
247 // record the angle for the agg switch layout
248 var dpid = s.dpid.split(':');
Paul Greyson832d2202013-03-21 13:27:56 -0700249 dpid[7] = '01'; // the last component of the agg switch is always '01'
Paul Greysonde7fad52013-03-19 12:47:32 -0700250 var aggdpid = dpid.join(':');
251 var aggRange = aggRanges[aggdpid];
252 if (!aggRange) {
253 aggRange = aggRanges[aggdpid] = {};
254 aggRange.min = aggRange.max = angle;
255 } else {
256 aggRange.max = angle;
257 }
Paul Greysonde7fad52013-03-19 12:47:32 -0700258 });
259
260 // arrange aggregation switches to "fan out" to edge switches
261 k = 360 / rings[1].switches.length;
262 rings[1].switches.forEach(function (s, i) {
263// rings[1].angles[i] = k * i;
264 var range = aggRanges[s.dpid];
265
Paul Greyson832d2202013-03-21 13:27:56 -0700266 rings[1].angles[i] = (range.min + range.max)/2;
Paul Greysonde7fad52013-03-19 12:47:32 -0700267 });
268
Paul Greyson3f890b62013-03-22 17:39:36 -0700269 // find the association between core switches and aggregation switches
270 var aggregationSwitchMap = {};
271 model.aggregationSwitches.forEach(function (s, i) {
Paul Greysonc17278a2013-03-23 10:17:12 -0700272 aggregationSwitchMap[s.dpid] = i;
Paul Greyson3f890b62013-03-22 17:39:36 -0700273 });
274
Paul Greyson3f890b62013-03-22 17:39:36 -0700275 // put core switches next to linked aggregation switches
Paul Greysonde7fad52013-03-19 12:47:32 -0700276 k = 360 / rings[2].switches.length;
277 rings[2].switches.forEach(function (s, i) {
Paul Greyson3f890b62013-03-22 17:39:36 -0700278// rings[2].angles[i] = k * i;
Paul Greysonc17278a2013-03-23 10:17:12 -0700279 var associatedAggregationSwitches = model.configuration.association[s.dpid];
280 // TODO: go between if there are multiple
281 var index = aggregationSwitchMap[associatedAggregationSwitches[0]];
282
283 rings[2].angles[i] = rings[1].angles[index];
Paul Greysonde7fad52013-03-19 12:47:32 -0700284 });
285
Paul Greyson644d92a2013-03-23 18:00:40 -0700286 // TODO: construct this form initially rather than converting. it works better because
287 // it allows binding by dpid
288 var testRings = [];
289 rings.forEach(function (ring) {
290 var testRing = [];
291 ring.switches.forEach(function (s, i) {
292 var testSwitch = {
293 dpid: s.dpid,
294 state: s.state,
295 radius: ring.radius,
296 width: ring.width,
297 className: ring.className,
298 angle: ring.angles[i],
299 controller: s.controller
Paul Greyson127d7fb2013-03-25 23:39:20 -0700300 };
Paul Greyson644d92a2013-03-23 18:00:40 -0700301 testRing.push(testSwitch);
302 });
Paul Greyson6d9ed862013-03-23 17:37:15 -0700303
304
Paul Greyson644d92a2013-03-23 18:00:40 -0700305 testRings.push(testRing);
306 });
Paul Greyson6d9ed862013-03-23 17:37:15 -0700307
308
Paul Greyson644d92a2013-03-23 18:00:40 -0700309// return rings;
310 return testRings;
Paul Greysonc17278a2013-03-23 10:17:12 -0700311}
312
Paul Greyson40c8a592013-03-27 14:10:33 -0700313function makeLinkKey(link) {
314 return link['src-switch'] + '=>' + link['dst-switch'];
315}
316
317function createLinkMap(links) {
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700318 var linkMap = {};
Paul Greyson40c8a592013-03-27 14:10:33 -0700319 links.forEach(function (link) {
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700320 var srcDPID = link['src-switch'];
321 var dstDPID = link['dst-switch'];
322
323 var srcMap = linkMap[srcDPID] || {};
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700324
Paul Greyson8d1c6362013-03-27 13:05:24 -0700325 srcMap[dstDPID] = link;
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700326
327 linkMap[srcDPID] = srcMap;
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700328 });
329 return linkMap;
330}
331
Paul Greyson56378ed2013-03-26 23:17:36 -0700332updateTopology = function(svg, model) {
Paul Greysonc17278a2013-03-23 10:17:12 -0700333
334 // DRAW THE SWITCHES
335 var rings = svg.selectAll('.ring').data(createRingsFromModel(model));
336
Paul Greyson40c8a592013-03-27 14:10:33 -0700337
338 var links = [];
339 model.links.forEach(function (link) {
340 links.push(link);
341 delete pendingLinks[makeLinkKey(link)]
342 })
343 var linkId;
344 for (linkId in pendingLinks) {
345 links.push(pendingLinks[linkId]);
346 }
347
348 var linkMap = createLinkMap(links);
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700349// var flowMap = createFlowMap(model);
350
Paul Greyson8d1c6362013-03-27 13:05:24 -0700351 function mouseOverSwitch(data) {
Paul Greyson72f18852013-03-27 15:56:11 -0700352
353 d3.event.preventDefault();
354
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700355 if (data.highlighted) {
356 return;
357 }
358
Paul Greyson72f18852013-03-27 15:56:11 -0700359
360
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700361 // only highlight valid link or flow destination by checking for class of existing highlighted circle
Paul Greyson421bfcd2013-03-27 22:22:09 -0700362 var highlighted = svg.selectAll('.highlight')[0];
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700363 if (highlighted.length == 1) {
Paul Greyson421bfcd2013-03-27 22:22:09 -0700364 var s = d3.select(highlighted[0]).select('circle');
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700365 // only allow links
366 // edge->edge (flow)
367 // aggregation->core
368 // core->core
369 if (data.className == 'edge' && !s.classed('edge') ||
370 data.className == 'core' && !s.classed('core') && !s.classed('aggregation') ||
371 data.className == 'aggregation' && !s.classed('core')) {
372 return;
373 }
374
375 // don't highlight if there's already a link or flow
376 // var map = linkMap[data.dpid];
377 // console.log(map);
378 // console.log(s.data()[0].dpid);
379 // console.log(map[s.data()[0].dpid]);
380 // if (map && map[s.data()[0].dpid]) {
381 // return;
382 // }
383
384 // the second highlighted switch is the target for a link or flow
385 data.target = true;
386 }
387
388
389 d3.select(document.getElementById(data.dpid + '-label')).classed('nolabel', false);
390 var node = d3.select(document.getElementById(data.dpid));
Paul Greyson421bfcd2013-03-27 22:22:09 -0700391 node.classed('highlight', true).select('circle').transition().duration(100).attr("r", widths.core);
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700392 data.highlighted = true;
393 node.moveToFront();
394 }
395
Paul Greyson8d1c6362013-03-27 13:05:24 -0700396 function mouseOutSwitch(data) {
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700397 if (data.mouseDown)
398 return;
399
400 d3.select(document.getElementById(data.dpid + '-label')).classed('nolabel', true);
401 var node = d3.select(document.getElementById(data.dpid));
Paul Greyson421bfcd2013-03-27 22:22:09 -0700402 node.classed('highlight', false).select('circle').transition().duration(100).attr("r", widths[data.className]);
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700403 data.highlighted = false;
404 data.target = false;
405 }
406
Paul Greyson8d1c6362013-03-27 13:05:24 -0700407 function mouseDownSwitch(data) {
408 mouseOverSwitch(data);
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700409 data.mouseDown = true;
Paul Greyson72f18852013-03-27 15:56:11 -0700410 d3.select('#topology').classed('linking', true);
411
Paul Greyson421bfcd2013-03-27 22:22:09 -0700412 d3.select('svg')
413 .append('svg:path')
414 .attr('id', 'linkVector')
415 .attr('d', function () {
416 var s = d3.select(document.getElementById(data.dpid));
417
418 var pt = document.querySelector('svg').createSVGPoint();
419 pt.x = s.attr('x');
420 pt.y = s.attr('y');
421 pt = pt.matrixTransform(s[0][0].getCTM());
422
423 return line([pt, pt]);
424 });
425
426
Paul Greyson72f18852013-03-27 15:56:11 -0700427 if (data.className === 'core') {
428 d3.selectAll('.edge').classed('nodrop', true);
429 }
430 if (data.className === 'edge') {
431 d3.selectAll('.core').classed('nodrop', true);
432 d3.selectAll('.aggregation').classed('nodrop', true);
433 }
434 if (data.className === 'aggregation') {
435 d3.selectAll('.edge').classed('nodrop', true);
436 d3.selectAll('.aggregation').classed('nodrop', true);
437 }
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700438 }
439
Paul Greyson8d1c6362013-03-27 13:05:24 -0700440 function mouseUpSwitch(data) {
441 if (data.mouseDown) {
442 data.mouseDown = false;
Paul Greyson72f18852013-03-27 15:56:11 -0700443 d3.select('#topology').classed('linking', false);
Paul Greyson421bfcd2013-03-27 22:22:09 -0700444 mouseup
Paul Greyson8d1c6362013-03-27 13:05:24 -0700445 d3.event.stopPropagation();
Paul Greyson72f18852013-03-27 15:56:11 -0700446 d3.selectAll('.nodrop').classed('nodrop', false);
Paul Greyson8d1c6362013-03-27 13:05:24 -0700447 }
448 }
449
450 function doubleClickSwitch(data) {
Paul Greyson084779b2013-03-27 13:55:49 -0700451 var circle = d3.select(document.getElementById(data.dpid)).select('circle');
Paul Greyson8d1c6362013-03-27 13:05:24 -0700452 if (data.state == 'ACTIVE') {
453 var prompt = 'Deactivate ' + data.dpid + '?';
454 if (confirm(prompt)) {
455 switchDown(data);
Paul Greyson084779b2013-03-27 13:55:49 -0700456 setPending(circle);
Paul Greyson8d1c6362013-03-27 13:05:24 -0700457 }
458 } else {
459 var prompt = 'Activate ' + data.dpid + '?';
460 if (confirm(prompt)) {
461 switchUp(data);
Paul Greyson084779b2013-03-27 13:55:49 -0700462 setPending(circle);
Paul Greyson8d1c6362013-03-27 13:05:24 -0700463 }
464 }
465 }
466
Paul Greyson740bdaf2013-03-18 16:10:48 -0700467 function ringEnter(data, i) {
Paul Greyson644d92a2013-03-23 18:00:40 -0700468 if (!data.length) {
Paul Greyson740bdaf2013-03-18 16:10:48 -0700469 return;
470 }
471
Paul Greysonc17278a2013-03-23 10:17:12 -0700472 // create the nodes
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700473 var nodes = d3.select(this).selectAll("g")
Paul Greyson644d92a2013-03-23 18:00:40 -0700474 .data(data, function (data) {
475 return data.dpid;
476 })
Paul Greyson740bdaf2013-03-18 16:10:48 -0700477 .enter().append("svg:g")
Paul Greyson644d92a2013-03-23 18:00:40 -0700478 .attr("id", function (data, i) {
479 return data.dpid;
Paul Greyson23b0cd32013-03-18 23:45:48 -0700480 })
Paul Greyson644d92a2013-03-23 18:00:40 -0700481 .attr("transform", function(data, i) {
482 return "rotate(" + data.angle+ ")translate(" + data.radius * 150 + ")rotate(" + (-data.angle) + ")";
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700483 });
484
Paul Greysonc17278a2013-03-23 10:17:12 -0700485 // add the cirles representing the switches
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700486 nodes.append("svg:circle")
Paul Greyson644d92a2013-03-23 18:00:40 -0700487 .attr("transform", function(data, i) {
Paul Greysond1a22d92013-03-19 12:15:19 -0700488 var m = document.querySelector('#viewbox').getTransformToElement().inverse();
Paul Greysonf8f43172013-03-18 23:00:30 -0700489 if (data.scale) {
490 m = m.scale(data.scale);
491 }
492 return "matrix( " + m.a + " " + m.b + " " + m.c + " " + m.d + " " + m.e + " " + m.f + " )";
Paul Greyson952ccb62013-03-18 22:22:08 -0700493 })
Paul Greyson644d92a2013-03-23 18:00:40 -0700494 .attr("x", function (data) {
495 return -data.width / 2;
496 })
497 .attr("y", function (data) {
498 return -data.width / 2;
499 })
500 .attr("r", function (data) {
501 return data.width;
Paul Greyson127d7fb2013-03-25 23:39:20 -0700502 });
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700503
Paul Greysonc17278a2013-03-23 10:17:12 -0700504 // setup the mouseover behaviors
Paul Greyson8d1c6362013-03-27 13:05:24 -0700505 nodes.on('mouseover', mouseOverSwitch);
506 nodes.on('mouseout', mouseOutSwitch);
507 nodes.on('mouseup', mouseUpSwitch);
508 nodes.on('mousedown', mouseDownSwitch);
509
510 // only do switch up/down for core switches
511 if (i == 2) {
512 nodes.on('dblclick', doubleClickSwitch);
513 }
Paul Greyson740bdaf2013-03-18 16:10:48 -0700514 }
515
Paul Greysonc17278a2013-03-23 10:17:12 -0700516 // append switches
517 rings.enter().append("svg:g")
Paul Greyson740bdaf2013-03-18 16:10:48 -0700518 .attr("class", "ring")
519 .each(ringEnter);
Paul Greysonf8f43172013-03-18 23:00:30 -0700520
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700521
Paul Greysonc17278a2013-03-23 10:17:12 -0700522 function ringUpdate(data, i) {
Paul Greyson127d7fb2013-03-25 23:39:20 -0700523 var nodes = d3.select(this).selectAll("g")
Paul Greyson644d92a2013-03-23 18:00:40 -0700524 .data(data, function (data) {
525 return data.dpid;
Paul Greyson127d7fb2013-03-25 23:39:20 -0700526 });
Paul Greyson347fb742013-03-27 13:40:29 -0700527 nodes.select('circle')
528 .each(function (data) {
529 // if there's a pending state changed and then the state changes, clear the pending class
530 var circle = d3.select(this);
531 if (data.state === 'ACTIVE' && circle.classed('inactive') ||
532 data.state === 'INACTIVE' && circle.classed('active')) {
533 circle.classed('pending', false);
534 }
535 })
536 .attr('class', function (data) {
Paul Greyson8d1c6362013-03-27 13:05:24 -0700537 if (data.state === 'ACTIVE' && data.controller) {
Paul Greyson347fb742013-03-27 13:40:29 -0700538 return data.className + ' active ' + controllerColorMap[data.controller];
Paul Greysonc17278a2013-03-23 10:17:12 -0700539 } else {
Paul Greyson347fb742013-03-27 13:40:29 -0700540 return data.className + ' inactive ' + 'colorInactive';
Paul Greysonc17278a2013-03-23 10:17:12 -0700541 }
Paul Greyson127d7fb2013-03-25 23:39:20 -0700542 });
Paul Greysonc17278a2013-03-23 10:17:12 -0700543 }
544
545 // update switches
546 rings.each(ringUpdate);
547
Paul Greyson968d1b42013-03-23 16:58:41 -0700548
549 // Now setup the labels
550 // This is done separately because SVG draws in node order and we want the labels
551 // always on top
552 var labelRings = svg.selectAll('.labelRing').data(createRingsFromModel(model));
553
Paul Greyson421bfcd2013-03-27 22:22:09 -0700554 d3.select(document.body).on('mousemove', function () {
555 if (!d3.select('#topology').classed('linking')) {
556 return;
557 }
558 var linkVector = document.getElementById('linkVector');
559 if (!linkVector) {
560 return;
561 }
562 linkVector = d3.select(linkVector);
563
564 var highlighted = svg.selectAll('.highlight')[0];
565 var s1 = null, s2 = null;
566 if (highlighted.length > 1) {
567 var s1 = d3.select(highlighted[0]);
568 var s2 = d3.select(highlighted[1]);
569
570 } else if (highlighted.length > 0) {
571 var s1 = d3.select(highlighted[0]);
572 }
573 var src = s1;
574 if (s2 && !s2.data()[0].target) {
575 src = s2;
576 }
577 if (src) {
578 linkVector.attr('d', function () {
579 var srcPt = document.querySelector('svg').createSVGPoint();
580 srcPt.x = src.attr('x');
581 srcPt.y = src.attr('y');
582 srcPt = srcPt.matrixTransform(src[0][0].getCTM());
583
584 var svg = document.getElementById('topology');
585 var mouse = d3.mouse(viewbox);
586 var dstPt = document.querySelector('svg').createSVGPoint();
587 dstPt.x = mouse[0];
588 dstPt.y = mouse[1];
589 dstPt = dstPt.matrixTransform(viewbox.getCTM());
590
591 return line([srcPt, dstPt]);
592 });
593 }
594 });
595
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700596 d3.select(document.body).on('mouseup', function () {
597 function clearHighlight() {
598 svg.selectAll('circle').each(function (data) {
599 data.mouseDown = false;
Paul Greyson72f18852013-03-27 15:56:11 -0700600 d3.select('#topology').classed('linking', false);
Paul Greyson8d1c6362013-03-27 13:05:24 -0700601 mouseOutSwitch(data);
Paul Greyson421bfcd2013-03-27 22:22:09 -0700602 });
603 d3.select('#linkVector').remove();
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700604 };
605
Paul Greyson72f18852013-03-27 15:56:11 -0700606 d3.selectAll('.nodrop').classed('nodrop', false);
607
Paul Greyson084779b2013-03-27 13:55:49 -0700608 function removeLink(link) {
609 var path1 = document.getElementById(link['src-switch'] + '=>' + link['dst-switch']);
610 var path2 = document.getElementById(link['dst-switch'] + '=>' + link['src-switch']);
611
612 if (path1) {
613 setPending(d3.select(path1));
614 }
615 if (path2) {
616 setPending(d3.select(path2));
617 }
618
619 linkDown(link);
620 }
621
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700622
Paul Greyson421bfcd2013-03-27 22:22:09 -0700623 var highlighted = svg.selectAll('.highlight')[0];
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700624 if (highlighted.length == 2) {
Paul Greyson421bfcd2013-03-27 22:22:09 -0700625 var s1Data = highlighted[0].__data__;
626 var s2Data = highlighted[1].__data__;
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700627
628 var srcData, dstData;
629 if (s1Data.target) {
630 dstData = s1Data;
631 srcData = s2Data;
632 } else {
633 dstData = s2Data;
634 srcData = s1Data;
635 }
636
637 if (s1Data.className == 'edge' && s2Data.className == 'edge') {
638 var prompt = 'Create flow from ' + srcData.dpid + ' to ' + dstData.dpid + '?';
639 if (confirm(prompt)) {
640 alert('do create flow');
641 } else {
642 alert('do not create flow');
643 }
644 } else {
645 var map = linkMap[srcData.dpid];
646 if (map && map[dstData.dpid]) {
647 var prompt = 'Remove link between ' + srcData.dpid + ' and ' + dstData.dpid + '?';
648 if (confirm(prompt)) {
Paul Greyson084779b2013-03-27 13:55:49 -0700649 removeLink(map[dstData.dpid]);
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700650 }
651 } else {
Paul Greyson8d1c6362013-03-27 13:05:24 -0700652 map = linkMap[dstData.dpid];
653 if (map && map[srcData.dpid]) {
654 var prompt = 'Remove link between ' + dstData.dpid + ' and ' + srcData.dpid + '?';
655 if (confirm(prompt)) {
Paul Greyson084779b2013-03-27 13:55:49 -0700656 removeLink(map[srcData.dpid]);
Paul Greyson8d1c6362013-03-27 13:05:24 -0700657 }
658 } else {
659 var prompt = 'Create link between ' + srcData.dpid + ' and ' + dstData.dpid + '?';
660 if (confirm(prompt)) {
Paul Greyson40c8a592013-03-27 14:10:33 -0700661 var link1 = {
662 'src-switch': srcData.dpid,
Paul Greyson2913af82013-03-27 14:53:17 -0700663 'src-port': 1,
Paul Greyson40c8a592013-03-27 14:10:33 -0700664 'dst-switch': dstData.dpid,
Paul Greyson2913af82013-03-27 14:53:17 -0700665 'dst-port': 1,
Paul Greyson40c8a592013-03-27 14:10:33 -0700666 pending: true
667 };
668 pendingLinks[makeLinkKey(link1)] = link1;
669 var link2 = {
670 'src-switch': dstData.dpid,
Paul Greyson2913af82013-03-27 14:53:17 -0700671 'src-port': 1,
Paul Greyson40c8a592013-03-27 14:10:33 -0700672 'dst-switch': srcData.dpid,
Paul Greyson2913af82013-03-27 14:53:17 -0700673 'dst-port': 1,
Paul Greyson40c8a592013-03-27 14:10:33 -0700674 pending: true
675 };
676 pendingLinks[makeLinkKey(link2)] = link2;
677 updateTopology(svg, model);
678
Paul Greyson2913af82013-03-27 14:53:17 -0700679 linkUp(link1);
Paul Greyson40c8a592013-03-27 14:10:33 -0700680
681 // remove the pending link after 10s
682 setTimeout(function () {
683 delete pendingLinks[makeLinkKey(link1)];
684 delete pendingLinks[makeLinkKey(link2)];
685
686 updateTopology(svg, model);
687 }, 10000);
Paul Greyson8d1c6362013-03-27 13:05:24 -0700688 }
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700689 }
690 }
691 }
692
693 clearHighlight();
694 } else {
695 clearHighlight();
696 }
697
698 });
699
Paul Greyson9066ab02013-03-23 18:15:41 -0700700 function labelRingEnter(data) {
Paul Greyson644d92a2013-03-23 18:00:40 -0700701 if (!data.length) {
Paul Greyson968d1b42013-03-23 16:58:41 -0700702 return;
703 }
704
705 // create the nodes
706 var nodes = d3.select(this).selectAll("g")
Paul Greyson644d92a2013-03-23 18:00:40 -0700707 .data(data, function (data) {
708 return data.dpid;
709 })
Paul Greyson968d1b42013-03-23 16:58:41 -0700710 .enter().append("svg:g")
711 .classed('nolabel', true)
Paul Greyson9066ab02013-03-23 18:15:41 -0700712 .attr("id", function (data) {
Paul Greyson644d92a2013-03-23 18:00:40 -0700713 return data.dpid + '-label';
Paul Greyson968d1b42013-03-23 16:58:41 -0700714 })
Paul Greyson644d92a2013-03-23 18:00:40 -0700715 .attr("transform", function(data, i) {
716 return "rotate(" + data.angle+ ")translate(" + data.radius * 150 + ")rotate(" + (-data.angle) + ")";
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700717 })
Paul Greyson968d1b42013-03-23 16:58:41 -0700718
719 // add the text nodes which show on mouse over
720 nodes.append("svg:text")
Paul Greyson127d7fb2013-03-25 23:39:20 -0700721 .text(function (data) {return data.dpid;})
Paul Greyson9066ab02013-03-23 18:15:41 -0700722 .attr("x", function (data) {
723 if (data.angle <= 90 || data.angle >= 270 && data.angle <= 360) {
724 if (data.className == 'edge') {
Paul Greyson1eb2dd12013-03-23 18:22:00 -0700725 return - data.width*3 - 4;
Paul Greyson9066ab02013-03-23 18:15:41 -0700726 } else {
Paul Greyson1eb2dd12013-03-23 18:22:00 -0700727 return - data.width - 4;
Paul Greyson9066ab02013-03-23 18:15:41 -0700728 }
729 } else {
730 if (data.className == 'edge') {
731 return data.width*3 + 4;
732 } else {
733 return data.width + 4;
734 }
735 }
736 })
737 .attr("y", function (data) {
738 var y;
739 if (data.angle <= 90 || data.angle >= 270 && data.angle <= 360) {
740 if (data.className == 'edge') {
741 y = data.width*3/2 + 4;
742 } else {
743 y = data.width/2 + 4;
744 }
745 } else {
746 if (data.className == 'edge') {
747 y = data.width*3/2 + 4;
748 } else {
749 y = data.width/2 + 4;
750 }
751 }
752 return y - 6;
753 })
754 .attr("text-anchor", function (data) {
755 if (data.angle <= 90 || data.angle >= 270 && data.angle <= 360) {
756 return "end";
757 } else {
758 return "start";
759 }
760 })
761 .attr("transform", function(data) {
Paul Greyson968d1b42013-03-23 16:58:41 -0700762 var m = document.querySelector('#viewbox').getTransformToElement().inverse();
763 if (data.scale) {
764 m = m.scale(data.scale);
765 }
766 return "matrix( " + m.a + " " + m.b + " " + m.c + " " + m.d + " " + m.e + " " + m.f + " )";
767 })
768 }
769
770 labelRings.enter().append("svg:g")
771 .attr("class", "textRing")
772 .each(labelRingEnter);
773
Paul Greysonc17278a2013-03-23 10:17:12 -0700774 // switches should not change during operation of the ui so no
775 // rings.exit()
776
777
Paul Greysond1a22d92013-03-19 12:15:19 -0700778 // DRAW THE LINKS
Paul Greysond1a22d92013-03-19 12:15:19 -0700779
Paul Greysonc17278a2013-03-23 10:17:12 -0700780 // key on link dpids since these will come/go during demo
Paul Greyson40c8a592013-03-27 14:10:33 -0700781 var links = d3.select('svg').selectAll('.link').data(links, function (d) {
Paul Greysonc17278a2013-03-23 10:17:12 -0700782 return d['src-switch']+'->'+d['dst-switch'];
783 });
784
785 // add new links
Paul Greysonb367de22013-03-23 11:09:11 -0700786 links.enter().append("svg:path")
Paul Greyson56378ed2013-03-26 23:17:36 -0700787 .attr("class", "link");
788
Paul Greyson084779b2013-03-27 13:55:49 -0700789 links.attr('id', function (d) {
Paul Greyson40c8a592013-03-27 14:10:33 -0700790 return makeLinkKey(d);
Paul Greyson084779b2013-03-27 13:55:49 -0700791 })
Paul Greyson56378ed2013-03-26 23:17:36 -0700792 .attr("d", function (d) {
Paul Greyson084779b2013-03-27 13:55:49 -0700793 var src = d3.select(document.getElementById(d['src-switch']));
794 var dst = d3.select(document.getElementById(d['dst-switch']));
Paul Greysonc17278a2013-03-23 10:17:12 -0700795
Paul Greyson084779b2013-03-27 13:55:49 -0700796 var srcPt = document.querySelector('svg').createSVGPoint();
797 srcPt.x = src.attr('x');
798 srcPt.y = src.attr('y');
799 srcPt = srcPt.matrixTransform(src[0][0].getCTM());
Paul Greysond1a22d92013-03-19 12:15:19 -0700800
Paul Greyson084779b2013-03-27 13:55:49 -0700801 var dstPt = document.querySelector('svg').createSVGPoint();
802 dstPt.x = dst.attr('x');
Paul Greyson421bfcd2013-03-27 22:22:09 -0700803 dstPt.y = dst.attr('y');
Paul Greyson084779b2013-03-27 13:55:49 -0700804 dstPt = dstPt.matrixTransform(dst[0][0].getCTM());
Paul Greysond1a22d92013-03-19 12:15:19 -0700805
Paul Greyson084779b2013-03-27 13:55:49 -0700806 var midPt = document.querySelector('svg').createSVGPoint();
807 midPt.x = (srcPt.x + dstPt.x)/2;
808 midPt.y = (srcPt.y + dstPt.y)/2;
Paul Greysond1a22d92013-03-19 12:15:19 -0700809
Paul Greyson084779b2013-03-27 13:55:49 -0700810 return line([srcPt, midPt, dstPt]);
811 })
Paul Greyson40c8a592013-03-27 14:10:33 -0700812 .attr("marker-mid", function(d) { return "url(#arrow)"; })
813 .classed('pending', function (d) {
814 return d.pending;
815 });
Paul Greysonc17278a2013-03-23 10:17:12 -0700816
Paul Greyson56378ed2013-03-26 23:17:36 -0700817
Paul Greysonc17278a2013-03-23 10:17:12 -0700818 // remove old links
819 links.exit().remove();
Paul Greyson644d92a2013-03-23 18:00:40 -0700820
Paul Greyson127d7fb2013-03-25 23:39:20 -0700821
822 drawFlows();
Paul Greysond1a22d92013-03-19 12:15:19 -0700823}
824
825function updateControllers(model) {
826 var controllers = d3.select('#controllerList').selectAll('.controller').data(model.controllers);
Paul Greyson3e142162013-03-19 13:56:17 -0700827 controllers.enter().append('div')
Paul Greysone262a292013-03-23 10:35:23 -0700828 .each(function (c) {
829 controllerColorMap[c] = colors.pop();
830 d3.select(document.body).classed(controllerColorMap[c] + '-selected', true);
831 })
832 .text(function (d) {
833 return d;
Paul Greyson2913af82013-03-27 14:53:17 -0700834 })
835 .append('div')
836 .attr('class', 'controllerEye');
Paul Greysonbcd3c772013-03-21 13:16:44 -0700837
Paul Greysone262a292013-03-23 10:35:23 -0700838 controllers.attr('class', function (d) {
Paul Greysoneed36352013-03-23 11:19:11 -0700839 var color = 'colorInactive';
Paul Greysonbcd3c772013-03-21 13:16:44 -0700840 if (model.activeControllers.indexOf(d) != -1) {
841 color = controllerColorMap[d];
Paul Greysond1a22d92013-03-19 12:15:19 -0700842 }
Paul Greysonbcd3c772013-03-21 13:16:44 -0700843 var className = 'controller ' + color;
844 return className;
Paul Greysond1a22d92013-03-19 12:15:19 -0700845 });
Paul Greysond1a22d92013-03-19 12:15:19 -0700846
Paul Greysone262a292013-03-23 10:35:23 -0700847 // this should never be needed
848 // controllers.exit().remove();
Paul Greysond1a22d92013-03-19 12:15:19 -0700849
Paul Greyson2913af82013-03-27 14:53:17 -0700850 controllers.on('dblclick', function (c) {
851 if (model.activeControllers.indexOf(c) != -1) {
852 var prompt = 'Dectivate ' + c + '?';
853 if (confirm(prompt)) {
854 controllerDown(c);
855 setPending(d3.select(this));
856 };
857 } else {
858 var prompt = 'Activate ' + c + '?';
859 if (confirm(prompt)) {
860 controllerUp(c);
861 setPending(d3.select(this));
862 };
863 }
864 });
865
866 controllers.select('.controllerEye').on('click', function (c) {
Paul Greysonc3e21a02013-03-21 13:56:05 -0700867 var allSelected = true;
868 for (var key in controllerColorMap) {
869 if (!d3.select(document.body).classed(controllerColorMap[key] + '-selected')) {
870 allSelected = false;
871 break;
872 }
873 }
874 if (allSelected) {
875 for (var key in controllerColorMap) {
876 d3.select(document.body).classed(controllerColorMap[key] + '-selected', key == c)
877 }
878 } else {
879 for (var key in controllerColorMap) {
880 d3.select(document.body).classed(controllerColorMap[key] + '-selected', true)
881 }
882 }
883
884 // var selected = d3.select(document.body).classed(controllerColorMap[c] + '-selected');
885 // d3.select(document.body).classed(controllerColorMap[c] + '-selected', !selected);
Paul Greysond1a22d92013-03-19 12:15:19 -0700886 });
Paul Greyson8d1c6362013-03-27 13:05:24 -0700887
888
Paul Greyson740bdaf2013-03-18 16:10:48 -0700889}
890
Paul Greyson127d7fb2013-03-25 23:39:20 -0700891function sync(svg, selectedFlowsView) {
Paul Greysonbcd3c772013-03-21 13:16:44 -0700892 var d = Date.now();
Paul Greysonb48943b2013-03-19 13:27:57 -0700893 updateModel(function (newModel) {
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700894// console.log('Update time: ' + (Date.now() - d)/1000 + 's');
Paul Greyson740bdaf2013-03-18 16:10:48 -0700895
Paul Greyson56378ed2013-03-26 23:17:36 -0700896 if (!model || JSON.stringify(model) != JSON.stringify(newModel)) {
Paul Greysonb48943b2013-03-19 13:27:57 -0700897 updateControllers(newModel);
Paul Greyson127d7fb2013-03-25 23:39:20 -0700898
899 // fake flows right now
900 var i;
Paul Greyson56378ed2013-03-26 23:17:36 -0700901 for (i = 0; i < newModel.flows.length && i < selectedFlowsData.length; i+=1) {
Paul Greyson127d7fb2013-03-25 23:39:20 -0700902 var selected = selectedFlowsData[i] ? selectedFlowsData[i].selected : false;
903 selectedFlowsData[i].flow = newModel.flows[i];
904 selectedFlowsData[i].selected = selected;
905 }
906
907 updateFlowView(newModel);
Paul Greysonb48943b2013-03-19 13:27:57 -0700908 updateTopology(svg, newModel);
909 } else {
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700910// console.log('no change');
Paul Greysonb48943b2013-03-19 13:27:57 -0700911 }
912 updateHeader(newModel);
913
Paul Greyson56378ed2013-03-26 23:17:36 -0700914 model = newModel;
Paul Greyson740bdaf2013-03-18 16:10:48 -0700915
916 // do it again in 1s
917 setTimeout(function () {
Paul Greysona36a9232013-03-22 22:41:27 -0700918 sync(svg)
Paul Greysond1a22d92013-03-19 12:15:19 -0700919 }, 1000);
Paul Greyson6f86d1e2013-03-18 14:40:39 -0700920 });
921}
Paul Greyson740bdaf2013-03-18 16:10:48 -0700922
Paul Greyson38d8bde2013-03-22 22:07:35 -0700923svg = createTopologyView();
Paul Greyson127d7fb2013-03-25 23:39:20 -0700924selectedFlowsView = createFlowView();
Paul Greyson72f18852013-03-27 15:56:11 -0700925
Paul Greyson38d8bde2013-03-22 22:07:35 -0700926// workaround for Chrome v25 bug
927// if executed immediately, the view box transform logic doesn't work properly
928// fixed in Chrome v27
929setTimeout(function () {
930 // workaround for another Chrome v25 bug
931 // viewbox transform stuff doesn't work in combination with browser zoom
Paul Greysonc17278a2013-03-23 10:17:12 -0700932 // also works in Chrome v27
Paul Greyson38d8bde2013-03-22 22:07:35 -0700933 d3.select('#svg-container').style('zoom', window.document.body.clientWidth/window.document.width);
Paul Greyson127d7fb2013-03-25 23:39:20 -0700934 sync(svg, selectedFlowsView);
Paul Greyson38d8bde2013-03-22 22:07:35 -0700935}, 100);