blob: 0fa5fa3e20473690b841aae91622ebba989ad79d [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',
Paul Greyson3e142162013-03-19 13:56:17 -070027 'color7',
28 'color8',
29 'color9',
Paul Greyson5cc35f02013-03-28 10:07:36 -070030// 'color11',
Paul Greyson127d7fb2013-03-25 23:39:20 -070031 'color12'
32];
Paul Greyson01a5dff2013-03-19 15:50:14 -070033colors.reverse();
Paul Greysond1a22d92013-03-19 12:15:19 -070034
35var controllerColorMap = {};
36
Paul Greyson084779b2013-03-27 13:55:49 -070037function setPending(selection) {
38 selection.classed('pending', false);
39 setTimeout(function () {
40 selection.classed('pending', true);
41 })
42}
Paul Greysond1a22d92013-03-19 12:15:19 -070043
Paul Greyson740bdaf2013-03-18 16:10:48 -070044function createTopologyView() {
Paul Greyson56378ed2013-03-26 23:17:36 -070045
46 window.addEventListener('resize', function () {
47 // this is too slow. instead detect first resize event and hide the paths that have explicit matrix applied
48 // either that or is it possible to position the paths so they get the automatic transform as well?
Paul Greyson5cc35f02013-03-28 10:07:36 -070049// updateTopology();
Paul Greyson56378ed2013-03-26 23:17:36 -070050 });
51
Paul Greysonb367de22013-03-23 11:09:11 -070052 var svg = d3.select('#svg-container').append('svg:svg');
53
54 svg.append("svg:defs").append("svg:marker")
55 .attr("id", "arrow")
56 .attr("viewBox", "0 -5 10 10")
57 .attr("refX", -1)
58 .attr("markerWidth", 5)
59 .attr("markerHeight", 5)
60 .attr("orient", "auto")
61 .append("svg:path")
Paul Greyson45303ac2013-03-23 16:44:01 -070062 .attr("d", "M0,-3L10,0L0,3");
Paul Greysonb367de22013-03-23 11:09:11 -070063
64 return svg.append('svg:svg').attr('id', 'viewBox').attr('viewBox', '0 0 1000 1000').attr('preserveAspectRatio', 'none').
Paul Greyson952ccb62013-03-18 22:22:08 -070065 attr('id', 'viewbox').append('svg:g').attr('transform', 'translate(500 500)');
Paul Greyson740bdaf2013-03-18 16:10:48 -070066}
67
Paul Greyson29aa98d2013-03-28 00:09:31 -070068var selectedFlows = [null, null, null];
Paul Greyson127d7fb2013-03-25 23:39:20 -070069
70function drawFlows() {
71 // DRAW THE FLOWS
Paul Greyson29aa98d2013-03-28 00:09:31 -070072 var flows = d3.select('svg').selectAll('.flow').data(selectedFlows, function (d) {
73 return d ? d.flowId.value : null;
Paul Greyson127d7fb2013-03-25 23:39:20 -070074 });
75
Paul Greyson29aa98d2013-03-28 00:09:31 -070076 flows.enter().append("svg:path").attr('class', 'flow')
77 .attr('stroke-dasharray', '4, 10')
78 .append('svg:animate')
79 .attr('attributeName', 'stroke-dashoffset')
80 .attr('attributeType', 'xml')
81 .attr('from', '500')
82 .attr('to', '-500')
83 .attr('dur', '20s')
84 .attr('repeatCount', 'indefinite');
85
86
87 flows.attr('d', function (d) {
88 if (!d) {
Paul Greyson127d7fb2013-03-25 23:39:20 -070089 return;
90 }
91 var pts = [];
Paul Greyson29aa98d2013-03-28 00:09:31 -070092 d.dataPath.flowEntries.forEach(function (flowEntry) {
Paul Greyson127d7fb2013-03-25 23:39:20 -070093 var s = d3.select(document.getElementById(flowEntry.dpid.value));
94 var pt = document.querySelector('svg').createSVGPoint();
95 pt.x = s.attr('x');
96 pt.y = s.attr('y');
97 pt = pt.matrixTransform(s[0][0].getCTM());
98 pts.push(pt);
99 });
100 return line(pts);
101 })
Paul Greyson127d7fb2013-03-25 23:39:20 -0700102
Paul Greyson56378ed2013-03-26 23:17:36 -0700103 // "marching ants"
Paul Greyson29aa98d2013-03-28 00:09:31 -0700104 flows.select('animate').attr('from', 500);
105
106 flows.exit().remove();
Paul Greyson127d7fb2013-03-25 23:39:20 -0700107}
108
Paul Greyson29aa98d2013-03-28 00:09:31 -0700109function showFlowChooser() {
110 function rowEnter(d) {
Paul Greyson127d7fb2013-03-25 23:39:20 -0700111 var row = d3.select(this);
112
Paul Greyson127d7fb2013-03-25 23:39:20 -0700113 row.append('div')
Paul Greyson8247c3f2013-03-28 00:24:02 -0700114 .classed('black-eye', true).
Paul Greyson29aa98d2013-03-28 00:09:31 -0700115 on('click', function () {
116 selectedFlows.unshift(d);
117 selectedFlows = selectedFlows.slice(0, 3);
118
119 updateSelectedFlows();
Paul Greyson5cc35f02013-03-28 10:07:36 -0700120 updateTopology();
Paul Greyson127d7fb2013-03-25 23:39:20 -0700121 });
122
123 row.append('div')
Paul Greyson29aa98d2013-03-28 00:09:31 -0700124 .classed('flowId', true)
125 .text(function (d) {
126 return d.flowId.value;
127 });
Paul Greyson127d7fb2013-03-25 23:39:20 -0700128
129 row.append('div')
Paul Greyson29aa98d2013-03-28 00:09:31 -0700130 .classed('srcDPID', true)
131 .text(function (d) {
132 return d.dataPath.srcPort.dpid.value;
133 });
134
Paul Greyson127d7fb2013-03-25 23:39:20 -0700135
136 row.append('div')
Paul Greyson29aa98d2013-03-28 00:09:31 -0700137 .classed('dstDPID', true)
138 .text(function (d) {
139 return d.dataPath.dstPort.dpid.value;
140 });
Paul Greyson127d7fb2013-03-25 23:39:20 -0700141
Paul Greyson127d7fb2013-03-25 23:39:20 -0700142 }
143
Paul Greyson29aa98d2013-03-28 00:09:31 -0700144 var flows = d3.select('#flowChooser')
145 .append('div')
146 .style('pointer-events', 'auto')
Paul Greyson127d7fb2013-03-25 23:39:20 -0700147 .selectAll('.selectedFlow')
Paul Greyson29aa98d2013-03-28 00:09:31 -0700148 .data(model.flows)
Paul Greyson127d7fb2013-03-25 23:39:20 -0700149 .enter()
150 .append('div')
151 .classed('selectedFlow', true)
152 .each(rowEnter);
153
Paul Greyson29aa98d2013-03-28 00:09:31 -0700154 setTimeout(function () {
155 d3.select(document.body).on('click', function () {
156 d3.select('#flowChooser').html('');
157 d3.select(document.body).on('click', null);
158 });
159 }, 0);
160}
161
162function updateSelectedFlows() {
163 function rowEnter(d) {
164 var row = d3.select(this);
165 row.append('div').classed('flowId', true);
166 row.append('div').classed('srcDPID', true);
167 row.append('div').classed('dstDPID', true);
168 row.append('div').classed('iperf', true);
169 }
170
171 function rowUpdate(d) {
172 var row = d3.select(this);
173 row.select('.flowId')
174 .text(function (d) {
175 if (d) {
176 return d.flowId.value;
177 }
178 });
179
180 row.select('.srcDPID')
181 .text(function (d) {
182 if (d) {
183 return d.dataPath.srcPort.dpid.value;
184 }
185 });
186
187 row.select('.dstDPID')
188 .text(function (d) {
189 if (d) {
190 return d.dataPath.dstPort.dpid.value;
191 }
192 });
193 }
194
195 var flows = d3.select('#selectedFlows')
196 .selectAll('.selectedFlow')
197 .data(selectedFlows);
198
199 flows.enter()
200 .append('div')
201 .classed('selectedFlow', true)
202 .each(rowEnter);
203
204 flows.each(rowUpdate);
205
206 flows.exit().remove();
Paul Greyson127d7fb2013-03-25 23:39:20 -0700207
208 return flows;
209}
210
Paul Greysond1a22d92013-03-19 12:15:19 -0700211function updateHeader(model) {
Paul Greysonb48943b2013-03-19 13:27:57 -0700212 d3.select('#lastUpdate').text(new Date());
Paul Greyson952ccb62013-03-18 22:22:08 -0700213 d3.select('#activeSwitches').text(model.edgeSwitches.length + model.aggregationSwitches.length + model.coreSwitches.length);
214 d3.select('#activeFlows').text(model.flows.length);
215}
216
217function toRadians (angle) {
218 return angle * (Math.PI / 180);
Paul Greyson740bdaf2013-03-18 16:10:48 -0700219}
220
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700221var widths = {
222 edge: 6,
223 aggregation: 12,
224 core: 18
225}
226
Paul Greysonc17278a2013-03-23 10:17:12 -0700227function createRingsFromModel(model) {
Paul Greyson740bdaf2013-03-18 16:10:48 -0700228 var rings = [{
229 radius: 3,
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700230 width: widths.edge,
Paul Greyson952ccb62013-03-18 22:22:08 -0700231 switches: model.edgeSwitches,
Paul Greysonde7fad52013-03-19 12:47:32 -0700232 className: 'edge',
233 angles: []
Paul Greyson740bdaf2013-03-18 16:10:48 -0700234 }, {
Paul Greysond1a22d92013-03-19 12:15:19 -0700235 radius: 2.25,
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700236 width: widths.aggregation,
Paul Greyson952ccb62013-03-18 22:22:08 -0700237 switches: model.aggregationSwitches,
Paul Greysonde7fad52013-03-19 12:47:32 -0700238 className: 'aggregation',
239 angles: []
Paul Greyson740bdaf2013-03-18 16:10:48 -0700240 }, {
Paul Greyson127d7fb2013-03-25 23:39:20 -0700241 radius: 0.75,
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700242 width: widths.core,
Paul Greyson952ccb62013-03-18 22:22:08 -0700243 switches: model.coreSwitches,
Paul Greysonde7fad52013-03-19 12:47:32 -0700244 className: 'core',
245 angles: []
Paul Greyson740bdaf2013-03-18 16:10:48 -0700246 }];
247
Paul Greysonde7fad52013-03-19 12:47:32 -0700248
249 var aggRanges = {};
250
251 // arrange edge switches at equal increments
252 var k = 360 / rings[0].switches.length;
253 rings[0].switches.forEach(function (s, i) {
254 var angle = k * i;
255
256 rings[0].angles[i] = angle;
257
258 // record the angle for the agg switch layout
259 var dpid = s.dpid.split(':');
Paul Greyson832d2202013-03-21 13:27:56 -0700260 dpid[7] = '01'; // the last component of the agg switch is always '01'
Paul Greysonde7fad52013-03-19 12:47:32 -0700261 var aggdpid = dpid.join(':');
262 var aggRange = aggRanges[aggdpid];
263 if (!aggRange) {
264 aggRange = aggRanges[aggdpid] = {};
265 aggRange.min = aggRange.max = angle;
266 } else {
267 aggRange.max = angle;
268 }
Paul Greysonde7fad52013-03-19 12:47:32 -0700269 });
270
271 // arrange aggregation switches to "fan out" to edge switches
272 k = 360 / rings[1].switches.length;
273 rings[1].switches.forEach(function (s, i) {
274// rings[1].angles[i] = k * i;
275 var range = aggRanges[s.dpid];
276
Paul Greyson832d2202013-03-21 13:27:56 -0700277 rings[1].angles[i] = (range.min + range.max)/2;
Paul Greysonde7fad52013-03-19 12:47:32 -0700278 });
279
Paul Greyson3f890b62013-03-22 17:39:36 -0700280 // find the association between core switches and aggregation switches
281 var aggregationSwitchMap = {};
282 model.aggregationSwitches.forEach(function (s, i) {
Paul Greysonc17278a2013-03-23 10:17:12 -0700283 aggregationSwitchMap[s.dpid] = i;
Paul Greyson3f890b62013-03-22 17:39:36 -0700284 });
285
Paul Greyson3f890b62013-03-22 17:39:36 -0700286 // put core switches next to linked aggregation switches
Paul Greysonde7fad52013-03-19 12:47:32 -0700287 k = 360 / rings[2].switches.length;
288 rings[2].switches.forEach(function (s, i) {
Paul Greyson3f890b62013-03-22 17:39:36 -0700289// rings[2].angles[i] = k * i;
Paul Greysonc17278a2013-03-23 10:17:12 -0700290 var associatedAggregationSwitches = model.configuration.association[s.dpid];
291 // TODO: go between if there are multiple
292 var index = aggregationSwitchMap[associatedAggregationSwitches[0]];
293
294 rings[2].angles[i] = rings[1].angles[index];
Paul Greysonde7fad52013-03-19 12:47:32 -0700295 });
296
Paul Greyson644d92a2013-03-23 18:00:40 -0700297 // TODO: construct this form initially rather than converting. it works better because
298 // it allows binding by dpid
299 var testRings = [];
300 rings.forEach(function (ring) {
301 var testRing = [];
302 ring.switches.forEach(function (s, i) {
303 var testSwitch = {
304 dpid: s.dpid,
305 state: s.state,
306 radius: ring.radius,
307 width: ring.width,
308 className: ring.className,
309 angle: ring.angles[i],
310 controller: s.controller
Paul Greyson127d7fb2013-03-25 23:39:20 -0700311 };
Paul Greyson644d92a2013-03-23 18:00:40 -0700312 testRing.push(testSwitch);
313 });
Paul Greyson6d9ed862013-03-23 17:37:15 -0700314
315
Paul Greyson644d92a2013-03-23 18:00:40 -0700316 testRings.push(testRing);
317 });
Paul Greyson6d9ed862013-03-23 17:37:15 -0700318
319
Paul Greyson644d92a2013-03-23 18:00:40 -0700320// return rings;
321 return testRings;
Paul Greysonc17278a2013-03-23 10:17:12 -0700322}
323
Paul Greyson40c8a592013-03-27 14:10:33 -0700324function makeLinkKey(link) {
325 return link['src-switch'] + '=>' + link['dst-switch'];
326}
327
328function createLinkMap(links) {
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700329 var linkMap = {};
Paul Greyson40c8a592013-03-27 14:10:33 -0700330 links.forEach(function (link) {
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700331 var srcDPID = link['src-switch'];
332 var dstDPID = link['dst-switch'];
333
334 var srcMap = linkMap[srcDPID] || {};
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700335
Paul Greyson8d1c6362013-03-27 13:05:24 -0700336 srcMap[dstDPID] = link;
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700337
338 linkMap[srcDPID] = srcMap;
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700339 });
340 return linkMap;
341}
342
Paul Greyson5cc35f02013-03-28 10:07:36 -0700343// removes links from the pending list that are now in the model
344function reconcilePendingLinks(model) {
Paul Greyson40c8a592013-03-27 14:10:33 -0700345 var links = [];
346 model.links.forEach(function (link) {
347 links.push(link);
348 delete pendingLinks[makeLinkKey(link)]
349 })
350 var linkId;
351 for (linkId in pendingLinks) {
352 links.push(pendingLinks[linkId]);
353 }
Paul Greyson5cc35f02013-03-28 10:07:36 -0700354 return links
355}
Paul Greyson40c8a592013-03-27 14:10:33 -0700356
Paul Greyson5cc35f02013-03-28 10:07:36 -0700357updateTopology = function() {
358
359 // DRAW THE SWITCHES
360 var rings = svg.selectAll('.ring').data(createRingsFromModel(model));
361
362 var links = reconcilePendingLinks(model);
Paul Greyson40c8a592013-03-27 14:10:33 -0700363 var linkMap = createLinkMap(links);
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700364// var flowMap = createFlowMap(model);
365
Paul Greyson8d1c6362013-03-27 13:05:24 -0700366 function mouseOverSwitch(data) {
Paul Greyson72f18852013-03-27 15:56:11 -0700367
368 d3.event.preventDefault();
369
Paul Greyson5cc35f02013-03-28 10:07:36 -0700370 d3.select(document.getElementById(data.dpid + '-label')).classed('nolabel', false);
371
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700372 if (data.highlighted) {
373 return;
374 }
375
376 // only highlight valid link or flow destination by checking for class of existing highlighted circle
Paul Greyson421bfcd2013-03-27 22:22:09 -0700377 var highlighted = svg.selectAll('.highlight')[0];
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700378 if (highlighted.length == 1) {
Paul Greyson421bfcd2013-03-27 22:22:09 -0700379 var s = d3.select(highlighted[0]).select('circle');
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700380 // only allow links
381 // edge->edge (flow)
382 // aggregation->core
383 // core->core
384 if (data.className == 'edge' && !s.classed('edge') ||
385 data.className == 'core' && !s.classed('core') && !s.classed('aggregation') ||
386 data.className == 'aggregation' && !s.classed('core')) {
387 return;
388 }
389
390 // don't highlight if there's already a link or flow
391 // var map = linkMap[data.dpid];
392 // console.log(map);
393 // console.log(s.data()[0].dpid);
394 // console.log(map[s.data()[0].dpid]);
395 // if (map && map[s.data()[0].dpid]) {
396 // return;
397 // }
398
399 // the second highlighted switch is the target for a link or flow
400 data.target = true;
401 }
402
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700403 var node = d3.select(document.getElementById(data.dpid));
Paul Greyson421bfcd2013-03-27 22:22:09 -0700404 node.classed('highlight', true).select('circle').transition().duration(100).attr("r", widths.core);
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700405 data.highlighted = true;
406 node.moveToFront();
407 }
408
Paul Greyson8d1c6362013-03-27 13:05:24 -0700409 function mouseOutSwitch(data) {
Paul Greyson5cc35f02013-03-28 10:07:36 -0700410 d3.select(document.getElementById(data.dpid + '-label')).classed('nolabel', true);
411
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700412 if (data.mouseDown)
413 return;
414
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700415 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 Greyson8d1c6362013-03-27 13:05:24 -0700458 d3.event.stopPropagation();
Paul Greyson72f18852013-03-27 15:56:11 -0700459 d3.selectAll('.nodrop').classed('nodrop', false);
Paul Greyson8d1c6362013-03-27 13:05:24 -0700460 }
461 }
462
463 function doubleClickSwitch(data) {
Paul Greyson084779b2013-03-27 13:55:49 -0700464 var circle = d3.select(document.getElementById(data.dpid)).select('circle');
Paul Greyson8d1c6362013-03-27 13:05:24 -0700465 if (data.state == 'ACTIVE') {
466 var prompt = 'Deactivate ' + data.dpid + '?';
467 if (confirm(prompt)) {
468 switchDown(data);
Paul Greyson084779b2013-03-27 13:55:49 -0700469 setPending(circle);
Paul Greyson8d1c6362013-03-27 13:05:24 -0700470 }
471 } else {
472 var prompt = 'Activate ' + data.dpid + '?';
473 if (confirm(prompt)) {
474 switchUp(data);
Paul Greyson084779b2013-03-27 13:55:49 -0700475 setPending(circle);
Paul Greyson8d1c6362013-03-27 13:05:24 -0700476 }
477 }
478 }
479
Paul Greyson740bdaf2013-03-18 16:10:48 -0700480 function ringEnter(data, i) {
Paul Greyson644d92a2013-03-23 18:00:40 -0700481 if (!data.length) {
Paul Greyson740bdaf2013-03-18 16:10:48 -0700482 return;
483 }
484
Paul Greysonc17278a2013-03-23 10:17:12 -0700485 // create the nodes
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700486 var nodes = d3.select(this).selectAll("g")
Paul Greyson644d92a2013-03-23 18:00:40 -0700487 .data(data, function (data) {
488 return data.dpid;
489 })
Paul Greyson740bdaf2013-03-18 16:10:48 -0700490 .enter().append("svg:g")
Paul Greyson644d92a2013-03-23 18:00:40 -0700491 .attr("id", function (data, i) {
492 return data.dpid;
Paul Greyson23b0cd32013-03-18 23:45:48 -0700493 })
Paul Greyson644d92a2013-03-23 18:00:40 -0700494 .attr("transform", function(data, i) {
495 return "rotate(" + data.angle+ ")translate(" + data.radius * 150 + ")rotate(" + (-data.angle) + ")";
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700496 });
497
Paul Greysonc17278a2013-03-23 10:17:12 -0700498 // add the cirles representing the switches
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700499 nodes.append("svg:circle")
Paul Greyson644d92a2013-03-23 18:00:40 -0700500 .attr("transform", function(data, i) {
Paul Greysond1a22d92013-03-19 12:15:19 -0700501 var m = document.querySelector('#viewbox').getTransformToElement().inverse();
Paul Greysonf8f43172013-03-18 23:00:30 -0700502 if (data.scale) {
503 m = m.scale(data.scale);
504 }
505 return "matrix( " + m.a + " " + m.b + " " + m.c + " " + m.d + " " + m.e + " " + m.f + " )";
Paul Greyson952ccb62013-03-18 22:22:08 -0700506 })
Paul Greyson644d92a2013-03-23 18:00:40 -0700507 .attr("x", function (data) {
508 return -data.width / 2;
509 })
510 .attr("y", function (data) {
511 return -data.width / 2;
512 })
513 .attr("r", function (data) {
514 return data.width;
Paul Greyson127d7fb2013-03-25 23:39:20 -0700515 });
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700516
Paul Greysonc17278a2013-03-23 10:17:12 -0700517 // setup the mouseover behaviors
Paul Greyson8d1c6362013-03-27 13:05:24 -0700518 nodes.on('mouseover', mouseOverSwitch);
519 nodes.on('mouseout', mouseOutSwitch);
520 nodes.on('mouseup', mouseUpSwitch);
521 nodes.on('mousedown', mouseDownSwitch);
522
523 // only do switch up/down for core switches
524 if (i == 2) {
525 nodes.on('dblclick', doubleClickSwitch);
526 }
Paul Greyson740bdaf2013-03-18 16:10:48 -0700527 }
528
Paul Greysonc17278a2013-03-23 10:17:12 -0700529 // append switches
530 rings.enter().append("svg:g")
Paul Greyson740bdaf2013-03-18 16:10:48 -0700531 .attr("class", "ring")
532 .each(ringEnter);
Paul Greysonf8f43172013-03-18 23:00:30 -0700533
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700534
Paul Greysonc17278a2013-03-23 10:17:12 -0700535 function ringUpdate(data, i) {
Paul Greyson127d7fb2013-03-25 23:39:20 -0700536 var nodes = d3.select(this).selectAll("g")
Paul Greyson644d92a2013-03-23 18:00:40 -0700537 .data(data, function (data) {
538 return data.dpid;
Paul Greyson127d7fb2013-03-25 23:39:20 -0700539 });
Paul Greyson347fb742013-03-27 13:40:29 -0700540 nodes.select('circle')
541 .each(function (data) {
542 // if there's a pending state changed and then the state changes, clear the pending class
543 var circle = d3.select(this);
544 if (data.state === 'ACTIVE' && circle.classed('inactive') ||
545 data.state === 'INACTIVE' && circle.classed('active')) {
546 circle.classed('pending', false);
547 }
548 })
549 .attr('class', function (data) {
Paul Greyson8d1c6362013-03-27 13:05:24 -0700550 if (data.state === 'ACTIVE' && data.controller) {
Paul Greyson347fb742013-03-27 13:40:29 -0700551 return data.className + ' active ' + controllerColorMap[data.controller];
Paul Greysonc17278a2013-03-23 10:17:12 -0700552 } else {
Paul Greyson347fb742013-03-27 13:40:29 -0700553 return data.className + ' inactive ' + 'colorInactive';
Paul Greysonc17278a2013-03-23 10:17:12 -0700554 }
Paul Greyson127d7fb2013-03-25 23:39:20 -0700555 });
Paul Greysonc17278a2013-03-23 10:17:12 -0700556 }
557
558 // update switches
559 rings.each(ringUpdate);
560
Paul Greyson968d1b42013-03-23 16:58:41 -0700561
562 // Now setup the labels
563 // This is done separately because SVG draws in node order and we want the labels
564 // always on top
565 var labelRings = svg.selectAll('.labelRing').data(createRingsFromModel(model));
566
Paul Greyson421bfcd2013-03-27 22:22:09 -0700567 d3.select(document.body).on('mousemove', function () {
568 if (!d3.select('#topology').classed('linking')) {
569 return;
570 }
571 var linkVector = document.getElementById('linkVector');
572 if (!linkVector) {
573 return;
574 }
575 linkVector = d3.select(linkVector);
576
577 var highlighted = svg.selectAll('.highlight')[0];
578 var s1 = null, s2 = null;
579 if (highlighted.length > 1) {
580 var s1 = d3.select(highlighted[0]);
581 var s2 = d3.select(highlighted[1]);
582
583 } else if (highlighted.length > 0) {
584 var s1 = d3.select(highlighted[0]);
585 }
586 var src = s1;
587 if (s2 && !s2.data()[0].target) {
588 src = s2;
589 }
590 if (src) {
591 linkVector.attr('d', function () {
592 var srcPt = document.querySelector('svg').createSVGPoint();
593 srcPt.x = src.attr('x');
594 srcPt.y = src.attr('y');
595 srcPt = srcPt.matrixTransform(src[0][0].getCTM());
596
597 var svg = document.getElementById('topology');
598 var mouse = d3.mouse(viewbox);
599 var dstPt = document.querySelector('svg').createSVGPoint();
600 dstPt.x = mouse[0];
601 dstPt.y = mouse[1];
602 dstPt = dstPt.matrixTransform(viewbox.getCTM());
603
604 return line([srcPt, dstPt]);
605 });
606 }
607 });
608
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700609 d3.select(document.body).on('mouseup', function () {
610 function clearHighlight() {
611 svg.selectAll('circle').each(function (data) {
612 data.mouseDown = false;
Paul Greyson72f18852013-03-27 15:56:11 -0700613 d3.select('#topology').classed('linking', false);
Paul Greyson8d1c6362013-03-27 13:05:24 -0700614 mouseOutSwitch(data);
Paul Greyson421bfcd2013-03-27 22:22:09 -0700615 });
616 d3.select('#linkVector').remove();
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700617 };
618
Paul Greyson72f18852013-03-27 15:56:11 -0700619 d3.selectAll('.nodrop').classed('nodrop', false);
620
Paul Greyson084779b2013-03-27 13:55:49 -0700621 function removeLink(link) {
622 var path1 = document.getElementById(link['src-switch'] + '=>' + link['dst-switch']);
623 var path2 = document.getElementById(link['dst-switch'] + '=>' + link['src-switch']);
624
625 if (path1) {
626 setPending(d3.select(path1));
627 }
628 if (path2) {
629 setPending(d3.select(path2));
630 }
631
632 linkDown(link);
633 }
634
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700635
Paul Greyson421bfcd2013-03-27 22:22:09 -0700636 var highlighted = svg.selectAll('.highlight')[0];
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700637 if (highlighted.length == 2) {
Paul Greyson421bfcd2013-03-27 22:22:09 -0700638 var s1Data = highlighted[0].__data__;
639 var s2Data = highlighted[1].__data__;
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700640
641 var srcData, dstData;
642 if (s1Data.target) {
643 dstData = s1Data;
644 srcData = s2Data;
645 } else {
646 dstData = s2Data;
647 srcData = s1Data;
648 }
649
650 if (s1Data.className == 'edge' && s2Data.className == 'edge') {
651 var prompt = 'Create flow from ' + srcData.dpid + ' to ' + dstData.dpid + '?';
652 if (confirm(prompt)) {
653 alert('do create flow');
654 } else {
655 alert('do not create flow');
656 }
657 } else {
658 var map = linkMap[srcData.dpid];
659 if (map && map[dstData.dpid]) {
660 var prompt = 'Remove link between ' + srcData.dpid + ' and ' + dstData.dpid + '?';
661 if (confirm(prompt)) {
Paul Greyson084779b2013-03-27 13:55:49 -0700662 removeLink(map[dstData.dpid]);
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700663 }
664 } else {
Paul Greyson8d1c6362013-03-27 13:05:24 -0700665 map = linkMap[dstData.dpid];
666 if (map && map[srcData.dpid]) {
667 var prompt = 'Remove link between ' + dstData.dpid + ' and ' + srcData.dpid + '?';
668 if (confirm(prompt)) {
Paul Greyson084779b2013-03-27 13:55:49 -0700669 removeLink(map[srcData.dpid]);
Paul Greyson8d1c6362013-03-27 13:05:24 -0700670 }
671 } else {
672 var prompt = 'Create link between ' + srcData.dpid + ' and ' + dstData.dpid + '?';
673 if (confirm(prompt)) {
Paul Greyson40c8a592013-03-27 14:10:33 -0700674 var link1 = {
675 'src-switch': srcData.dpid,
Paul Greyson2913af82013-03-27 14:53:17 -0700676 'src-port': 1,
Paul Greyson40c8a592013-03-27 14:10:33 -0700677 'dst-switch': dstData.dpid,
Paul Greyson2913af82013-03-27 14:53:17 -0700678 'dst-port': 1,
Paul Greyson40c8a592013-03-27 14:10:33 -0700679 pending: true
680 };
681 pendingLinks[makeLinkKey(link1)] = link1;
682 var link2 = {
683 'src-switch': dstData.dpid,
Paul Greyson2913af82013-03-27 14:53:17 -0700684 'src-port': 1,
Paul Greyson40c8a592013-03-27 14:10:33 -0700685 'dst-switch': srcData.dpid,
Paul Greyson2913af82013-03-27 14:53:17 -0700686 'dst-port': 1,
Paul Greyson40c8a592013-03-27 14:10:33 -0700687 pending: true
688 };
689 pendingLinks[makeLinkKey(link2)] = link2;
Paul Greyson5cc35f02013-03-28 10:07:36 -0700690 updateTopology();
Paul Greyson40c8a592013-03-27 14:10:33 -0700691
Paul Greyson2913af82013-03-27 14:53:17 -0700692 linkUp(link1);
Paul Greyson40c8a592013-03-27 14:10:33 -0700693
Paul Greyson5cc35f02013-03-28 10:07:36 -0700694 // remove the pending links after 10s
Paul Greyson40c8a592013-03-27 14:10:33 -0700695 setTimeout(function () {
696 delete pendingLinks[makeLinkKey(link1)];
697 delete pendingLinks[makeLinkKey(link2)];
698
Paul Greyson5cc35f02013-03-28 10:07:36 -0700699 updateTopology();
Paul Greyson40c8a592013-03-27 14:10:33 -0700700 }, 10000);
Paul Greyson8d1c6362013-03-27 13:05:24 -0700701 }
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700702 }
703 }
704 }
705
706 clearHighlight();
707 } else {
708 clearHighlight();
709 }
710
711 });
712
Paul Greyson9066ab02013-03-23 18:15:41 -0700713 function labelRingEnter(data) {
Paul Greyson644d92a2013-03-23 18:00:40 -0700714 if (!data.length) {
Paul Greyson968d1b42013-03-23 16:58:41 -0700715 return;
716 }
717
718 // create the nodes
719 var nodes = d3.select(this).selectAll("g")
Paul Greyson644d92a2013-03-23 18:00:40 -0700720 .data(data, function (data) {
721 return data.dpid;
722 })
Paul Greyson968d1b42013-03-23 16:58:41 -0700723 .enter().append("svg:g")
724 .classed('nolabel', true)
Paul Greyson9066ab02013-03-23 18:15:41 -0700725 .attr("id", function (data) {
Paul Greyson644d92a2013-03-23 18:00:40 -0700726 return data.dpid + '-label';
Paul Greyson968d1b42013-03-23 16:58:41 -0700727 })
Paul Greyson644d92a2013-03-23 18:00:40 -0700728 .attr("transform", function(data, i) {
729 return "rotate(" + data.angle+ ")translate(" + data.radius * 150 + ")rotate(" + (-data.angle) + ")";
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700730 })
Paul Greyson968d1b42013-03-23 16:58:41 -0700731
732 // add the text nodes which show on mouse over
733 nodes.append("svg:text")
Paul Greyson127d7fb2013-03-25 23:39:20 -0700734 .text(function (data) {return data.dpid;})
Paul Greyson9066ab02013-03-23 18:15:41 -0700735 .attr("x", function (data) {
736 if (data.angle <= 90 || data.angle >= 270 && data.angle <= 360) {
737 if (data.className == 'edge') {
Paul Greyson1eb2dd12013-03-23 18:22:00 -0700738 return - data.width*3 - 4;
Paul Greyson9066ab02013-03-23 18:15:41 -0700739 } else {
Paul Greyson1eb2dd12013-03-23 18:22:00 -0700740 return - data.width - 4;
Paul Greyson9066ab02013-03-23 18:15:41 -0700741 }
742 } else {
743 if (data.className == 'edge') {
744 return data.width*3 + 4;
745 } else {
746 return data.width + 4;
747 }
748 }
749 })
750 .attr("y", function (data) {
751 var y;
752 if (data.angle <= 90 || data.angle >= 270 && data.angle <= 360) {
753 if (data.className == 'edge') {
754 y = data.width*3/2 + 4;
755 } else {
756 y = data.width/2 + 4;
757 }
758 } else {
759 if (data.className == 'edge') {
760 y = data.width*3/2 + 4;
761 } else {
762 y = data.width/2 + 4;
763 }
764 }
765 return y - 6;
766 })
767 .attr("text-anchor", function (data) {
768 if (data.angle <= 90 || data.angle >= 270 && data.angle <= 360) {
769 return "end";
770 } else {
771 return "start";
772 }
773 })
774 .attr("transform", function(data) {
Paul Greyson968d1b42013-03-23 16:58:41 -0700775 var m = document.querySelector('#viewbox').getTransformToElement().inverse();
776 if (data.scale) {
777 m = m.scale(data.scale);
778 }
779 return "matrix( " + m.a + " " + m.b + " " + m.c + " " + m.d + " " + m.e + " " + m.f + " )";
780 })
781 }
782
783 labelRings.enter().append("svg:g")
784 .attr("class", "textRing")
785 .each(labelRingEnter);
786
Paul Greysonc17278a2013-03-23 10:17:12 -0700787 // switches should not change during operation of the ui so no
788 // rings.exit()
789
790
Paul Greysond1a22d92013-03-19 12:15:19 -0700791 // DRAW THE LINKS
Paul Greysond1a22d92013-03-19 12:15:19 -0700792
Paul Greysonc17278a2013-03-23 10:17:12 -0700793 // key on link dpids since these will come/go during demo
Paul Greyson40c8a592013-03-27 14:10:33 -0700794 var links = d3.select('svg').selectAll('.link').data(links, function (d) {
Paul Greysonc17278a2013-03-23 10:17:12 -0700795 return d['src-switch']+'->'+d['dst-switch'];
796 });
797
798 // add new links
Paul Greysonb367de22013-03-23 11:09:11 -0700799 links.enter().append("svg:path")
Paul Greyson56378ed2013-03-26 23:17:36 -0700800 .attr("class", "link");
801
Paul Greyson084779b2013-03-27 13:55:49 -0700802 links.attr('id', function (d) {
Paul Greyson40c8a592013-03-27 14:10:33 -0700803 return makeLinkKey(d);
Paul Greyson084779b2013-03-27 13:55:49 -0700804 })
Paul Greyson56378ed2013-03-26 23:17:36 -0700805 .attr("d", function (d) {
Paul Greyson084779b2013-03-27 13:55:49 -0700806 var src = d3.select(document.getElementById(d['src-switch']));
807 var dst = d3.select(document.getElementById(d['dst-switch']));
Paul Greysonc17278a2013-03-23 10:17:12 -0700808
Paul Greyson084779b2013-03-27 13:55:49 -0700809 var srcPt = document.querySelector('svg').createSVGPoint();
810 srcPt.x = src.attr('x');
811 srcPt.y = src.attr('y');
812 srcPt = srcPt.matrixTransform(src[0][0].getCTM());
Paul Greysond1a22d92013-03-19 12:15:19 -0700813
Paul Greyson084779b2013-03-27 13:55:49 -0700814 var dstPt = document.querySelector('svg').createSVGPoint();
815 dstPt.x = dst.attr('x');
Paul Greyson421bfcd2013-03-27 22:22:09 -0700816 dstPt.y = dst.attr('y');
Paul Greyson084779b2013-03-27 13:55:49 -0700817 dstPt = dstPt.matrixTransform(dst[0][0].getCTM());
Paul Greysond1a22d92013-03-19 12:15:19 -0700818
Paul Greyson084779b2013-03-27 13:55:49 -0700819 var midPt = document.querySelector('svg').createSVGPoint();
820 midPt.x = (srcPt.x + dstPt.x)/2;
821 midPt.y = (srcPt.y + dstPt.y)/2;
Paul Greysond1a22d92013-03-19 12:15:19 -0700822
Paul Greyson084779b2013-03-27 13:55:49 -0700823 return line([srcPt, midPt, dstPt]);
824 })
Paul Greyson40c8a592013-03-27 14:10:33 -0700825 .attr("marker-mid", function(d) { return "url(#arrow)"; })
826 .classed('pending', function (d) {
827 return d.pending;
828 });
Paul Greysonc17278a2013-03-23 10:17:12 -0700829
Paul Greyson56378ed2013-03-26 23:17:36 -0700830
Paul Greysonc17278a2013-03-23 10:17:12 -0700831 // remove old links
832 links.exit().remove();
Paul Greyson644d92a2013-03-23 18:00:40 -0700833
Paul Greyson127d7fb2013-03-25 23:39:20 -0700834
835 drawFlows();
Paul Greysond1a22d92013-03-19 12:15:19 -0700836}
837
Paul Greyson5cc35f02013-03-28 10:07:36 -0700838function updateControllers() {
Paul Greysond1a22d92013-03-19 12:15:19 -0700839 var controllers = d3.select('#controllerList').selectAll('.controller').data(model.controllers);
Paul Greyson3e142162013-03-19 13:56:17 -0700840 controllers.enter().append('div')
Paul Greysone262a292013-03-23 10:35:23 -0700841 .each(function (c) {
842 controllerColorMap[c] = colors.pop();
843 d3.select(document.body).classed(controllerColorMap[c] + '-selected', true);
844 })
845 .text(function (d) {
846 return d;
Paul Greyson2913af82013-03-27 14:53:17 -0700847 })
848 .append('div')
Paul Greyson8247c3f2013-03-28 00:24:02 -0700849 .attr('class', 'black-eye');
Paul Greysonbcd3c772013-03-21 13:16:44 -0700850
Paul Greysone262a292013-03-23 10:35:23 -0700851 controllers.attr('class', function (d) {
Paul Greysoneed36352013-03-23 11:19:11 -0700852 var color = 'colorInactive';
Paul Greysonbcd3c772013-03-21 13:16:44 -0700853 if (model.activeControllers.indexOf(d) != -1) {
854 color = controllerColorMap[d];
Paul Greysond1a22d92013-03-19 12:15:19 -0700855 }
Paul Greysonbcd3c772013-03-21 13:16:44 -0700856 var className = 'controller ' + color;
857 return className;
Paul Greysond1a22d92013-03-19 12:15:19 -0700858 });
Paul Greysond1a22d92013-03-19 12:15:19 -0700859
Paul Greysone262a292013-03-23 10:35:23 -0700860 // this should never be needed
861 // controllers.exit().remove();
Paul Greysond1a22d92013-03-19 12:15:19 -0700862
Paul Greyson2913af82013-03-27 14:53:17 -0700863 controllers.on('dblclick', function (c) {
864 if (model.activeControllers.indexOf(c) != -1) {
865 var prompt = 'Dectivate ' + c + '?';
866 if (confirm(prompt)) {
867 controllerDown(c);
868 setPending(d3.select(this));
869 };
870 } else {
871 var prompt = 'Activate ' + c + '?';
872 if (confirm(prompt)) {
873 controllerUp(c);
874 setPending(d3.select(this));
875 };
876 }
877 });
878
Paul Greyson8247c3f2013-03-28 00:24:02 -0700879 controllers.select('.black-eye').on('click', function (c) {
Paul Greysonc3e21a02013-03-21 13:56:05 -0700880 var allSelected = true;
881 for (var key in controllerColorMap) {
882 if (!d3.select(document.body).classed(controllerColorMap[key] + '-selected')) {
883 allSelected = false;
884 break;
885 }
886 }
887 if (allSelected) {
888 for (var key in controllerColorMap) {
889 d3.select(document.body).classed(controllerColorMap[key] + '-selected', key == c)
890 }
891 } else {
892 for (var key in controllerColorMap) {
893 d3.select(document.body).classed(controllerColorMap[key] + '-selected', true)
894 }
895 }
896
897 // var selected = d3.select(document.body).classed(controllerColorMap[c] + '-selected');
898 // d3.select(document.body).classed(controllerColorMap[c] + '-selected', !selected);
Paul Greysond1a22d92013-03-19 12:15:19 -0700899 });
Paul Greyson8d1c6362013-03-27 13:05:24 -0700900
901
Paul Greyson740bdaf2013-03-18 16:10:48 -0700902}
903
Paul Greyson29aa98d2013-03-28 00:09:31 -0700904function sync(svg) {
Paul Greysonbcd3c772013-03-21 13:16:44 -0700905 var d = Date.now();
Paul Greysonb48943b2013-03-19 13:27:57 -0700906 updateModel(function (newModel) {
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700907// console.log('Update time: ' + (Date.now() - d)/1000 + 's');
Paul Greyson740bdaf2013-03-18 16:10:48 -0700908
Paul Greyson5cc35f02013-03-28 10:07:36 -0700909 var modelChanged = false;
Paul Greyson56378ed2013-03-26 23:17:36 -0700910 if (!model || JSON.stringify(model) != JSON.stringify(newModel)) {
Paul Greyson5cc35f02013-03-28 10:07:36 -0700911 modelChanged = true;
912 model = newModel;
Paul Greysonb48943b2013-03-19 13:27:57 -0700913 } else {
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700914// console.log('no change');
Paul Greysonb48943b2013-03-19 13:27:57 -0700915 }
Paul Greysonb48943b2013-03-19 13:27:57 -0700916
Paul Greyson5cc35f02013-03-28 10:07:36 -0700917 if (modelChanged) {
918 updateControllers();
919 updateSelectedFlows();
920 updateTopology();
921 }
922
923 updateHeader(newModel);
Paul Greyson740bdaf2013-03-18 16:10:48 -0700924
925 // do it again in 1s
926 setTimeout(function () {
Paul Greysona36a9232013-03-22 22:41:27 -0700927 sync(svg)
Paul Greysond1a22d92013-03-19 12:15:19 -0700928 }, 1000);
Paul Greyson6f86d1e2013-03-18 14:40:39 -0700929 });
930}
Paul Greyson740bdaf2013-03-18 16:10:48 -0700931
Paul Greyson38d8bde2013-03-22 22:07:35 -0700932svg = createTopologyView();
Paul Greyson29aa98d2013-03-28 00:09:31 -0700933updateSelectedFlows();
934
935d3.select('#showFlowChooser').on('click', function () {
936 showFlowChooser();
937});
938
Paul Greyson72f18852013-03-27 15:56:11 -0700939
Paul Greyson38d8bde2013-03-22 22:07:35 -0700940// workaround for Chrome v25 bug
941// if executed immediately, the view box transform logic doesn't work properly
942// fixed in Chrome v27
943setTimeout(function () {
944 // workaround for another Chrome v25 bug
945 // viewbox transform stuff doesn't work in combination with browser zoom
Paul Greysonc17278a2013-03-23 10:17:12 -0700946 // also works in Chrome v27
Paul Greyson38d8bde2013-03-22 22:07:35 -0700947 d3.select('#svg-container').style('zoom', window.document.body.clientWidth/window.document.width);
Paul Greyson29aa98d2013-03-28 00:09:31 -0700948 sync(svg);
Paul Greyson38d8bde2013-03-22 22:07:35 -0700949}, 100);