blob: 5573b1b638c35070b1d4e3e3c8eec3fb93313926 [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 Greyson6f918402013-03-28 12:18:30 -070021var selectedFlows = [];
22
Paul Greyson97cc9f52013-04-04 01:45:40 -070023var pendingTimeout = 30000;
Paul Greyson127d7fb2013-03-25 23:39:20 -070024
Paul Greysond1a22d92013-03-19 12:15:19 -070025var colors = [
Paul Greyson3e142162013-03-19 13:56:17 -070026 'color1',
27 'color2',
28 'color3',
29 'color4',
Paul Greyson3e142162013-03-19 13:56:17 -070030 'color7',
31 'color8',
32 'color9',
Paul Greyson5cc35f02013-03-28 10:07:36 -070033// '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?
Paul Greyson5cc35f02013-03-28 10:07:36 -070052// updateTopology();
Paul Greyson56378ed2013-03-26 23:17:36 -070053 });
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 Greyson13f02b92013-03-28 11:29:35 -070071function updateSelectedFlowsTopology() {
Paul Greyson127d7fb2013-03-25 23:39:20 -070072 // DRAW THE FLOWS
Paul Greyson97cc9f52013-04-04 01:45:40 -070073 var topologyFlows = [];
74 selectedFlows.forEach(function (flow) {
75 if (flow) {
76 topologyFlows.push(flow);
77 }
78 });
79
80 var flows = d3.select('svg').selectAll('.flow').data(topologyFlows);
Paul Greyson127d7fb2013-03-25 23:39:20 -070081
Paul Greyson29aa98d2013-03-28 00:09:31 -070082 flows.enter().append("svg:path").attr('class', 'flow')
83 .attr('stroke-dasharray', '4, 10')
84 .append('svg:animate')
85 .attr('attributeName', 'stroke-dashoffset')
86 .attr('attributeType', 'xml')
87 .attr('from', '500')
88 .attr('to', '-500')
89 .attr('dur', '20s')
90 .attr('repeatCount', 'indefinite');
91
Paul Greyson97cc9f52013-04-04 01:45:40 -070092 flows.exit().remove();
Paul Greyson29aa98d2013-03-28 00:09:31 -070093
94 flows.attr('d', function (d) {
Paul Greyson13f02b92013-03-28 11:29:35 -070095 if (!d) {
96 return;
97 }
98 var pts = [];
Paul Greyson280a2752013-04-04 03:21:59 -070099 if (!d.dataPath.flowEntries || !d.dataPath.flowEntries.length) {
Paul Greyson13f02b92013-03-28 11:29:35 -0700100 // create a temporary vector to indicate the pending flow
101 var s1 = d3.select(document.getElementById(d.dataPath.srcPort.dpid.value));
102 var s2 = d3.select(document.getElementById(d.dataPath.dstPort.dpid.value));
103
104 var pt1 = document.querySelector('svg').createSVGPoint();
105 pt1.x = s1.attr('x');
106 pt1.y = s1.attr('y');
107 pt1 = pt1.matrixTransform(s1[0][0].getCTM());
108 pts.push(pt1);
109
110 var pt2 = document.querySelector('svg').createSVGPoint();
111 pt2.x = s2.attr('x');
112 pt2.y = s2.attr('y');
113 pt2 = pt2.matrixTransform(s2[0][0].getCTM());
114 pts.push(pt2);
115
116 } else {
117 d.dataPath.flowEntries.forEach(function (flowEntry) {
118 var s = d3.select(document.getElementById(flowEntry.dpid.value));
119 // s[0] is null if the flow entry refers to a non-existent switch
120 if (s[0][0]) {
121 var pt = document.querySelector('svg').createSVGPoint();
122 pt.x = s.attr('x');
123 pt.y = s.attr('y');
124 pt = pt.matrixTransform(s[0][0].getCTM());
125 pts.push(pt);
126 } else {
127 console.log('flow refers to non-existent switch: ' + flowEntry.dpid.value);
128 }
129 });
130 }
131 return line(pts);
132 })
Paul Greyson71b550d2013-03-28 11:56:19 -0700133 .attr('id', function (d) {
134 if (d) {
135 return makeFlowKey(d);
136 }
137 })
Paul Greyson13f02b92013-03-28 11:29:35 -0700138 .classed('pending', function (d) {
Paul Greysonaa812562013-03-28 12:43:12 -0700139 return d && (d.createPending || d.deletePending);
Paul Greyson127d7fb2013-03-25 23:39:20 -0700140 });
Paul Greyson127d7fb2013-03-25 23:39:20 -0700141
Paul Greyson56378ed2013-03-26 23:17:36 -0700142 // "marching ants"
Paul Greyson29aa98d2013-03-28 00:09:31 -0700143 flows.select('animate').attr('from', 500);
144
Paul Greyson13f02b92013-03-28 11:29:35 -0700145}
146
147function updateSelectedFlowsTable() {
148 function rowEnter(d) {
149 var row = d3.select(this);
Paul Greyson6f918402013-03-28 12:18:30 -0700150 row.append('div').classed('deleteFlow', true);
Paul Greyson13f02b92013-03-28 11:29:35 -0700151 row.append('div').classed('flowId', true);
152 row.append('div').classed('srcDPID', true);
153 row.append('div').classed('dstDPID', true);
154 row.append('div').classed('iperf', true);
Paul Greyson71b550d2013-03-28 11:56:19 -0700155
Paul Greyson95db7a12013-04-04 14:57:58 -0700156 row.select('.iperf')
157 .append('div')
158 .attr('class', 'iperf-container')
159 .append('svg:svg')
160 .attr('viewBox', '0 0 1000 32')
161 .attr('preserveAspectRatio', 'none')
162 .append('svg:g')
163 .append('svg:path')
164 .attr('class', 'iperfdata');
165
Paul Greyson71b550d2013-03-28 11:56:19 -0700166 row.on('mouseover', function (d) {
167 if (d) {
168 var path = document.getElementById(makeFlowKey(d));
169 d3.select(path).classed('highlight', true);
170 }
171 });
172 row.on('mouseout', function (d) {
173 if (d) {
174 var path = document.getElementById(makeFlowKey(d));
175 d3.select(path).classed('highlight', false);
176 }
Paul Greyson95db7a12013-04-04 14:57:58 -0700177 });
Paul Greyson13f02b92013-03-28 11:29:35 -0700178 }
179
180 function rowUpdate(d) {
181 var row = d3.select(this);
Paul Greyson95db7a12013-04-04 14:57:58 -0700182 row.attr('id', function (d) {
183 if (d) {
184 return makeSelectedFlowKey(d);
185 }
186 });
Paul Greyson2c35f572013-04-04 16:23:48 -0700187
188 if (d && !d.iperfData) {
189 row.select('.iperfdata')
190 .attr('d', 'M0,0');
191 }
192
Paul Greyson6f918402013-03-28 12:18:30 -0700193 row.select('.deleteFlow').on('click', function () {
Paul Greyson2c35f572013-04-04 16:23:48 -0700194 deselectFlow(d);
Paul Greysonbe8bf872013-04-04 01:53:45 -0700195 });
196 row.on('dblclick', function () {
Paul Greyson6f918402013-03-28 12:18:30 -0700197 if (d) {
198 var prompt = 'Delete flow ' + d.flowId.value + '?';
199 if (confirm(prompt)) {
200 deleteFlow(d);
Paul Greysonaa812562013-03-28 12:43:12 -0700201 d.deletePending = true;
Paul Greyson6f918402013-03-28 12:18:30 -0700202 updateSelectedFlows();
203
204 setTimeout(function () {
Paul Greysonaa812562013-03-28 12:43:12 -0700205 d.deletePending = false;
Paul Greyson6f918402013-03-28 12:18:30 -0700206 updateSelectedFlows();
207 }, pendingTimeout)
208 };
209 }
210 });
211
Paul Greyson13f02b92013-03-28 11:29:35 -0700212 row.select('.flowId')
213 .text(function (d) {
214 if (d) {
215 if (d.flowId) {
216 return d.flowId.value;
217 } else {
218 return '0x--';
219 }
220 }
221 })
Paul Greysoncb5d30d2013-04-04 02:00:56 -0700222 .classed('pending', function (d) {
223 return d && (d.createPending || d.deletePending);
224 });
Paul Greyson13f02b92013-03-28 11:29:35 -0700225
226 row.select('.srcDPID')
227 .text(function (d) {
228 if (d) {
229 return d.dataPath.srcPort.dpid.value;
230 }
231 });
232
233 row.select('.dstDPID')
234 .text(function (d) {
235 if (d) {
236 return d.dataPath.dstPort.dpid.value;
237 }
238 });
239 }
240
241 var flows = d3.select('#selectedFlows')
242 .selectAll('.selectedFlow')
243 .data(selectedFlows);
244
245 flows.enter()
246 .append('div')
247 .classed('selectedFlow', true)
248 .each(rowEnter);
249
250 flows.each(rowUpdate);
251
Paul Greyson29aa98d2013-03-28 00:09:31 -0700252 flows.exit().remove();
Paul Greyson127d7fb2013-03-25 23:39:20 -0700253}
254
Paul Greyson4b4c8af2013-04-04 09:02:09 -0700255// TODO: cancel the interval when the flow is desel
256function startIPerfForFlow(flow) {
Paul Greyson95db7a12013-04-04 14:57:58 -0700257 var duration = 10000; // seconds
Paul Greyson4b4c8af2013-04-04 09:02:09 -0700258 var interval = 100; // ms. this is defined by the server
259 var updateRate = 1000; // ms
260
Paul Greyson2c35f572013-04-04 16:23:48 -0700261 function makePoints() {
262 var pts = [];
263 var i;
264 for (i=0; i < 100; ++i) {
265 var sample = flow.iperfData.samples[i];
266 var height = 32 * sample/50000000;
267 if (height > 32)
268 height = 32;
269 pts.push({
270 x: i * 10,
271 y: 32 - height
272 })
273 }
274 return pts;
275 }
276
Paul Greyson4b4c8af2013-04-04 09:02:09 -0700277 if (flow.flowId) {
278 console.log('starting iperf for: ' + flow.flowId.value);
279 startIPerf(flow, duration, updateRate/interval);
Paul Greyson2c35f572013-04-04 16:23:48 -0700280 flow.iperfDisplayInterval = setInterval(function () {
281 if (flow.iperfData) {
282 if (flow.iperfData.samples.length < 100) {
283 var repeatValue = flow.iperfData.samples[flow.iperfData.samples.length-1];
284 while (flow.iperfData.samples.length < 100) {
285 flow.iperfData.samples.push(repeatValue);
286 }
287 }
288 var iperfPath = d3.select(document.getElementById(makeSelectedFlowKey(flow))).select('path');
289 iperfPath.attr('d', line(makePoints()));
290 flow.iperfData.samples.shift();
291 }
292
293
294 }, interval);
295 flow.iperfFetchInterval = setInterval(function () {
Paul Greyson4b4c8af2013-04-04 09:02:09 -0700296 getIPerfData(flow, function (data) {
Paul Greyson95db7a12013-04-04 14:57:58 -0700297 try {
Paul Greyson2c35f572013-04-04 16:23:48 -0700298 if (!flow.iperfData) {
299 flow.iperfData = {
300 samples: []
301 };
302 var i;
303 for (i = 0; i < 100; ++i) {
304 flow.iperfData.samples.push(0);
305 }
306 }
307
Paul Greyson95db7a12013-04-04 14:57:58 -0700308 var iperfData = JSON.parse(data);
309 // if the data is fresh
Paul Greyson2c35f572013-04-04 16:23:48 -0700310 if (iperfData.timestamp != flow.iperfData.timestamp) {
311 iperfData.samples.forEach(function (s) {
312 flow.iperfData.samples.push(s);
313 });
Paul Greyson95db7a12013-04-04 14:57:58 -0700314 }
Paul Greyson2c35f572013-04-04 16:23:48 -0700315 flow.iperfData.timestamp = iperfData.timestamp;
Paul Greyson95db7a12013-04-04 14:57:58 -0700316 } catch (e) {
317 console.log('bad iperf data: ' + data);
318 }
319// console.log(data);
Paul Greyson4b4c8af2013-04-04 09:02:09 -0700320 });
Paul Greyson95db7a12013-04-04 14:57:58 -0700321 }, updateRate);
Paul Greyson4b4c8af2013-04-04 09:02:09 -0700322 }
323}
324
Paul Greyson13f02b92013-03-28 11:29:35 -0700325function updateSelectedFlows() {
326 // make sure that all of the selected flows are either
327 // 1) valid (meaning they are in the latest list of flows)
328 // 2) pending
329 if (model) {
330 var flowMap = {};
331 model.flows.forEach(function (flow) {
332 flowMap[makeFlowKey(flow)] = flow;
333 });
334
335 var newSelectedFlows = [];
336 selectedFlows.forEach(function (flow) {
337 if (flow) {
Paul Greysonf430fd02013-03-28 12:32:24 -0700338 var liveFlow = flowMap[makeFlowKey(flow)];
339 if (liveFlow) {
340 newSelectedFlows.push(liveFlow);
Paul Greysonaa812562013-03-28 12:43:12 -0700341 liveFlow.deletePending = flow.deletePending;
Paul Greyson2c35f572013-04-04 16:23:48 -0700342 liveFlow.iperfFetchInterval = flow.iperfFetchInterval;
343 liveFlow.iperfDisplayInterval = flow.iperfDisplayInterval;
Paul Greysonaa812562013-03-28 12:43:12 -0700344 } else if (flow.createPending) {
Paul Greyson13f02b92013-03-28 11:29:35 -0700345 newSelectedFlows.push(flow);
Paul Greyson13f02b92013-03-28 11:29:35 -0700346 }
Paul Greyson13f02b92013-03-28 11:29:35 -0700347 }
348 });
349 selectedFlows = newSelectedFlows;
350 }
Paul Greyson4b4c8af2013-04-04 09:02:09 -0700351 selectedFlows.forEach(function (flow) {
Paul Greyson2c35f572013-04-04 16:23:48 -0700352 if (!flow.iperfFetchInterval) {
Paul Greyson4b4c8af2013-04-04 09:02:09 -0700353 startIPerfForFlow(flow);
354 }
355 });
Paul Greyson6f918402013-03-28 12:18:30 -0700356 while (selectedFlows.length < 3) {
357 selectedFlows.push(null);
358 }
Paul Greyson13f02b92013-03-28 11:29:35 -0700359
360 updateSelectedFlowsTable();
361 updateSelectedFlowsTopology();
362}
363
364function selectFlow(flow) {
Paul Greysonc30f75e2013-03-28 11:45:15 -0700365 var flowKey = makeFlowKey(flow);
366 var alreadySelected = false;
367 selectedFlows.forEach(function (f) {
368 if (f && makeFlowKey(f) === flowKey) {
369 alreadySelected = true;
370 }
371 });
372
373 if (!alreadySelected) {
374 selectedFlows.unshift(flow);
375 selectedFlows = selectedFlows.slice(0, 3);
376 updateSelectedFlows();
377 }
Paul Greyson13f02b92013-03-28 11:29:35 -0700378}
379
Paul Greysonaa812562013-03-28 12:43:12 -0700380function deselectFlow(flow, ifCreatePending) {
Paul Greyson13f02b92013-03-28 11:29:35 -0700381 var flowKey = makeFlowKey(flow);
382 var newSelectedFlows = [];
383 selectedFlows.forEach(function (flow) {
Paul Greysonf430fd02013-03-28 12:32:24 -0700384 if (!flow ||
385 flowKey !== makeFlowKey(flow) ||
Paul Greysonaa812562013-03-28 12:43:12 -0700386 flowKey === makeFlowKey(flow) && ifCreatePending && !flow.createPending ) {
Paul Greyson13f02b92013-03-28 11:29:35 -0700387 newSelectedFlows.push(flow);
Paul Greyson4b4c8af2013-04-04 09:02:09 -0700388 } else {
Paul Greyson2c35f572013-04-04 16:23:48 -0700389 if (flow && flow.iperfFetchInterval) {
Paul Greyson4b4c8af2013-04-04 09:02:09 -0700390 console.log('clearing iperf interval for: ' + flow.flowId.value);
Paul Greyson2c35f572013-04-04 16:23:48 -0700391 clearInterval(flow.iperfFetchInterval);
392 clearInterval(flow.iperfDisplayInterval);
Paul Greyson4b4c8af2013-04-04 09:02:09 -0700393 }
Paul Greyson13f02b92013-03-28 11:29:35 -0700394 }
395 });
396 selectedFlows = newSelectedFlows;
397 while (selectedFlows.length < 3) {
398 selectedFlows.push(null);
399 }
400
401 updateSelectedFlows();
402}
403
Paul Greysonaa812562013-03-28 12:43:12 -0700404function deselectFlowIfCreatePending(flow) {
Paul Greysonf430fd02013-03-28 12:32:24 -0700405 deselectFlow(flow, true);
406}
407
Paul Greyson29aa98d2013-03-28 00:09:31 -0700408function showFlowChooser() {
409 function rowEnter(d) {
Paul Greyson127d7fb2013-03-25 23:39:20 -0700410 var row = d3.select(this);
411
Paul Greyson127d7fb2013-03-25 23:39:20 -0700412 row.append('div')
Paul Greyson8247c3f2013-03-28 00:24:02 -0700413 .classed('black-eye', true).
Paul Greyson29aa98d2013-03-28 00:09:31 -0700414 on('click', function () {
Paul Greyson13f02b92013-03-28 11:29:35 -0700415 selectFlow(d);
Paul Greyson127d7fb2013-03-25 23:39:20 -0700416 });
417
418 row.append('div')
Paul Greyson29aa98d2013-03-28 00:09:31 -0700419 .classed('flowId', true)
420 .text(function (d) {
421 return d.flowId.value;
422 });
Paul Greyson127d7fb2013-03-25 23:39:20 -0700423
424 row.append('div')
Paul Greyson29aa98d2013-03-28 00:09:31 -0700425 .classed('srcDPID', true)
426 .text(function (d) {
427 return d.dataPath.srcPort.dpid.value;
428 });
429
Paul Greyson127d7fb2013-03-25 23:39:20 -0700430
431 row.append('div')
Paul Greyson29aa98d2013-03-28 00:09:31 -0700432 .classed('dstDPID', true)
433 .text(function (d) {
434 return d.dataPath.dstPort.dpid.value;
435 });
Paul Greyson127d7fb2013-03-25 23:39:20 -0700436
Paul Greyson127d7fb2013-03-25 23:39:20 -0700437 }
438
Paul Greyson29aa98d2013-03-28 00:09:31 -0700439 var flows = d3.select('#flowChooser')
440 .append('div')
441 .style('pointer-events', 'auto')
Paul Greyson127d7fb2013-03-25 23:39:20 -0700442 .selectAll('.selectedFlow')
Paul Greyson29aa98d2013-03-28 00:09:31 -0700443 .data(model.flows)
Paul Greyson127d7fb2013-03-25 23:39:20 -0700444 .enter()
445 .append('div')
446 .classed('selectedFlow', true)
447 .each(rowEnter);
448
Paul Greyson29aa98d2013-03-28 00:09:31 -0700449 setTimeout(function () {
450 d3.select(document.body).on('click', function () {
451 d3.select('#flowChooser').html('');
452 d3.select(document.body).on('click', null);
453 });
454 }, 0);
455}
456
Paul Greyson29aa98d2013-03-28 00:09:31 -0700457
Paul Greyson127d7fb2013-03-25 23:39:20 -0700458
Paul Greysond1a22d92013-03-19 12:15:19 -0700459function updateHeader(model) {
Paul Greysonb48943b2013-03-19 13:27:57 -0700460 d3.select('#lastUpdate').text(new Date());
Paul Greyson952ccb62013-03-18 22:22:08 -0700461 d3.select('#activeSwitches').text(model.edgeSwitches.length + model.aggregationSwitches.length + model.coreSwitches.length);
462 d3.select('#activeFlows').text(model.flows.length);
463}
464
465function toRadians (angle) {
466 return angle * (Math.PI / 180);
Paul Greyson740bdaf2013-03-18 16:10:48 -0700467}
468
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700469var widths = {
470 edge: 6,
471 aggregation: 12,
472 core: 18
473}
474
Paul Greysonc17278a2013-03-23 10:17:12 -0700475function createRingsFromModel(model) {
Paul Greyson740bdaf2013-03-18 16:10:48 -0700476 var rings = [{
477 radius: 3,
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700478 width: widths.edge,
Paul Greyson952ccb62013-03-18 22:22:08 -0700479 switches: model.edgeSwitches,
Paul Greysonde7fad52013-03-19 12:47:32 -0700480 className: 'edge',
481 angles: []
Paul Greyson740bdaf2013-03-18 16:10:48 -0700482 }, {
Paul Greysond1a22d92013-03-19 12:15:19 -0700483 radius: 2.25,
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700484 width: widths.aggregation,
Paul Greyson952ccb62013-03-18 22:22:08 -0700485 switches: model.aggregationSwitches,
Paul Greysonde7fad52013-03-19 12:47:32 -0700486 className: 'aggregation',
487 angles: []
Paul Greyson740bdaf2013-03-18 16:10:48 -0700488 }, {
Paul Greyson127d7fb2013-03-25 23:39:20 -0700489 radius: 0.75,
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700490 width: widths.core,
Paul Greyson952ccb62013-03-18 22:22:08 -0700491 switches: model.coreSwitches,
Paul Greysonde7fad52013-03-19 12:47:32 -0700492 className: 'core',
493 angles: []
Paul Greyson740bdaf2013-03-18 16:10:48 -0700494 }];
495
Paul Greysonde7fad52013-03-19 12:47:32 -0700496
497 var aggRanges = {};
498
499 // arrange edge switches at equal increments
500 var k = 360 / rings[0].switches.length;
501 rings[0].switches.forEach(function (s, i) {
502 var angle = k * i;
503
504 rings[0].angles[i] = angle;
505
506 // record the angle for the agg switch layout
507 var dpid = s.dpid.split(':');
Paul Greyson832d2202013-03-21 13:27:56 -0700508 dpid[7] = '01'; // the last component of the agg switch is always '01'
Paul Greysonde7fad52013-03-19 12:47:32 -0700509 var aggdpid = dpid.join(':');
510 var aggRange = aggRanges[aggdpid];
511 if (!aggRange) {
512 aggRange = aggRanges[aggdpid] = {};
513 aggRange.min = aggRange.max = angle;
514 } else {
515 aggRange.max = angle;
516 }
Paul Greysonde7fad52013-03-19 12:47:32 -0700517 });
518
519 // arrange aggregation switches to "fan out" to edge switches
520 k = 360 / rings[1].switches.length;
521 rings[1].switches.forEach(function (s, i) {
522// rings[1].angles[i] = k * i;
523 var range = aggRanges[s.dpid];
524
Paul Greyson832d2202013-03-21 13:27:56 -0700525 rings[1].angles[i] = (range.min + range.max)/2;
Paul Greysonde7fad52013-03-19 12:47:32 -0700526 });
527
Paul Greyson3f890b62013-03-22 17:39:36 -0700528 // find the association between core switches and aggregation switches
529 var aggregationSwitchMap = {};
530 model.aggregationSwitches.forEach(function (s, i) {
Paul Greysonc17278a2013-03-23 10:17:12 -0700531 aggregationSwitchMap[s.dpid] = i;
Paul Greyson3f890b62013-03-22 17:39:36 -0700532 });
533
Paul Greyson3f890b62013-03-22 17:39:36 -0700534 // put core switches next to linked aggregation switches
Paul Greysonde7fad52013-03-19 12:47:32 -0700535 k = 360 / rings[2].switches.length;
536 rings[2].switches.forEach(function (s, i) {
Paul Greyson3f890b62013-03-22 17:39:36 -0700537// rings[2].angles[i] = k * i;
Paul Greysonc17278a2013-03-23 10:17:12 -0700538 var associatedAggregationSwitches = model.configuration.association[s.dpid];
539 // TODO: go between if there are multiple
540 var index = aggregationSwitchMap[associatedAggregationSwitches[0]];
541
542 rings[2].angles[i] = rings[1].angles[index];
Paul Greysonde7fad52013-03-19 12:47:32 -0700543 });
544
Paul Greyson644d92a2013-03-23 18:00:40 -0700545 // TODO: construct this form initially rather than converting. it works better because
546 // it allows binding by dpid
547 var testRings = [];
548 rings.forEach(function (ring) {
549 var testRing = [];
550 ring.switches.forEach(function (s, i) {
551 var testSwitch = {
552 dpid: s.dpid,
553 state: s.state,
554 radius: ring.radius,
555 width: ring.width,
556 className: ring.className,
557 angle: ring.angles[i],
558 controller: s.controller
Paul Greyson127d7fb2013-03-25 23:39:20 -0700559 };
Paul Greyson644d92a2013-03-23 18:00:40 -0700560 testRing.push(testSwitch);
561 });
Paul Greyson6d9ed862013-03-23 17:37:15 -0700562
563
Paul Greyson644d92a2013-03-23 18:00:40 -0700564 testRings.push(testRing);
565 });
Paul Greyson6d9ed862013-03-23 17:37:15 -0700566
567
Paul Greyson644d92a2013-03-23 18:00:40 -0700568// return rings;
569 return testRings;
Paul Greysonc17278a2013-03-23 10:17:12 -0700570}
571
Paul Greyson40c8a592013-03-27 14:10:33 -0700572function makeLinkKey(link) {
573 return link['src-switch'] + '=>' + link['dst-switch'];
574}
575
Paul Greyson13f02b92013-03-28 11:29:35 -0700576function makeFlowKey(flow) {
577 return flow.dataPath.srcPort.dpid.value + '=>' + flow.dataPath.dstPort.dpid.value;
578}
579
Paul Greyson95db7a12013-04-04 14:57:58 -0700580function makeSelectedFlowKey(flow) {
581 return 'S' + makeFlowKey(flow);
582}
583
Paul Greyson40c8a592013-03-27 14:10:33 -0700584function createLinkMap(links) {
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700585 var linkMap = {};
Paul Greyson40c8a592013-03-27 14:10:33 -0700586 links.forEach(function (link) {
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700587 var srcDPID = link['src-switch'];
588 var dstDPID = link['dst-switch'];
589
590 var srcMap = linkMap[srcDPID] || {};
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700591
Paul Greyson8d1c6362013-03-27 13:05:24 -0700592 srcMap[dstDPID] = link;
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700593
594 linkMap[srcDPID] = srcMap;
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700595 });
596 return linkMap;
597}
598
Paul Greyson5cc35f02013-03-28 10:07:36 -0700599// removes links from the pending list that are now in the model
600function reconcilePendingLinks(model) {
Paul Greyson40c8a592013-03-27 14:10:33 -0700601 var links = [];
602 model.links.forEach(function (link) {
603 links.push(link);
604 delete pendingLinks[makeLinkKey(link)]
605 })
606 var linkId;
607 for (linkId in pendingLinks) {
608 links.push(pendingLinks[linkId]);
609 }
Paul Greyson5cc35f02013-03-28 10:07:36 -0700610 return links
611}
Paul Greyson40c8a592013-03-27 14:10:33 -0700612
Paul Greyson5cc35f02013-03-28 10:07:36 -0700613updateTopology = function() {
614
615 // DRAW THE SWITCHES
616 var rings = svg.selectAll('.ring').data(createRingsFromModel(model));
617
618 var links = reconcilePendingLinks(model);
Paul Greyson40c8a592013-03-27 14:10:33 -0700619 var linkMap = createLinkMap(links);
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700620
Paul Greyson8d1c6362013-03-27 13:05:24 -0700621 function mouseOverSwitch(data) {
Paul Greyson72f18852013-03-27 15:56:11 -0700622
623 d3.event.preventDefault();
624
Paul Greyson5cc35f02013-03-28 10:07:36 -0700625 d3.select(document.getElementById(data.dpid + '-label')).classed('nolabel', false);
626
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700627 if (data.highlighted) {
628 return;
629 }
630
631 // only highlight valid link or flow destination by checking for class of existing highlighted circle
Paul Greyson421bfcd2013-03-27 22:22:09 -0700632 var highlighted = svg.selectAll('.highlight')[0];
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700633 if (highlighted.length == 1) {
Paul Greyson421bfcd2013-03-27 22:22:09 -0700634 var s = d3.select(highlighted[0]).select('circle');
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700635 // only allow links
636 // edge->edge (flow)
637 // aggregation->core
638 // core->core
639 if (data.className == 'edge' && !s.classed('edge') ||
640 data.className == 'core' && !s.classed('core') && !s.classed('aggregation') ||
641 data.className == 'aggregation' && !s.classed('core')) {
642 return;
643 }
644
645 // don't highlight if there's already a link or flow
646 // var map = linkMap[data.dpid];
647 // console.log(map);
648 // console.log(s.data()[0].dpid);
649 // console.log(map[s.data()[0].dpid]);
650 // if (map && map[s.data()[0].dpid]) {
651 // return;
652 // }
653
654 // the second highlighted switch is the target for a link or flow
655 data.target = true;
656 }
657
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700658 var node = d3.select(document.getElementById(data.dpid));
Paul Greyson421bfcd2013-03-27 22:22:09 -0700659 node.classed('highlight', true).select('circle').transition().duration(100).attr("r", widths.core);
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700660 data.highlighted = true;
661 node.moveToFront();
662 }
663
Paul Greyson8d1c6362013-03-27 13:05:24 -0700664 function mouseOutSwitch(data) {
Paul Greyson5cc35f02013-03-28 10:07:36 -0700665 d3.select(document.getElementById(data.dpid + '-label')).classed('nolabel', true);
666
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700667 if (data.mouseDown)
668 return;
669
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700670 var node = d3.select(document.getElementById(data.dpid));
Paul Greyson421bfcd2013-03-27 22:22:09 -0700671 node.classed('highlight', false).select('circle').transition().duration(100).attr("r", widths[data.className]);
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700672 data.highlighted = false;
673 data.target = false;
674 }
675
Paul Greyson8d1c6362013-03-27 13:05:24 -0700676 function mouseDownSwitch(data) {
677 mouseOverSwitch(data);
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700678 data.mouseDown = true;
Paul Greyson72f18852013-03-27 15:56:11 -0700679 d3.select('#topology').classed('linking', true);
680
Paul Greyson421bfcd2013-03-27 22:22:09 -0700681 d3.select('svg')
682 .append('svg:path')
683 .attr('id', 'linkVector')
684 .attr('d', function () {
685 var s = d3.select(document.getElementById(data.dpid));
686
687 var pt = document.querySelector('svg').createSVGPoint();
688 pt.x = s.attr('x');
689 pt.y = s.attr('y');
690 pt = pt.matrixTransform(s[0][0].getCTM());
691
692 return line([pt, pt]);
693 });
694
695
Paul Greyson72f18852013-03-27 15:56:11 -0700696 if (data.className === 'core') {
697 d3.selectAll('.edge').classed('nodrop', true);
698 }
699 if (data.className === 'edge') {
700 d3.selectAll('.core').classed('nodrop', true);
701 d3.selectAll('.aggregation').classed('nodrop', true);
702 }
703 if (data.className === 'aggregation') {
704 d3.selectAll('.edge').classed('nodrop', true);
705 d3.selectAll('.aggregation').classed('nodrop', true);
706 }
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700707 }
708
Paul Greyson8d1c6362013-03-27 13:05:24 -0700709 function mouseUpSwitch(data) {
710 if (data.mouseDown) {
711 data.mouseDown = false;
Paul Greyson72f18852013-03-27 15:56:11 -0700712 d3.select('#topology').classed('linking', false);
Paul Greyson8d1c6362013-03-27 13:05:24 -0700713 d3.event.stopPropagation();
Paul Greyson72f18852013-03-27 15:56:11 -0700714 d3.selectAll('.nodrop').classed('nodrop', false);
Paul Greyson8d1c6362013-03-27 13:05:24 -0700715 }
716 }
717
718 function doubleClickSwitch(data) {
Paul Greyson084779b2013-03-27 13:55:49 -0700719 var circle = d3.select(document.getElementById(data.dpid)).select('circle');
Paul Greyson8d1c6362013-03-27 13:05:24 -0700720 if (data.state == 'ACTIVE') {
721 var prompt = 'Deactivate ' + data.dpid + '?';
722 if (confirm(prompt)) {
723 switchDown(data);
Paul Greyson084779b2013-03-27 13:55:49 -0700724 setPending(circle);
Paul Greyson8d1c6362013-03-27 13:05:24 -0700725 }
726 } else {
727 var prompt = 'Activate ' + data.dpid + '?';
728 if (confirm(prompt)) {
729 switchUp(data);
Paul Greyson084779b2013-03-27 13:55:49 -0700730 setPending(circle);
Paul Greyson8d1c6362013-03-27 13:05:24 -0700731 }
732 }
733 }
734
Paul Greyson740bdaf2013-03-18 16:10:48 -0700735 function ringEnter(data, i) {
Paul Greyson644d92a2013-03-23 18:00:40 -0700736 if (!data.length) {
Paul Greyson740bdaf2013-03-18 16:10:48 -0700737 return;
738 }
739
Paul Greysonc17278a2013-03-23 10:17:12 -0700740 // create the nodes
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700741 var nodes = d3.select(this).selectAll("g")
Paul Greyson644d92a2013-03-23 18:00:40 -0700742 .data(data, function (data) {
743 return data.dpid;
744 })
Paul Greyson740bdaf2013-03-18 16:10:48 -0700745 .enter().append("svg:g")
Paul Greyson644d92a2013-03-23 18:00:40 -0700746 .attr("id", function (data, i) {
747 return data.dpid;
Paul Greyson23b0cd32013-03-18 23:45:48 -0700748 })
Paul Greyson644d92a2013-03-23 18:00:40 -0700749 .attr("transform", function(data, i) {
750 return "rotate(" + data.angle+ ")translate(" + data.radius * 150 + ")rotate(" + (-data.angle) + ")";
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700751 });
752
Paul Greysonc17278a2013-03-23 10:17:12 -0700753 // add the cirles representing the switches
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700754 nodes.append("svg:circle")
Paul Greyson644d92a2013-03-23 18:00:40 -0700755 .attr("transform", function(data, i) {
Paul Greysond1a22d92013-03-19 12:15:19 -0700756 var m = document.querySelector('#viewbox').getTransformToElement().inverse();
Paul Greysonf8f43172013-03-18 23:00:30 -0700757 if (data.scale) {
758 m = m.scale(data.scale);
759 }
760 return "matrix( " + m.a + " " + m.b + " " + m.c + " " + m.d + " " + m.e + " " + m.f + " )";
Paul Greyson952ccb62013-03-18 22:22:08 -0700761 })
Paul Greyson644d92a2013-03-23 18:00:40 -0700762 .attr("x", function (data) {
763 return -data.width / 2;
764 })
765 .attr("y", function (data) {
766 return -data.width / 2;
767 })
768 .attr("r", function (data) {
769 return data.width;
Paul Greyson127d7fb2013-03-25 23:39:20 -0700770 });
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700771
Paul Greysonc17278a2013-03-23 10:17:12 -0700772 // setup the mouseover behaviors
Paul Greyson8d1c6362013-03-27 13:05:24 -0700773 nodes.on('mouseover', mouseOverSwitch);
774 nodes.on('mouseout', mouseOutSwitch);
775 nodes.on('mouseup', mouseUpSwitch);
776 nodes.on('mousedown', mouseDownSwitch);
777
778 // only do switch up/down for core switches
779 if (i == 2) {
780 nodes.on('dblclick', doubleClickSwitch);
781 }
Paul Greyson740bdaf2013-03-18 16:10:48 -0700782 }
783
Paul Greysonc17278a2013-03-23 10:17:12 -0700784 // append switches
785 rings.enter().append("svg:g")
Paul Greyson740bdaf2013-03-18 16:10:48 -0700786 .attr("class", "ring")
787 .each(ringEnter);
Paul Greysonf8f43172013-03-18 23:00:30 -0700788
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700789
Paul Greysonc17278a2013-03-23 10:17:12 -0700790 function ringUpdate(data, i) {
Paul Greyson127d7fb2013-03-25 23:39:20 -0700791 var nodes = d3.select(this).selectAll("g")
Paul Greyson644d92a2013-03-23 18:00:40 -0700792 .data(data, function (data) {
793 return data.dpid;
Paul Greyson127d7fb2013-03-25 23:39:20 -0700794 });
Paul Greyson347fb742013-03-27 13:40:29 -0700795 nodes.select('circle')
796 .each(function (data) {
797 // if there's a pending state changed and then the state changes, clear the pending class
798 var circle = d3.select(this);
799 if (data.state === 'ACTIVE' && circle.classed('inactive') ||
800 data.state === 'INACTIVE' && circle.classed('active')) {
801 circle.classed('pending', false);
802 }
803 })
804 .attr('class', function (data) {
Paul Greyson8d1c6362013-03-27 13:05:24 -0700805 if (data.state === 'ACTIVE' && data.controller) {
Paul Greyson347fb742013-03-27 13:40:29 -0700806 return data.className + ' active ' + controllerColorMap[data.controller];
Paul Greysonc17278a2013-03-23 10:17:12 -0700807 } else {
Paul Greyson347fb742013-03-27 13:40:29 -0700808 return data.className + ' inactive ' + 'colorInactive';
Paul Greysonc17278a2013-03-23 10:17:12 -0700809 }
Paul Greyson127d7fb2013-03-25 23:39:20 -0700810 });
Paul Greysonc17278a2013-03-23 10:17:12 -0700811 }
812
813 // update switches
814 rings.each(ringUpdate);
815
Paul Greyson968d1b42013-03-23 16:58:41 -0700816
817 // Now setup the labels
818 // This is done separately because SVG draws in node order and we want the labels
819 // always on top
820 var labelRings = svg.selectAll('.labelRing').data(createRingsFromModel(model));
821
Paul Greyson421bfcd2013-03-27 22:22:09 -0700822 d3.select(document.body).on('mousemove', function () {
823 if (!d3.select('#topology').classed('linking')) {
824 return;
825 }
826 var linkVector = document.getElementById('linkVector');
827 if (!linkVector) {
828 return;
829 }
830 linkVector = d3.select(linkVector);
831
832 var highlighted = svg.selectAll('.highlight')[0];
833 var s1 = null, s2 = null;
834 if (highlighted.length > 1) {
835 var s1 = d3.select(highlighted[0]);
836 var s2 = d3.select(highlighted[1]);
837
838 } else if (highlighted.length > 0) {
839 var s1 = d3.select(highlighted[0]);
840 }
841 var src = s1;
842 if (s2 && !s2.data()[0].target) {
843 src = s2;
844 }
845 if (src) {
846 linkVector.attr('d', function () {
847 var srcPt = document.querySelector('svg').createSVGPoint();
848 srcPt.x = src.attr('x');
849 srcPt.y = src.attr('y');
850 srcPt = srcPt.matrixTransform(src[0][0].getCTM());
851
852 var svg = document.getElementById('topology');
853 var mouse = d3.mouse(viewbox);
854 var dstPt = document.querySelector('svg').createSVGPoint();
855 dstPt.x = mouse[0];
856 dstPt.y = mouse[1];
857 dstPt = dstPt.matrixTransform(viewbox.getCTM());
858
859 return line([srcPt, dstPt]);
860 });
861 }
862 });
863
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700864 d3.select(document.body).on('mouseup', function () {
865 function clearHighlight() {
866 svg.selectAll('circle').each(function (data) {
867 data.mouseDown = false;
Paul Greyson72f18852013-03-27 15:56:11 -0700868 d3.select('#topology').classed('linking', false);
Paul Greyson8d1c6362013-03-27 13:05:24 -0700869 mouseOutSwitch(data);
Paul Greyson421bfcd2013-03-27 22:22:09 -0700870 });
871 d3.select('#linkVector').remove();
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700872 };
873
Paul Greyson72f18852013-03-27 15:56:11 -0700874 d3.selectAll('.nodrop').classed('nodrop', false);
875
Paul Greyson084779b2013-03-27 13:55:49 -0700876 function removeLink(link) {
877 var path1 = document.getElementById(link['src-switch'] + '=>' + link['dst-switch']);
878 var path2 = document.getElementById(link['dst-switch'] + '=>' + link['src-switch']);
879
880 if (path1) {
881 setPending(d3.select(path1));
882 }
883 if (path2) {
884 setPending(d3.select(path2));
885 }
886
887 linkDown(link);
888 }
889
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700890
Paul Greyson421bfcd2013-03-27 22:22:09 -0700891 var highlighted = svg.selectAll('.highlight')[0];
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700892 if (highlighted.length == 2) {
Paul Greyson421bfcd2013-03-27 22:22:09 -0700893 var s1Data = highlighted[0].__data__;
894 var s2Data = highlighted[1].__data__;
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700895
896 var srcData, dstData;
897 if (s1Data.target) {
898 dstData = s1Data;
899 srcData = s2Data;
900 } else {
901 dstData = s2Data;
902 srcData = s1Data;
903 }
904
905 if (s1Data.className == 'edge' && s2Data.className == 'edge') {
906 var prompt = 'Create flow from ' + srcData.dpid + ' to ' + dstData.dpid + '?';
907 if (confirm(prompt)) {
Paul Greyson13f02b92013-03-28 11:29:35 -0700908 addFlow(srcData, dstData);
909
910 var flow = {
911 dataPath: {
912 srcPort: {
913 dpid: {
914 value: srcData.dpid
915 }
916 },
917 dstPort: {
918 dpid: {
919 value: dstData.dpid
920 }
921 }
922 },
Paul Greysonaa812562013-03-28 12:43:12 -0700923 createPending: true
Paul Greyson13f02b92013-03-28 11:29:35 -0700924 };
925
926 selectFlow(flow);
927
928 setTimeout(function () {
Paul Greysonaa812562013-03-28 12:43:12 -0700929 deselectFlowIfCreatePending(flow);
Paul Greyson6f918402013-03-28 12:18:30 -0700930 }, pendingTimeout);
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700931 }
932 } else {
933 var map = linkMap[srcData.dpid];
934 if (map && map[dstData.dpid]) {
935 var prompt = 'Remove link between ' + srcData.dpid + ' and ' + dstData.dpid + '?';
936 if (confirm(prompt)) {
Paul Greyson084779b2013-03-27 13:55:49 -0700937 removeLink(map[dstData.dpid]);
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700938 }
939 } else {
Paul Greyson8d1c6362013-03-27 13:05:24 -0700940 map = linkMap[dstData.dpid];
941 if (map && map[srcData.dpid]) {
942 var prompt = 'Remove link between ' + dstData.dpid + ' and ' + srcData.dpid + '?';
943 if (confirm(prompt)) {
Paul Greyson084779b2013-03-27 13:55:49 -0700944 removeLink(map[srcData.dpid]);
Paul Greyson8d1c6362013-03-27 13:05:24 -0700945 }
946 } else {
947 var prompt = 'Create link between ' + srcData.dpid + ' and ' + dstData.dpid + '?';
948 if (confirm(prompt)) {
Paul Greyson40c8a592013-03-27 14:10:33 -0700949 var link1 = {
950 'src-switch': srcData.dpid,
Paul Greyson2913af82013-03-27 14:53:17 -0700951 'src-port': 1,
Paul Greyson40c8a592013-03-27 14:10:33 -0700952 'dst-switch': dstData.dpid,
Paul Greyson2913af82013-03-27 14:53:17 -0700953 'dst-port': 1,
Paul Greyson40c8a592013-03-27 14:10:33 -0700954 pending: true
955 };
956 pendingLinks[makeLinkKey(link1)] = link1;
957 var link2 = {
958 'src-switch': dstData.dpid,
Paul Greyson2913af82013-03-27 14:53:17 -0700959 'src-port': 1,
Paul Greyson40c8a592013-03-27 14:10:33 -0700960 'dst-switch': srcData.dpid,
Paul Greyson2913af82013-03-27 14:53:17 -0700961 'dst-port': 1,
Paul Greyson40c8a592013-03-27 14:10:33 -0700962 pending: true
963 };
964 pendingLinks[makeLinkKey(link2)] = link2;
Paul Greyson5cc35f02013-03-28 10:07:36 -0700965 updateTopology();
Paul Greyson40c8a592013-03-27 14:10:33 -0700966
Paul Greyson2913af82013-03-27 14:53:17 -0700967 linkUp(link1);
Paul Greyson40c8a592013-03-27 14:10:33 -0700968
Paul Greyson5cc35f02013-03-28 10:07:36 -0700969 // remove the pending links after 10s
Paul Greyson40c8a592013-03-27 14:10:33 -0700970 setTimeout(function () {
971 delete pendingLinks[makeLinkKey(link1)];
972 delete pendingLinks[makeLinkKey(link2)];
973
Paul Greyson5cc35f02013-03-28 10:07:36 -0700974 updateTopology();
Paul Greyson6f918402013-03-28 12:18:30 -0700975 }, pendingTimeout);
Paul Greyson8d1c6362013-03-27 13:05:24 -0700976 }
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700977 }
978 }
979 }
980
981 clearHighlight();
982 } else {
983 clearHighlight();
984 }
985
986 });
987
Paul Greyson9066ab02013-03-23 18:15:41 -0700988 function labelRingEnter(data) {
Paul Greyson644d92a2013-03-23 18:00:40 -0700989 if (!data.length) {
Paul Greyson968d1b42013-03-23 16:58:41 -0700990 return;
991 }
992
993 // create the nodes
994 var nodes = d3.select(this).selectAll("g")
Paul Greyson644d92a2013-03-23 18:00:40 -0700995 .data(data, function (data) {
996 return data.dpid;
997 })
Paul Greyson968d1b42013-03-23 16:58:41 -0700998 .enter().append("svg:g")
999 .classed('nolabel', true)
Paul Greyson9066ab02013-03-23 18:15:41 -07001000 .attr("id", function (data) {
Paul Greyson644d92a2013-03-23 18:00:40 -07001001 return data.dpid + '-label';
Paul Greyson968d1b42013-03-23 16:58:41 -07001002 })
Paul Greyson644d92a2013-03-23 18:00:40 -07001003 .attr("transform", function(data, i) {
1004 return "rotate(" + data.angle+ ")translate(" + data.radius * 150 + ")rotate(" + (-data.angle) + ")";
Paul Greyson4e6dc3a2013-03-27 11:37:14 -07001005 })
Paul Greyson968d1b42013-03-23 16:58:41 -07001006
1007 // add the text nodes which show on mouse over
1008 nodes.append("svg:text")
Paul Greyson127d7fb2013-03-25 23:39:20 -07001009 .text(function (data) {return data.dpid;})
Paul Greyson9066ab02013-03-23 18:15:41 -07001010 .attr("x", function (data) {
1011 if (data.angle <= 90 || data.angle >= 270 && data.angle <= 360) {
1012 if (data.className == 'edge') {
Paul Greyson1eb2dd12013-03-23 18:22:00 -07001013 return - data.width*3 - 4;
Paul Greyson9066ab02013-03-23 18:15:41 -07001014 } else {
Paul Greyson1eb2dd12013-03-23 18:22:00 -07001015 return - data.width - 4;
Paul Greyson9066ab02013-03-23 18:15:41 -07001016 }
1017 } else {
1018 if (data.className == 'edge') {
1019 return data.width*3 + 4;
1020 } else {
1021 return data.width + 4;
1022 }
1023 }
1024 })
1025 .attr("y", function (data) {
1026 var y;
1027 if (data.angle <= 90 || data.angle >= 270 && data.angle <= 360) {
1028 if (data.className == 'edge') {
1029 y = data.width*3/2 + 4;
1030 } else {
1031 y = data.width/2 + 4;
1032 }
1033 } else {
1034 if (data.className == 'edge') {
1035 y = data.width*3/2 + 4;
1036 } else {
1037 y = data.width/2 + 4;
1038 }
1039 }
1040 return y - 6;
1041 })
1042 .attr("text-anchor", function (data) {
1043 if (data.angle <= 90 || data.angle >= 270 && data.angle <= 360) {
1044 return "end";
1045 } else {
1046 return "start";
1047 }
1048 })
1049 .attr("transform", function(data) {
Paul Greyson968d1b42013-03-23 16:58:41 -07001050 var m = document.querySelector('#viewbox').getTransformToElement().inverse();
1051 if (data.scale) {
1052 m = m.scale(data.scale);
1053 }
1054 return "matrix( " + m.a + " " + m.b + " " + m.c + " " + m.d + " " + m.e + " " + m.f + " )";
1055 })
1056 }
1057
1058 labelRings.enter().append("svg:g")
1059 .attr("class", "textRing")
1060 .each(labelRingEnter);
1061
Paul Greysonc17278a2013-03-23 10:17:12 -07001062 // switches should not change during operation of the ui so no
1063 // rings.exit()
1064
1065
Paul Greysond1a22d92013-03-19 12:15:19 -07001066 // DRAW THE LINKS
Paul Greysond1a22d92013-03-19 12:15:19 -07001067
Paul Greysonc17278a2013-03-23 10:17:12 -07001068 // key on link dpids since these will come/go during demo
Paul Greyson40c8a592013-03-27 14:10:33 -07001069 var links = d3.select('svg').selectAll('.link').data(links, function (d) {
Paul Greysonc17278a2013-03-23 10:17:12 -07001070 return d['src-switch']+'->'+d['dst-switch'];
1071 });
1072
1073 // add new links
Paul Greysonb367de22013-03-23 11:09:11 -07001074 links.enter().append("svg:path")
Paul Greyson56378ed2013-03-26 23:17:36 -07001075 .attr("class", "link");
1076
Paul Greyson084779b2013-03-27 13:55:49 -07001077 links.attr('id', function (d) {
Paul Greyson40c8a592013-03-27 14:10:33 -07001078 return makeLinkKey(d);
Paul Greyson084779b2013-03-27 13:55:49 -07001079 })
Paul Greyson56378ed2013-03-26 23:17:36 -07001080 .attr("d", function (d) {
Paul Greyson084779b2013-03-27 13:55:49 -07001081 var src = d3.select(document.getElementById(d['src-switch']));
1082 var dst = d3.select(document.getElementById(d['dst-switch']));
Paul Greysonc17278a2013-03-23 10:17:12 -07001083
Paul Greyson084779b2013-03-27 13:55:49 -07001084 var srcPt = document.querySelector('svg').createSVGPoint();
1085 srcPt.x = src.attr('x');
1086 srcPt.y = src.attr('y');
1087 srcPt = srcPt.matrixTransform(src[0][0].getCTM());
Paul Greysond1a22d92013-03-19 12:15:19 -07001088
Paul Greyson084779b2013-03-27 13:55:49 -07001089 var dstPt = document.querySelector('svg').createSVGPoint();
1090 dstPt.x = dst.attr('x');
Paul Greyson421bfcd2013-03-27 22:22:09 -07001091 dstPt.y = dst.attr('y');
Paul Greyson084779b2013-03-27 13:55:49 -07001092 dstPt = dstPt.matrixTransform(dst[0][0].getCTM());
Paul Greysond1a22d92013-03-19 12:15:19 -07001093
Paul Greyson084779b2013-03-27 13:55:49 -07001094 var midPt = document.querySelector('svg').createSVGPoint();
1095 midPt.x = (srcPt.x + dstPt.x)/2;
1096 midPt.y = (srcPt.y + dstPt.y)/2;
Paul Greysond1a22d92013-03-19 12:15:19 -07001097
Paul Greyson084779b2013-03-27 13:55:49 -07001098 return line([srcPt, midPt, dstPt]);
1099 })
Paul Greyson40c8a592013-03-27 14:10:33 -07001100 .attr("marker-mid", function(d) { return "url(#arrow)"; })
1101 .classed('pending', function (d) {
1102 return d.pending;
1103 });
Paul Greysonc17278a2013-03-23 10:17:12 -07001104
Paul Greyson56378ed2013-03-26 23:17:36 -07001105
Paul Greysonc17278a2013-03-23 10:17:12 -07001106 // remove old links
1107 links.exit().remove();
Paul Greysond1a22d92013-03-19 12:15:19 -07001108}
1109
Paul Greyson5cc35f02013-03-28 10:07:36 -07001110function updateControllers() {
Paul Greysond1a22d92013-03-19 12:15:19 -07001111 var controllers = d3.select('#controllerList').selectAll('.controller').data(model.controllers);
Paul Greyson3e142162013-03-19 13:56:17 -07001112 controllers.enter().append('div')
Paul Greysone262a292013-03-23 10:35:23 -07001113 .each(function (c) {
1114 controllerColorMap[c] = colors.pop();
1115 d3.select(document.body).classed(controllerColorMap[c] + '-selected', true);
1116 })
1117 .text(function (d) {
1118 return d;
Paul Greyson2913af82013-03-27 14:53:17 -07001119 })
1120 .append('div')
Paul Greyson8247c3f2013-03-28 00:24:02 -07001121 .attr('class', 'black-eye');
Paul Greysonbcd3c772013-03-21 13:16:44 -07001122
Paul Greysone262a292013-03-23 10:35:23 -07001123 controllers.attr('class', function (d) {
Paul Greysoneed36352013-03-23 11:19:11 -07001124 var color = 'colorInactive';
Paul Greysonbcd3c772013-03-21 13:16:44 -07001125 if (model.activeControllers.indexOf(d) != -1) {
1126 color = controllerColorMap[d];
Paul Greysond1a22d92013-03-19 12:15:19 -07001127 }
Paul Greysonbcd3c772013-03-21 13:16:44 -07001128 var className = 'controller ' + color;
1129 return className;
Paul Greysond1a22d92013-03-19 12:15:19 -07001130 });
Paul Greysond1a22d92013-03-19 12:15:19 -07001131
Paul Greysone262a292013-03-23 10:35:23 -07001132 // this should never be needed
1133 // controllers.exit().remove();
Paul Greysond1a22d92013-03-19 12:15:19 -07001134
Paul Greyson2913af82013-03-27 14:53:17 -07001135 controllers.on('dblclick', function (c) {
1136 if (model.activeControllers.indexOf(c) != -1) {
1137 var prompt = 'Dectivate ' + c + '?';
1138 if (confirm(prompt)) {
1139 controllerDown(c);
1140 setPending(d3.select(this));
1141 };
1142 } else {
1143 var prompt = 'Activate ' + c + '?';
1144 if (confirm(prompt)) {
1145 controllerUp(c);
1146 setPending(d3.select(this));
1147 };
1148 }
1149 });
1150
Paul Greyson8247c3f2013-03-28 00:24:02 -07001151 controllers.select('.black-eye').on('click', function (c) {
Paul Greysonc3e21a02013-03-21 13:56:05 -07001152 var allSelected = true;
1153 for (var key in controllerColorMap) {
1154 if (!d3.select(document.body).classed(controllerColorMap[key] + '-selected')) {
1155 allSelected = false;
1156 break;
1157 }
1158 }
1159 if (allSelected) {
1160 for (var key in controllerColorMap) {
1161 d3.select(document.body).classed(controllerColorMap[key] + '-selected', key == c)
1162 }
1163 } else {
1164 for (var key in controllerColorMap) {
1165 d3.select(document.body).classed(controllerColorMap[key] + '-selected', true)
1166 }
1167 }
1168
1169 // var selected = d3.select(document.body).classed(controllerColorMap[c] + '-selected');
1170 // d3.select(document.body).classed(controllerColorMap[c] + '-selected', !selected);
Paul Greysond1a22d92013-03-19 12:15:19 -07001171 });
Paul Greyson8d1c6362013-03-27 13:05:24 -07001172
1173
Paul Greyson740bdaf2013-03-18 16:10:48 -07001174}
1175
Paul Greyson2c35f572013-04-04 16:23:48 -07001176var modelString;
Paul Greyson29aa98d2013-03-28 00:09:31 -07001177function sync(svg) {
Paul Greysonbcd3c772013-03-21 13:16:44 -07001178 var d = Date.now();
Paul Greysonb48943b2013-03-19 13:27:57 -07001179 updateModel(function (newModel) {
Paul Greyson4e6dc3a2013-03-27 11:37:14 -07001180// console.log('Update time: ' + (Date.now() - d)/1000 + 's');
Paul Greyson740bdaf2013-03-18 16:10:48 -07001181
Paul Greysone5991b52013-04-04 01:34:04 -07001182 if (newModel) {
1183 var modelChanged = false;
Paul Greyson2c35f572013-04-04 16:23:48 -07001184 var newModelString = JSON.stringify(newModel);
1185 if (!modelString || newModelString != modelString) {
Paul Greysone5991b52013-04-04 01:34:04 -07001186 modelChanged = true;
1187 model = newModel;
Paul Greyson2c35f572013-04-04 16:23:48 -07001188 modelString = newModelString;
Paul Greysone5991b52013-04-04 01:34:04 -07001189 } else {
1190 // console.log('no change');
1191 }
Paul Greysonb48943b2013-03-19 13:27:57 -07001192
Paul Greysone5991b52013-04-04 01:34:04 -07001193 if (modelChanged) {
1194 updateControllers();
1195 updateSelectedFlows();
1196 updateTopology();
1197 }
Paul Greyson5cc35f02013-03-28 10:07:36 -07001198
Paul Greysone5991b52013-04-04 01:34:04 -07001199 updateHeader(newModel);
1200 }
Paul Greyson740bdaf2013-03-18 16:10:48 -07001201
1202 // do it again in 1s
1203 setTimeout(function () {
Paul Greysona36a9232013-03-22 22:41:27 -07001204 sync(svg)
Paul Greysond1a22d92013-03-19 12:15:19 -07001205 }, 1000);
Paul Greyson6f86d1e2013-03-18 14:40:39 -07001206 });
1207}
Paul Greyson740bdaf2013-03-18 16:10:48 -07001208
Paul Greyson38d8bde2013-03-22 22:07:35 -07001209svg = createTopologyView();
Paul Greyson29aa98d2013-03-28 00:09:31 -07001210updateSelectedFlows();
1211
1212d3.select('#showFlowChooser').on('click', function () {
1213 showFlowChooser();
1214});
1215
Paul Greyson72f18852013-03-27 15:56:11 -07001216
Paul Greyson38d8bde2013-03-22 22:07:35 -07001217// workaround for Chrome v25 bug
1218// if executed immediately, the view box transform logic doesn't work properly
1219// fixed in Chrome v27
1220setTimeout(function () {
1221 // workaround for another Chrome v25 bug
1222 // viewbox transform stuff doesn't work in combination with browser zoom
Paul Greysonc17278a2013-03-23 10:17:12 -07001223 // also works in Chrome v27
Paul Greyson38d8bde2013-03-22 22:07:35 -07001224 d3.select('#svg-container').style('zoom', window.document.body.clientWidth/window.document.width);
Paul Greyson29aa98d2013-03-28 00:09:31 -07001225 sync(svg);
Paul Greyson38d8bde2013-03-22 22:07:35 -07001226}, 100);