blob: 84cffd06ae8b9ef454593080e11be895f99e6da1 [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 Greyson29aa98d2013-03-28 00:09:31 -070018var svg;
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 Greyson29aa98d2013-03-28 00:09:31 -070071var selectedFlows = [null, null, null];
Paul Greyson127d7fb2013-03-25 23:39:20 -070072
73function drawFlows() {
74 // DRAW THE FLOWS
Paul Greyson29aa98d2013-03-28 00:09:31 -070075 var flows = d3.select('svg').selectAll('.flow').data(selectedFlows, function (d) {
76 return d ? d.flowId.value : null;
Paul Greyson127d7fb2013-03-25 23:39:20 -070077 });
78
Paul Greyson29aa98d2013-03-28 00:09:31 -070079 flows.enter().append("svg:path").attr('class', 'flow')
80 .attr('stroke-dasharray', '4, 10')
81 .append('svg:animate')
82 .attr('attributeName', 'stroke-dashoffset')
83 .attr('attributeType', 'xml')
84 .attr('from', '500')
85 .attr('to', '-500')
86 .attr('dur', '20s')
87 .attr('repeatCount', 'indefinite');
88
89
90 flows.attr('d', function (d) {
91 if (!d) {
Paul Greyson127d7fb2013-03-25 23:39:20 -070092 return;
93 }
94 var pts = [];
Paul Greyson29aa98d2013-03-28 00:09:31 -070095 d.dataPath.flowEntries.forEach(function (flowEntry) {
Paul Greyson127d7fb2013-03-25 23:39:20 -070096 var s = d3.select(document.getElementById(flowEntry.dpid.value));
97 var pt = document.querySelector('svg').createSVGPoint();
98 pt.x = s.attr('x');
99 pt.y = s.attr('y');
100 pt = pt.matrixTransform(s[0][0].getCTM());
101 pts.push(pt);
102 });
103 return line(pts);
104 })
Paul Greyson127d7fb2013-03-25 23:39:20 -0700105
Paul Greyson56378ed2013-03-26 23:17:36 -0700106 // "marching ants"
Paul Greyson29aa98d2013-03-28 00:09:31 -0700107 flows.select('animate').attr('from', 500);
108
109 flows.exit().remove();
Paul Greyson127d7fb2013-03-25 23:39:20 -0700110}
111
Paul Greyson29aa98d2013-03-28 00:09:31 -0700112function showFlowChooser() {
113 function rowEnter(d) {
Paul Greyson127d7fb2013-03-25 23:39:20 -0700114 var row = d3.select(this);
115
Paul Greyson127d7fb2013-03-25 23:39:20 -0700116 row.append('div')
Paul Greyson8247c3f2013-03-28 00:24:02 -0700117 .classed('black-eye', true).
Paul Greyson29aa98d2013-03-28 00:09:31 -0700118 on('click', function () {
119 selectedFlows.unshift(d);
120 selectedFlows = selectedFlows.slice(0, 3);
121
122 updateSelectedFlows();
123 updateTopology(svg, model);
Paul Greyson127d7fb2013-03-25 23:39:20 -0700124 });
125
126 row.append('div')
Paul Greyson29aa98d2013-03-28 00:09:31 -0700127 .classed('flowId', true)
128 .text(function (d) {
129 return d.flowId.value;
130 });
Paul Greyson127d7fb2013-03-25 23:39:20 -0700131
132 row.append('div')
Paul Greyson29aa98d2013-03-28 00:09:31 -0700133 .classed('srcDPID', true)
134 .text(function (d) {
135 return d.dataPath.srcPort.dpid.value;
136 });
137
Paul Greyson127d7fb2013-03-25 23:39:20 -0700138
139 row.append('div')
Paul Greyson29aa98d2013-03-28 00:09:31 -0700140 .classed('dstDPID', true)
141 .text(function (d) {
142 return d.dataPath.dstPort.dpid.value;
143 });
Paul Greyson127d7fb2013-03-25 23:39:20 -0700144
Paul Greyson127d7fb2013-03-25 23:39:20 -0700145 }
146
Paul Greyson29aa98d2013-03-28 00:09:31 -0700147 var flows = d3.select('#flowChooser')
148 .append('div')
149 .style('pointer-events', 'auto')
Paul Greyson127d7fb2013-03-25 23:39:20 -0700150 .selectAll('.selectedFlow')
Paul Greyson29aa98d2013-03-28 00:09:31 -0700151 .data(model.flows)
Paul Greyson127d7fb2013-03-25 23:39:20 -0700152 .enter()
153 .append('div')
154 .classed('selectedFlow', true)
155 .each(rowEnter);
156
Paul Greyson29aa98d2013-03-28 00:09:31 -0700157 setTimeout(function () {
158 d3.select(document.body).on('click', function () {
159 d3.select('#flowChooser').html('');
160 d3.select(document.body).on('click', null);
161 });
162 }, 0);
163}
164
165function updateSelectedFlows() {
166 function rowEnter(d) {
167 var row = d3.select(this);
168 row.append('div').classed('flowId', true);
169 row.append('div').classed('srcDPID', true);
170 row.append('div').classed('dstDPID', true);
171 row.append('div').classed('iperf', true);
172 }
173
174 function rowUpdate(d) {
175 var row = d3.select(this);
176 row.select('.flowId')
177 .text(function (d) {
178 if (d) {
179 return d.flowId.value;
180 }
181 });
182
183 row.select('.srcDPID')
184 .text(function (d) {
185 if (d) {
186 return d.dataPath.srcPort.dpid.value;
187 }
188 });
189
190 row.select('.dstDPID')
191 .text(function (d) {
192 if (d) {
193 return d.dataPath.dstPort.dpid.value;
194 }
195 });
196 }
197
198 var flows = d3.select('#selectedFlows')
199 .selectAll('.selectedFlow')
200 .data(selectedFlows);
201
202 flows.enter()
203 .append('div')
204 .classed('selectedFlow', true)
205 .each(rowEnter);
206
207 flows.each(rowUpdate);
208
209 flows.exit().remove();
Paul Greyson127d7fb2013-03-25 23:39:20 -0700210
211 return flows;
212}
213
Paul Greysond1a22d92013-03-19 12:15:19 -0700214function updateHeader(model) {
Paul Greysonb48943b2013-03-19 13:27:57 -0700215 d3.select('#lastUpdate').text(new Date());
Paul Greyson952ccb62013-03-18 22:22:08 -0700216 d3.select('#activeSwitches').text(model.edgeSwitches.length + model.aggregationSwitches.length + model.coreSwitches.length);
217 d3.select('#activeFlows').text(model.flows.length);
218}
219
220function toRadians (angle) {
221 return angle * (Math.PI / 180);
Paul Greyson740bdaf2013-03-18 16:10:48 -0700222}
223
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700224var widths = {
225 edge: 6,
226 aggregation: 12,
227 core: 18
228}
229
Paul Greysonc17278a2013-03-23 10:17:12 -0700230function createRingsFromModel(model) {
Paul Greyson740bdaf2013-03-18 16:10:48 -0700231 var rings = [{
232 radius: 3,
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700233 width: widths.edge,
Paul Greyson952ccb62013-03-18 22:22:08 -0700234 switches: model.edgeSwitches,
Paul Greysonde7fad52013-03-19 12:47:32 -0700235 className: 'edge',
236 angles: []
Paul Greyson740bdaf2013-03-18 16:10:48 -0700237 }, {
Paul Greysond1a22d92013-03-19 12:15:19 -0700238 radius: 2.25,
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700239 width: widths.aggregation,
Paul Greyson952ccb62013-03-18 22:22:08 -0700240 switches: model.aggregationSwitches,
Paul Greysonde7fad52013-03-19 12:47:32 -0700241 className: 'aggregation',
242 angles: []
Paul Greyson740bdaf2013-03-18 16:10:48 -0700243 }, {
Paul Greyson127d7fb2013-03-25 23:39:20 -0700244 radius: 0.75,
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700245 width: widths.core,
Paul Greyson952ccb62013-03-18 22:22:08 -0700246 switches: model.coreSwitches,
Paul Greysonde7fad52013-03-19 12:47:32 -0700247 className: 'core',
248 angles: []
Paul Greyson740bdaf2013-03-18 16:10:48 -0700249 }];
250
Paul Greysonde7fad52013-03-19 12:47:32 -0700251
252 var aggRanges = {};
253
254 // arrange edge switches at equal increments
255 var k = 360 / rings[0].switches.length;
256 rings[0].switches.forEach(function (s, i) {
257 var angle = k * i;
258
259 rings[0].angles[i] = angle;
260
261 // record the angle for the agg switch layout
262 var dpid = s.dpid.split(':');
Paul Greyson832d2202013-03-21 13:27:56 -0700263 dpid[7] = '01'; // the last component of the agg switch is always '01'
Paul Greysonde7fad52013-03-19 12:47:32 -0700264 var aggdpid = dpid.join(':');
265 var aggRange = aggRanges[aggdpid];
266 if (!aggRange) {
267 aggRange = aggRanges[aggdpid] = {};
268 aggRange.min = aggRange.max = angle;
269 } else {
270 aggRange.max = angle;
271 }
Paul Greysonde7fad52013-03-19 12:47:32 -0700272 });
273
274 // arrange aggregation switches to "fan out" to edge switches
275 k = 360 / rings[1].switches.length;
276 rings[1].switches.forEach(function (s, i) {
277// rings[1].angles[i] = k * i;
278 var range = aggRanges[s.dpid];
279
Paul Greyson832d2202013-03-21 13:27:56 -0700280 rings[1].angles[i] = (range.min + range.max)/2;
Paul Greysonde7fad52013-03-19 12:47:32 -0700281 });
282
Paul Greyson3f890b62013-03-22 17:39:36 -0700283 // find the association between core switches and aggregation switches
284 var aggregationSwitchMap = {};
285 model.aggregationSwitches.forEach(function (s, i) {
Paul Greysonc17278a2013-03-23 10:17:12 -0700286 aggregationSwitchMap[s.dpid] = i;
Paul Greyson3f890b62013-03-22 17:39:36 -0700287 });
288
Paul Greyson3f890b62013-03-22 17:39:36 -0700289 // put core switches next to linked aggregation switches
Paul Greysonde7fad52013-03-19 12:47:32 -0700290 k = 360 / rings[2].switches.length;
291 rings[2].switches.forEach(function (s, i) {
Paul Greyson3f890b62013-03-22 17:39:36 -0700292// rings[2].angles[i] = k * i;
Paul Greysonc17278a2013-03-23 10:17:12 -0700293 var associatedAggregationSwitches = model.configuration.association[s.dpid];
294 // TODO: go between if there are multiple
295 var index = aggregationSwitchMap[associatedAggregationSwitches[0]];
296
297 rings[2].angles[i] = rings[1].angles[index];
Paul Greysonde7fad52013-03-19 12:47:32 -0700298 });
299
Paul Greyson644d92a2013-03-23 18:00:40 -0700300 // TODO: construct this form initially rather than converting. it works better because
301 // it allows binding by dpid
302 var testRings = [];
303 rings.forEach(function (ring) {
304 var testRing = [];
305 ring.switches.forEach(function (s, i) {
306 var testSwitch = {
307 dpid: s.dpid,
308 state: s.state,
309 radius: ring.radius,
310 width: ring.width,
311 className: ring.className,
312 angle: ring.angles[i],
313 controller: s.controller
Paul Greyson127d7fb2013-03-25 23:39:20 -0700314 };
Paul Greyson644d92a2013-03-23 18:00:40 -0700315 testRing.push(testSwitch);
316 });
Paul Greyson6d9ed862013-03-23 17:37:15 -0700317
318
Paul Greyson644d92a2013-03-23 18:00:40 -0700319 testRings.push(testRing);
320 });
Paul Greyson6d9ed862013-03-23 17:37:15 -0700321
322
Paul Greyson644d92a2013-03-23 18:00:40 -0700323// return rings;
324 return testRings;
Paul Greysonc17278a2013-03-23 10:17:12 -0700325}
326
Paul Greyson40c8a592013-03-27 14:10:33 -0700327function makeLinkKey(link) {
328 return link['src-switch'] + '=>' + link['dst-switch'];
329}
330
331function createLinkMap(links) {
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700332 var linkMap = {};
Paul Greyson40c8a592013-03-27 14:10:33 -0700333 links.forEach(function (link) {
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700334 var srcDPID = link['src-switch'];
335 var dstDPID = link['dst-switch'];
336
337 var srcMap = linkMap[srcDPID] || {};
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700338
Paul Greyson8d1c6362013-03-27 13:05:24 -0700339 srcMap[dstDPID] = link;
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700340
341 linkMap[srcDPID] = srcMap;
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700342 });
343 return linkMap;
344}
345
Paul Greyson56378ed2013-03-26 23:17:36 -0700346updateTopology = function(svg, model) {
Paul Greysonc17278a2013-03-23 10:17:12 -0700347
348 // DRAW THE SWITCHES
349 var rings = svg.selectAll('.ring').data(createRingsFromModel(model));
350
Paul Greyson40c8a592013-03-27 14:10:33 -0700351
352 var links = [];
353 model.links.forEach(function (link) {
354 links.push(link);
355 delete pendingLinks[makeLinkKey(link)]
356 })
357 var linkId;
358 for (linkId in pendingLinks) {
359 links.push(pendingLinks[linkId]);
360 }
361
362 var linkMap = createLinkMap(links);
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700363// var flowMap = createFlowMap(model);
364
Paul Greyson8d1c6362013-03-27 13:05:24 -0700365 function mouseOverSwitch(data) {
Paul Greyson72f18852013-03-27 15:56:11 -0700366
367 d3.event.preventDefault();
368
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700369 if (data.highlighted) {
370 return;
371 }
372
Paul Greyson72f18852013-03-27 15:56:11 -0700373
374
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700375 // only highlight valid link or flow destination by checking for class of existing highlighted circle
Paul Greyson421bfcd2013-03-27 22:22:09 -0700376 var highlighted = svg.selectAll('.highlight')[0];
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700377 if (highlighted.length == 1) {
Paul Greyson421bfcd2013-03-27 22:22:09 -0700378 var s = d3.select(highlighted[0]).select('circle');
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700379 // only allow links
380 // edge->edge (flow)
381 // aggregation->core
382 // core->core
383 if (data.className == 'edge' && !s.classed('edge') ||
384 data.className == 'core' && !s.classed('core') && !s.classed('aggregation') ||
385 data.className == 'aggregation' && !s.classed('core')) {
386 return;
387 }
388
389 // don't highlight if there's already a link or flow
390 // var map = linkMap[data.dpid];
391 // console.log(map);
392 // console.log(s.data()[0].dpid);
393 // console.log(map[s.data()[0].dpid]);
394 // if (map && map[s.data()[0].dpid]) {
395 // return;
396 // }
397
398 // the second highlighted switch is the target for a link or flow
399 data.target = true;
400 }
401
402
403 d3.select(document.getElementById(data.dpid + '-label')).classed('nolabel', false);
404 var node = d3.select(document.getElementById(data.dpid));
Paul Greyson421bfcd2013-03-27 22:22:09 -0700405 node.classed('highlight', true).select('circle').transition().duration(100).attr("r", widths.core);
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700406 data.highlighted = true;
407 node.moveToFront();
408 }
409
Paul Greyson8d1c6362013-03-27 13:05:24 -0700410 function mouseOutSwitch(data) {
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700411 if (data.mouseDown)
412 return;
413
414 d3.select(document.getElementById(data.dpid + '-label')).classed('nolabel', true);
415 var node = d3.select(document.getElementById(data.dpid));
Paul Greyson421bfcd2013-03-27 22:22:09 -0700416 node.classed('highlight', false).select('circle').transition().duration(100).attr("r", widths[data.className]);
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700417 data.highlighted = false;
418 data.target = false;
419 }
420
Paul Greyson8d1c6362013-03-27 13:05:24 -0700421 function mouseDownSwitch(data) {
422 mouseOverSwitch(data);
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700423 data.mouseDown = true;
Paul Greyson72f18852013-03-27 15:56:11 -0700424 d3.select('#topology').classed('linking', true);
425
Paul Greyson421bfcd2013-03-27 22:22:09 -0700426 d3.select('svg')
427 .append('svg:path')
428 .attr('id', 'linkVector')
429 .attr('d', function () {
430 var s = d3.select(document.getElementById(data.dpid));
431
432 var pt = document.querySelector('svg').createSVGPoint();
433 pt.x = s.attr('x');
434 pt.y = s.attr('y');
435 pt = pt.matrixTransform(s[0][0].getCTM());
436
437 return line([pt, pt]);
438 });
439
440
Paul Greyson72f18852013-03-27 15:56:11 -0700441 if (data.className === 'core') {
442 d3.selectAll('.edge').classed('nodrop', true);
443 }
444 if (data.className === 'edge') {
445 d3.selectAll('.core').classed('nodrop', true);
446 d3.selectAll('.aggregation').classed('nodrop', true);
447 }
448 if (data.className === 'aggregation') {
449 d3.selectAll('.edge').classed('nodrop', true);
450 d3.selectAll('.aggregation').classed('nodrop', true);
451 }
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700452 }
453
Paul Greyson8d1c6362013-03-27 13:05:24 -0700454 function mouseUpSwitch(data) {
455 if (data.mouseDown) {
456 data.mouseDown = false;
Paul Greyson72f18852013-03-27 15:56:11 -0700457 d3.select('#topology').classed('linking', false);
Paul Greyson421bfcd2013-03-27 22:22:09 -0700458 mouseup
Paul Greyson8d1c6362013-03-27 13:05:24 -0700459 d3.event.stopPropagation();
Paul Greyson72f18852013-03-27 15:56:11 -0700460 d3.selectAll('.nodrop').classed('nodrop', false);
Paul Greyson8d1c6362013-03-27 13:05:24 -0700461 }
462 }
463
464 function doubleClickSwitch(data) {
Paul Greyson084779b2013-03-27 13:55:49 -0700465 var circle = d3.select(document.getElementById(data.dpid)).select('circle');
Paul Greyson8d1c6362013-03-27 13:05:24 -0700466 if (data.state == 'ACTIVE') {
467 var prompt = 'Deactivate ' + data.dpid + '?';
468 if (confirm(prompt)) {
469 switchDown(data);
Paul Greyson084779b2013-03-27 13:55:49 -0700470 setPending(circle);
Paul Greyson8d1c6362013-03-27 13:05:24 -0700471 }
472 } else {
473 var prompt = 'Activate ' + data.dpid + '?';
474 if (confirm(prompt)) {
475 switchUp(data);
Paul Greyson084779b2013-03-27 13:55:49 -0700476 setPending(circle);
Paul Greyson8d1c6362013-03-27 13:05:24 -0700477 }
478 }
479 }
480
Paul Greyson740bdaf2013-03-18 16:10:48 -0700481 function ringEnter(data, i) {
Paul Greyson644d92a2013-03-23 18:00:40 -0700482 if (!data.length) {
Paul Greyson740bdaf2013-03-18 16:10:48 -0700483 return;
484 }
485
Paul Greysonc17278a2013-03-23 10:17:12 -0700486 // create the nodes
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700487 var nodes = d3.select(this).selectAll("g")
Paul Greyson644d92a2013-03-23 18:00:40 -0700488 .data(data, function (data) {
489 return data.dpid;
490 })
Paul Greyson740bdaf2013-03-18 16:10:48 -0700491 .enter().append("svg:g")
Paul Greyson644d92a2013-03-23 18:00:40 -0700492 .attr("id", function (data, i) {
493 return data.dpid;
Paul Greyson23b0cd32013-03-18 23:45:48 -0700494 })
Paul Greyson644d92a2013-03-23 18:00:40 -0700495 .attr("transform", function(data, i) {
496 return "rotate(" + data.angle+ ")translate(" + data.radius * 150 + ")rotate(" + (-data.angle) + ")";
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700497 });
498
Paul Greysonc17278a2013-03-23 10:17:12 -0700499 // add the cirles representing the switches
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700500 nodes.append("svg:circle")
Paul Greyson644d92a2013-03-23 18:00:40 -0700501 .attr("transform", function(data, i) {
Paul Greysond1a22d92013-03-19 12:15:19 -0700502 var m = document.querySelector('#viewbox').getTransformToElement().inverse();
Paul Greysonf8f43172013-03-18 23:00:30 -0700503 if (data.scale) {
504 m = m.scale(data.scale);
505 }
506 return "matrix( " + m.a + " " + m.b + " " + m.c + " " + m.d + " " + m.e + " " + m.f + " )";
Paul Greyson952ccb62013-03-18 22:22:08 -0700507 })
Paul Greyson644d92a2013-03-23 18:00:40 -0700508 .attr("x", function (data) {
509 return -data.width / 2;
510 })
511 .attr("y", function (data) {
512 return -data.width / 2;
513 })
514 .attr("r", function (data) {
515 return data.width;
Paul Greyson127d7fb2013-03-25 23:39:20 -0700516 });
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700517
Paul Greysonc17278a2013-03-23 10:17:12 -0700518 // setup the mouseover behaviors
Paul Greyson8d1c6362013-03-27 13:05:24 -0700519 nodes.on('mouseover', mouseOverSwitch);
520 nodes.on('mouseout', mouseOutSwitch);
521 nodes.on('mouseup', mouseUpSwitch);
522 nodes.on('mousedown', mouseDownSwitch);
523
524 // only do switch up/down for core switches
525 if (i == 2) {
526 nodes.on('dblclick', doubleClickSwitch);
527 }
Paul Greyson740bdaf2013-03-18 16:10:48 -0700528 }
529
Paul Greysonc17278a2013-03-23 10:17:12 -0700530 // append switches
531 rings.enter().append("svg:g")
Paul Greyson740bdaf2013-03-18 16:10:48 -0700532 .attr("class", "ring")
533 .each(ringEnter);
Paul Greysonf8f43172013-03-18 23:00:30 -0700534
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700535
Paul Greysonc17278a2013-03-23 10:17:12 -0700536 function ringUpdate(data, i) {
Paul Greyson127d7fb2013-03-25 23:39:20 -0700537 var nodes = d3.select(this).selectAll("g")
Paul Greyson644d92a2013-03-23 18:00:40 -0700538 .data(data, function (data) {
539 return data.dpid;
Paul Greyson127d7fb2013-03-25 23:39:20 -0700540 });
Paul Greyson347fb742013-03-27 13:40:29 -0700541 nodes.select('circle')
542 .each(function (data) {
543 // if there's a pending state changed and then the state changes, clear the pending class
544 var circle = d3.select(this);
545 if (data.state === 'ACTIVE' && circle.classed('inactive') ||
546 data.state === 'INACTIVE' && circle.classed('active')) {
547 circle.classed('pending', false);
548 }
549 })
550 .attr('class', function (data) {
Paul Greyson8d1c6362013-03-27 13:05:24 -0700551 if (data.state === 'ACTIVE' && data.controller) {
Paul Greyson347fb742013-03-27 13:40:29 -0700552 return data.className + ' active ' + controllerColorMap[data.controller];
Paul Greysonc17278a2013-03-23 10:17:12 -0700553 } else {
Paul Greyson347fb742013-03-27 13:40:29 -0700554 return data.className + ' inactive ' + 'colorInactive';
Paul Greysonc17278a2013-03-23 10:17:12 -0700555 }
Paul Greyson127d7fb2013-03-25 23:39:20 -0700556 });
Paul Greysonc17278a2013-03-23 10:17:12 -0700557 }
558
559 // update switches
560 rings.each(ringUpdate);
561
Paul Greyson968d1b42013-03-23 16:58:41 -0700562
563 // Now setup the labels
564 // This is done separately because SVG draws in node order and we want the labels
565 // always on top
566 var labelRings = svg.selectAll('.labelRing').data(createRingsFromModel(model));
567
Paul Greyson421bfcd2013-03-27 22:22:09 -0700568 d3.select(document.body).on('mousemove', function () {
569 if (!d3.select('#topology').classed('linking')) {
570 return;
571 }
572 var linkVector = document.getElementById('linkVector');
573 if (!linkVector) {
574 return;
575 }
576 linkVector = d3.select(linkVector);
577
578 var highlighted = svg.selectAll('.highlight')[0];
579 var s1 = null, s2 = null;
580 if (highlighted.length > 1) {
581 var s1 = d3.select(highlighted[0]);
582 var s2 = d3.select(highlighted[1]);
583
584 } else if (highlighted.length > 0) {
585 var s1 = d3.select(highlighted[0]);
586 }
587 var src = s1;
588 if (s2 && !s2.data()[0].target) {
589 src = s2;
590 }
591 if (src) {
592 linkVector.attr('d', function () {
593 var srcPt = document.querySelector('svg').createSVGPoint();
594 srcPt.x = src.attr('x');
595 srcPt.y = src.attr('y');
596 srcPt = srcPt.matrixTransform(src[0][0].getCTM());
597
598 var svg = document.getElementById('topology');
599 var mouse = d3.mouse(viewbox);
600 var dstPt = document.querySelector('svg').createSVGPoint();
601 dstPt.x = mouse[0];
602 dstPt.y = mouse[1];
603 dstPt = dstPt.matrixTransform(viewbox.getCTM());
604
605 return line([srcPt, dstPt]);
606 });
607 }
608 });
609
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700610 d3.select(document.body).on('mouseup', function () {
611 function clearHighlight() {
612 svg.selectAll('circle').each(function (data) {
613 data.mouseDown = false;
Paul Greyson72f18852013-03-27 15:56:11 -0700614 d3.select('#topology').classed('linking', false);
Paul Greyson8d1c6362013-03-27 13:05:24 -0700615 mouseOutSwitch(data);
Paul Greyson421bfcd2013-03-27 22:22:09 -0700616 });
617 d3.select('#linkVector').remove();
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700618 };
619
Paul Greyson72f18852013-03-27 15:56:11 -0700620 d3.selectAll('.nodrop').classed('nodrop', false);
621
Paul Greyson084779b2013-03-27 13:55:49 -0700622 function removeLink(link) {
623 var path1 = document.getElementById(link['src-switch'] + '=>' + link['dst-switch']);
624 var path2 = document.getElementById(link['dst-switch'] + '=>' + link['src-switch']);
625
626 if (path1) {
627 setPending(d3.select(path1));
628 }
629 if (path2) {
630 setPending(d3.select(path2));
631 }
632
633 linkDown(link);
634 }
635
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700636
Paul Greyson421bfcd2013-03-27 22:22:09 -0700637 var highlighted = svg.selectAll('.highlight')[0];
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700638 if (highlighted.length == 2) {
Paul Greyson421bfcd2013-03-27 22:22:09 -0700639 var s1Data = highlighted[0].__data__;
640 var s2Data = highlighted[1].__data__;
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700641
642 var srcData, dstData;
643 if (s1Data.target) {
644 dstData = s1Data;
645 srcData = s2Data;
646 } else {
647 dstData = s2Data;
648 srcData = s1Data;
649 }
650
651 if (s1Data.className == 'edge' && s2Data.className == 'edge') {
652 var prompt = 'Create flow from ' + srcData.dpid + ' to ' + dstData.dpid + '?';
653 if (confirm(prompt)) {
654 alert('do create flow');
655 } else {
656 alert('do not create flow');
657 }
658 } else {
659 var map = linkMap[srcData.dpid];
660 if (map && map[dstData.dpid]) {
661 var prompt = 'Remove link between ' + srcData.dpid + ' and ' + dstData.dpid + '?';
662 if (confirm(prompt)) {
Paul Greyson084779b2013-03-27 13:55:49 -0700663 removeLink(map[dstData.dpid]);
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700664 }
665 } else {
Paul Greyson8d1c6362013-03-27 13:05:24 -0700666 map = linkMap[dstData.dpid];
667 if (map && map[srcData.dpid]) {
668 var prompt = 'Remove link between ' + dstData.dpid + ' and ' + srcData.dpid + '?';
669 if (confirm(prompt)) {
Paul Greyson084779b2013-03-27 13:55:49 -0700670 removeLink(map[srcData.dpid]);
Paul Greyson8d1c6362013-03-27 13:05:24 -0700671 }
672 } else {
673 var prompt = 'Create link between ' + srcData.dpid + ' and ' + dstData.dpid + '?';
674 if (confirm(prompt)) {
Paul Greyson40c8a592013-03-27 14:10:33 -0700675 var link1 = {
676 'src-switch': srcData.dpid,
Paul Greyson2913af82013-03-27 14:53:17 -0700677 'src-port': 1,
Paul Greyson40c8a592013-03-27 14:10:33 -0700678 'dst-switch': dstData.dpid,
Paul Greyson2913af82013-03-27 14:53:17 -0700679 'dst-port': 1,
Paul Greyson40c8a592013-03-27 14:10:33 -0700680 pending: true
681 };
682 pendingLinks[makeLinkKey(link1)] = link1;
683 var link2 = {
684 'src-switch': dstData.dpid,
Paul Greyson2913af82013-03-27 14:53:17 -0700685 'src-port': 1,
Paul Greyson40c8a592013-03-27 14:10:33 -0700686 'dst-switch': srcData.dpid,
Paul Greyson2913af82013-03-27 14:53:17 -0700687 'dst-port': 1,
Paul Greyson40c8a592013-03-27 14:10:33 -0700688 pending: true
689 };
690 pendingLinks[makeLinkKey(link2)] = link2;
691 updateTopology(svg, model);
692
Paul Greyson2913af82013-03-27 14:53:17 -0700693 linkUp(link1);
Paul Greyson40c8a592013-03-27 14:10:33 -0700694
695 // remove the pending link after 10s
696 setTimeout(function () {
697 delete pendingLinks[makeLinkKey(link1)];
698 delete pendingLinks[makeLinkKey(link2)];
699
700 updateTopology(svg, model);
701 }, 10000);
Paul Greyson8d1c6362013-03-27 13:05:24 -0700702 }
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700703 }
704 }
705 }
706
707 clearHighlight();
708 } else {
709 clearHighlight();
710 }
711
712 });
713
Paul Greyson9066ab02013-03-23 18:15:41 -0700714 function labelRingEnter(data) {
Paul Greyson644d92a2013-03-23 18:00:40 -0700715 if (!data.length) {
Paul Greyson968d1b42013-03-23 16:58:41 -0700716 return;
717 }
718
719 // create the nodes
720 var nodes = d3.select(this).selectAll("g")
Paul Greyson644d92a2013-03-23 18:00:40 -0700721 .data(data, function (data) {
722 return data.dpid;
723 })
Paul Greyson968d1b42013-03-23 16:58:41 -0700724 .enter().append("svg:g")
725 .classed('nolabel', true)
Paul Greyson9066ab02013-03-23 18:15:41 -0700726 .attr("id", function (data) {
Paul Greyson644d92a2013-03-23 18:00:40 -0700727 return data.dpid + '-label';
Paul Greyson968d1b42013-03-23 16:58:41 -0700728 })
Paul Greyson644d92a2013-03-23 18:00:40 -0700729 .attr("transform", function(data, i) {
730 return "rotate(" + data.angle+ ")translate(" + data.radius * 150 + ")rotate(" + (-data.angle) + ")";
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700731 })
Paul Greyson968d1b42013-03-23 16:58:41 -0700732
733 // add the text nodes which show on mouse over
734 nodes.append("svg:text")
Paul Greyson127d7fb2013-03-25 23:39:20 -0700735 .text(function (data) {return data.dpid;})
Paul Greyson9066ab02013-03-23 18:15:41 -0700736 .attr("x", function (data) {
737 if (data.angle <= 90 || data.angle >= 270 && data.angle <= 360) {
738 if (data.className == 'edge') {
Paul Greyson1eb2dd12013-03-23 18:22:00 -0700739 return - data.width*3 - 4;
Paul Greyson9066ab02013-03-23 18:15:41 -0700740 } else {
Paul Greyson1eb2dd12013-03-23 18:22:00 -0700741 return - data.width - 4;
Paul Greyson9066ab02013-03-23 18:15:41 -0700742 }
743 } else {
744 if (data.className == 'edge') {
745 return data.width*3 + 4;
746 } else {
747 return data.width + 4;
748 }
749 }
750 })
751 .attr("y", function (data) {
752 var y;
753 if (data.angle <= 90 || data.angle >= 270 && data.angle <= 360) {
754 if (data.className == 'edge') {
755 y = data.width*3/2 + 4;
756 } else {
757 y = data.width/2 + 4;
758 }
759 } else {
760 if (data.className == 'edge') {
761 y = data.width*3/2 + 4;
762 } else {
763 y = data.width/2 + 4;
764 }
765 }
766 return y - 6;
767 })
768 .attr("text-anchor", function (data) {
769 if (data.angle <= 90 || data.angle >= 270 && data.angle <= 360) {
770 return "end";
771 } else {
772 return "start";
773 }
774 })
775 .attr("transform", function(data) {
Paul Greyson968d1b42013-03-23 16:58:41 -0700776 var m = document.querySelector('#viewbox').getTransformToElement().inverse();
777 if (data.scale) {
778 m = m.scale(data.scale);
779 }
780 return "matrix( " + m.a + " " + m.b + " " + m.c + " " + m.d + " " + m.e + " " + m.f + " )";
781 })
782 }
783
784 labelRings.enter().append("svg:g")
785 .attr("class", "textRing")
786 .each(labelRingEnter);
787
Paul Greysonc17278a2013-03-23 10:17:12 -0700788 // switches should not change during operation of the ui so no
789 // rings.exit()
790
791
Paul Greysond1a22d92013-03-19 12:15:19 -0700792 // DRAW THE LINKS
Paul Greysond1a22d92013-03-19 12:15:19 -0700793
Paul Greysonc17278a2013-03-23 10:17:12 -0700794 // key on link dpids since these will come/go during demo
Paul Greyson40c8a592013-03-27 14:10:33 -0700795 var links = d3.select('svg').selectAll('.link').data(links, function (d) {
Paul Greysonc17278a2013-03-23 10:17:12 -0700796 return d['src-switch']+'->'+d['dst-switch'];
797 });
798
799 // add new links
Paul Greysonb367de22013-03-23 11:09:11 -0700800 links.enter().append("svg:path")
Paul Greyson56378ed2013-03-26 23:17:36 -0700801 .attr("class", "link");
802
Paul Greyson084779b2013-03-27 13:55:49 -0700803 links.attr('id', function (d) {
Paul Greyson40c8a592013-03-27 14:10:33 -0700804 return makeLinkKey(d);
Paul Greyson084779b2013-03-27 13:55:49 -0700805 })
Paul Greyson56378ed2013-03-26 23:17:36 -0700806 .attr("d", function (d) {
Paul Greyson084779b2013-03-27 13:55:49 -0700807 var src = d3.select(document.getElementById(d['src-switch']));
808 var dst = d3.select(document.getElementById(d['dst-switch']));
Paul Greysonc17278a2013-03-23 10:17:12 -0700809
Paul Greyson084779b2013-03-27 13:55:49 -0700810 var srcPt = document.querySelector('svg').createSVGPoint();
811 srcPt.x = src.attr('x');
812 srcPt.y = src.attr('y');
813 srcPt = srcPt.matrixTransform(src[0][0].getCTM());
Paul Greysond1a22d92013-03-19 12:15:19 -0700814
Paul Greyson084779b2013-03-27 13:55:49 -0700815 var dstPt = document.querySelector('svg').createSVGPoint();
816 dstPt.x = dst.attr('x');
Paul Greyson421bfcd2013-03-27 22:22:09 -0700817 dstPt.y = dst.attr('y');
Paul Greyson084779b2013-03-27 13:55:49 -0700818 dstPt = dstPt.matrixTransform(dst[0][0].getCTM());
Paul Greysond1a22d92013-03-19 12:15:19 -0700819
Paul Greyson084779b2013-03-27 13:55:49 -0700820 var midPt = document.querySelector('svg').createSVGPoint();
821 midPt.x = (srcPt.x + dstPt.x)/2;
822 midPt.y = (srcPt.y + dstPt.y)/2;
Paul Greysond1a22d92013-03-19 12:15:19 -0700823
Paul Greyson084779b2013-03-27 13:55:49 -0700824 return line([srcPt, midPt, dstPt]);
825 })
Paul Greyson40c8a592013-03-27 14:10:33 -0700826 .attr("marker-mid", function(d) { return "url(#arrow)"; })
827 .classed('pending', function (d) {
828 return d.pending;
829 });
Paul Greysonc17278a2013-03-23 10:17:12 -0700830
Paul Greyson56378ed2013-03-26 23:17:36 -0700831
Paul Greysonc17278a2013-03-23 10:17:12 -0700832 // remove old links
833 links.exit().remove();
Paul Greyson644d92a2013-03-23 18:00:40 -0700834
Paul Greyson127d7fb2013-03-25 23:39:20 -0700835
836 drawFlows();
Paul Greysond1a22d92013-03-19 12:15:19 -0700837}
838
839function updateControllers(model) {
840 var controllers = d3.select('#controllerList').selectAll('.controller').data(model.controllers);
Paul Greyson3e142162013-03-19 13:56:17 -0700841 controllers.enter().append('div')
Paul Greysone262a292013-03-23 10:35:23 -0700842 .each(function (c) {
843 controllerColorMap[c] = colors.pop();
844 d3.select(document.body).classed(controllerColorMap[c] + '-selected', true);
845 })
846 .text(function (d) {
847 return d;
Paul Greyson2913af82013-03-27 14:53:17 -0700848 })
849 .append('div')
Paul Greyson8247c3f2013-03-28 00:24:02 -0700850 .attr('class', 'black-eye');
Paul Greysonbcd3c772013-03-21 13:16:44 -0700851
Paul Greysone262a292013-03-23 10:35:23 -0700852 controllers.attr('class', function (d) {
Paul Greysoneed36352013-03-23 11:19:11 -0700853 var color = 'colorInactive';
Paul Greysonbcd3c772013-03-21 13:16:44 -0700854 if (model.activeControllers.indexOf(d) != -1) {
855 color = controllerColorMap[d];
Paul Greysond1a22d92013-03-19 12:15:19 -0700856 }
Paul Greysonbcd3c772013-03-21 13:16:44 -0700857 var className = 'controller ' + color;
858 return className;
Paul Greysond1a22d92013-03-19 12:15:19 -0700859 });
Paul Greysond1a22d92013-03-19 12:15:19 -0700860
Paul Greysone262a292013-03-23 10:35:23 -0700861 // this should never be needed
862 // controllers.exit().remove();
Paul Greysond1a22d92013-03-19 12:15:19 -0700863
Paul Greyson2913af82013-03-27 14:53:17 -0700864 controllers.on('dblclick', function (c) {
865 if (model.activeControllers.indexOf(c) != -1) {
866 var prompt = 'Dectivate ' + c + '?';
867 if (confirm(prompt)) {
868 controllerDown(c);
869 setPending(d3.select(this));
870 };
871 } else {
872 var prompt = 'Activate ' + c + '?';
873 if (confirm(prompt)) {
874 controllerUp(c);
875 setPending(d3.select(this));
876 };
877 }
878 });
879
Paul Greyson8247c3f2013-03-28 00:24:02 -0700880 controllers.select('.black-eye').on('click', function (c) {
Paul Greysonc3e21a02013-03-21 13:56:05 -0700881 var allSelected = true;
882 for (var key in controllerColorMap) {
883 if (!d3.select(document.body).classed(controllerColorMap[key] + '-selected')) {
884 allSelected = false;
885 break;
886 }
887 }
888 if (allSelected) {
889 for (var key in controllerColorMap) {
890 d3.select(document.body).classed(controllerColorMap[key] + '-selected', key == c)
891 }
892 } else {
893 for (var key in controllerColorMap) {
894 d3.select(document.body).classed(controllerColorMap[key] + '-selected', true)
895 }
896 }
897
898 // var selected = d3.select(document.body).classed(controllerColorMap[c] + '-selected');
899 // d3.select(document.body).classed(controllerColorMap[c] + '-selected', !selected);
Paul Greysond1a22d92013-03-19 12:15:19 -0700900 });
Paul Greyson8d1c6362013-03-27 13:05:24 -0700901
902
Paul Greyson740bdaf2013-03-18 16:10:48 -0700903}
904
Paul Greyson29aa98d2013-03-28 00:09:31 -0700905function sync(svg) {
Paul Greysonbcd3c772013-03-21 13:16:44 -0700906 var d = Date.now();
Paul Greysonb48943b2013-03-19 13:27:57 -0700907 updateModel(function (newModel) {
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700908// console.log('Update time: ' + (Date.now() - d)/1000 + 's');
Paul Greyson740bdaf2013-03-18 16:10:48 -0700909
Paul Greyson56378ed2013-03-26 23:17:36 -0700910 if (!model || JSON.stringify(model) != JSON.stringify(newModel)) {
Paul Greysonb48943b2013-03-19 13:27:57 -0700911 updateControllers(newModel);
Paul Greyson29aa98d2013-03-28 00:09:31 -0700912 updateSelectedFlows();
Paul Greysonb48943b2013-03-19 13:27:57 -0700913 updateTopology(svg, newModel);
914 } else {
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700915// console.log('no change');
Paul Greysonb48943b2013-03-19 13:27:57 -0700916 }
917 updateHeader(newModel);
918
Paul Greyson56378ed2013-03-26 23:17:36 -0700919 model = newModel;
Paul Greyson740bdaf2013-03-18 16:10:48 -0700920
921 // do it again in 1s
922 setTimeout(function () {
Paul Greysona36a9232013-03-22 22:41:27 -0700923 sync(svg)
Paul Greysond1a22d92013-03-19 12:15:19 -0700924 }, 1000);
Paul Greyson6f86d1e2013-03-18 14:40:39 -0700925 });
926}
Paul Greyson740bdaf2013-03-18 16:10:48 -0700927
Paul Greyson38d8bde2013-03-22 22:07:35 -0700928svg = createTopologyView();
Paul Greyson29aa98d2013-03-28 00:09:31 -0700929updateSelectedFlows();
930
931d3.select('#showFlowChooser').on('click', function () {
932 showFlowChooser();
933});
934
Paul Greyson72f18852013-03-27 15:56:11 -0700935
Paul Greyson38d8bde2013-03-22 22:07:35 -0700936// workaround for Chrome v25 bug
937// if executed immediately, the view box transform logic doesn't work properly
938// fixed in Chrome v27
939setTimeout(function () {
940 // workaround for another Chrome v25 bug
941 // viewbox transform stuff doesn't work in combination with browser zoom
Paul Greysonc17278a2013-03-23 10:17:12 -0700942 // also works in Chrome v27
Paul Greyson38d8bde2013-03-22 22:07:35 -0700943 d3.select('#svg-container').style('zoom', window.document.body.clientWidth/window.document.width);
Paul Greyson29aa98d2013-03-28 00:09:31 -0700944 sync(svg);
Paul Greyson38d8bde2013-03-22 22:07:35 -0700945}, 100);