blob: 6fa1bd34e459947bf4ff8a14a5a9d189e87657c5 [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 Greysonbb403b02013-04-04 17:11:24 -070099 if (!d.dataPath.flowEntries) {
Paul Greyson13f02b92013-03-28 11:29:35 -0700100 // create a temporary vector to indicate the pending flow
Jonathan Hart3888b042013-04-04 20:04:26 -0700101 var s1 = d3.select(document.getElementById(d.srcDpid));
102 var s2 = d3.select(document.getElementById(d.dstDpid));
Paul Greyson13f02b92013-03-28 11:29:35 -0700103
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 }
Paul Greysonbb403b02013-04-04 17:11:24 -0700131 if (pts.length) {
132 return line(pts);
133 } else {
134 return "M0,0";
135 }
Paul Greyson13f02b92013-03-28 11:29:35 -0700136 })
Paul Greyson71b550d2013-03-28 11:56:19 -0700137 .attr('id', function (d) {
138 if (d) {
139 return makeFlowKey(d);
140 }
141 })
Paul Greyson13f02b92013-03-28 11:29:35 -0700142 .classed('pending', function (d) {
Paul Greysonaa812562013-03-28 12:43:12 -0700143 return d && (d.createPending || d.deletePending);
Paul Greyson127d7fb2013-03-25 23:39:20 -0700144 });
Paul Greyson127d7fb2013-03-25 23:39:20 -0700145
Paul Greyson56378ed2013-03-26 23:17:36 -0700146 // "marching ants"
Paul Greyson29aa98d2013-03-28 00:09:31 -0700147 flows.select('animate').attr('from', 500);
148
Paul Greyson13f02b92013-03-28 11:29:35 -0700149}
150
151function updateSelectedFlowsTable() {
152 function rowEnter(d) {
153 var row = d3.select(this);
Paul Greyson6f918402013-03-28 12:18:30 -0700154 row.append('div').classed('deleteFlow', true);
Paul Greyson13f02b92013-03-28 11:29:35 -0700155 row.append('div').classed('flowId', true);
156 row.append('div').classed('srcDPID', true);
157 row.append('div').classed('dstDPID', true);
158 row.append('div').classed('iperf', true);
Paul Greyson71b550d2013-03-28 11:56:19 -0700159
Paul Greyson95db7a12013-04-04 14:57:58 -0700160 row.select('.iperf')
161 .append('div')
162 .attr('class', 'iperf-container')
163 .append('svg:svg')
164 .attr('viewBox', '0 0 1000 32')
165 .attr('preserveAspectRatio', 'none')
166 .append('svg:g')
167 .append('svg:path')
168 .attr('class', 'iperfdata');
169
Paul Greyson71b550d2013-03-28 11:56:19 -0700170 row.on('mouseover', function (d) {
171 if (d) {
172 var path = document.getElementById(makeFlowKey(d));
173 d3.select(path).classed('highlight', true);
174 }
175 });
176 row.on('mouseout', function (d) {
177 if (d) {
178 var path = document.getElementById(makeFlowKey(d));
179 d3.select(path).classed('highlight', false);
180 }
Paul Greyson95db7a12013-04-04 14:57:58 -0700181 });
Paul Greyson13f02b92013-03-28 11:29:35 -0700182 }
183
184 function rowUpdate(d) {
185 var row = d3.select(this);
Paul Greyson95db7a12013-04-04 14:57:58 -0700186 row.attr('id', function (d) {
187 if (d) {
188 return makeSelectedFlowKey(d);
189 }
190 });
Paul Greyson2c35f572013-04-04 16:23:48 -0700191
Paul Greysonbb403b02013-04-04 17:11:24 -0700192 if (!d || !hasIPerf(d)) {
Paul Greyson2c35f572013-04-04 16:23:48 -0700193 row.select('.iperfdata')
194 .attr('d', 'M0,0');
195 }
196
Paul Greyson6f918402013-03-28 12:18:30 -0700197 row.select('.deleteFlow').on('click', function () {
Paul Greyson2c35f572013-04-04 16:23:48 -0700198 deselectFlow(d);
Paul Greysonbe8bf872013-04-04 01:53:45 -0700199 });
200 row.on('dblclick', function () {
Paul Greyson6f918402013-03-28 12:18:30 -0700201 if (d) {
Jonathan Hart3888b042013-04-04 20:04:26 -0700202 var prompt = 'Delete flow ' + d.flowId + '?';
Paul Greyson6f918402013-03-28 12:18:30 -0700203 if (confirm(prompt)) {
204 deleteFlow(d);
Paul Greysonaa812562013-03-28 12:43:12 -0700205 d.deletePending = true;
Paul Greyson6f918402013-03-28 12:18:30 -0700206 updateSelectedFlows();
207
208 setTimeout(function () {
Paul Greysonaa812562013-03-28 12:43:12 -0700209 d.deletePending = false;
Paul Greyson6f918402013-03-28 12:18:30 -0700210 updateSelectedFlows();
211 }, pendingTimeout)
212 };
213 }
214 });
215
Paul Greyson13f02b92013-03-28 11:29:35 -0700216 row.select('.flowId')
217 .text(function (d) {
218 if (d) {
219 if (d.flowId) {
Jonathan Hart3888b042013-04-04 20:04:26 -0700220 return d.flowId;
Paul Greyson13f02b92013-03-28 11:29:35 -0700221 } else {
222 return '0x--';
223 }
224 }
225 })
Paul Greysoncb5d30d2013-04-04 02:00:56 -0700226 .classed('pending', function (d) {
227 return d && (d.createPending || d.deletePending);
228 });
Paul Greyson13f02b92013-03-28 11:29:35 -0700229
230 row.select('.srcDPID')
231 .text(function (d) {
232 if (d) {
Jonathan Hart3888b042013-04-04 20:04:26 -0700233 return d.srcDpid;
Paul Greyson13f02b92013-03-28 11:29:35 -0700234 }
235 });
236
237 row.select('.dstDPID')
238 .text(function (d) {
239 if (d) {
Jonathan Hart3888b042013-04-04 20:04:26 -0700240 return d.dstDpid;
Paul Greyson13f02b92013-03-28 11:29:35 -0700241 }
242 });
243 }
244
245 var flows = d3.select('#selectedFlows')
246 .selectAll('.selectedFlow')
247 .data(selectedFlows);
248
249 flows.enter()
250 .append('div')
251 .classed('selectedFlow', true)
252 .each(rowEnter);
253
254 flows.each(rowUpdate);
255
Paul Greyson29aa98d2013-03-28 00:09:31 -0700256 flows.exit().remove();
Paul Greyson127d7fb2013-03-25 23:39:20 -0700257}
258
Paul Greyson4b4c8af2013-04-04 09:02:09 -0700259// TODO: cancel the interval when the flow is desel
260function startIPerfForFlow(flow) {
Paul Greyson95db7a12013-04-04 14:57:58 -0700261 var duration = 10000; // seconds
Paul Greyson4b4c8af2013-04-04 09:02:09 -0700262 var interval = 100; // ms. this is defined by the server
Paul Greysonbb403b02013-04-04 17:11:24 -0700263 var updateRate = 2000; // ms
Paul Greysonf7a77db2013-04-04 18:48:20 -0700264 var pointsToDisplay = 1000;
Paul Greyson4b4c8af2013-04-04 09:02:09 -0700265
Paul Greyson2c35f572013-04-04 16:23:48 -0700266 function makePoints() {
267 var pts = [];
268 var i;
Paul Greysonf7a77db2013-04-04 18:48:20 -0700269 for (i=0; i < pointsToDisplay; ++i) {
Paul Greyson2c35f572013-04-04 16:23:48 -0700270 var sample = flow.iperfData.samples[i];
Umesh Krishnaswamy5c3eddf2013-04-06 00:24:01 -0700271 var height = 30 * sample/1000000;
272 if (height > 30)
273 height = 30;
Paul Greyson2c35f572013-04-04 16:23:48 -0700274 pts.push({
Paul Greysonf7a77db2013-04-04 18:48:20 -0700275 x: i * 1000/(pointsToDisplay-1),
Paul Greyson2c35f572013-04-04 16:23:48 -0700276 y: 32 - height
277 })
278 }
279 return pts;
280 }
281
Paul Greyson4b4c8af2013-04-04 09:02:09 -0700282 if (flow.flowId) {
Jonathan Hart4cf35b82013-04-04 20:55:49 -0700283 console.log('starting iperf for: ' + flow.flowId);
Paul Greyson4b4c8af2013-04-04 09:02:09 -0700284 startIPerf(flow, duration, updateRate/interval);
Paul Greyson2c35f572013-04-04 16:23:48 -0700285 flow.iperfDisplayInterval = setInterval(function () {
286 if (flow.iperfData) {
Paul Greysonf7a77db2013-04-04 18:48:20 -0700287 while (flow.iperfData.samples.length < pointsToDisplay) {
Paul Greysonbb403b02013-04-04 17:11:24 -0700288 flow.iperfData.samples.push(0);
Paul Greyson2c35f572013-04-04 16:23:48 -0700289 }
290 var iperfPath = d3.select(document.getElementById(makeSelectedFlowKey(flow))).select('path');
291 iperfPath.attr('d', line(makePoints()));
292 flow.iperfData.samples.shift();
293 }
294
295
296 }, interval);
297 flow.iperfFetchInterval = setInterval(function () {
Paul Greyson4b4c8af2013-04-04 09:02:09 -0700298 getIPerfData(flow, function (data) {
Paul Greyson95db7a12013-04-04 14:57:58 -0700299 try {
Paul Greyson2c35f572013-04-04 16:23:48 -0700300 if (!flow.iperfData) {
301 flow.iperfData = {
302 samples: []
303 };
304 var i;
Paul Greysonf7a77db2013-04-04 18:48:20 -0700305 for (i = 0; i < pointsToDisplay; ++i) {
Paul Greyson2c35f572013-04-04 16:23:48 -0700306 flow.iperfData.samples.push(0);
307 }
308 }
309
Paul Greyson95db7a12013-04-04 14:57:58 -0700310 var iperfData = JSON.parse(data);
Paul Greyson50aa8b52013-04-08 15:01:34 -0700311
Paul Greyson7cc528b2013-04-08 15:39:04 -0700312// console.log(iperfData.timestamp);
Paul Greyson50aa8b52013-04-08 15:01:34 -0700313
Paul Greyson95db7a12013-04-04 14:57:58 -0700314 // if the data is fresh
Paul Greysonbb403b02013-04-04 17:11:24 -0700315 if (flow.iperfData.timestamp && iperfData.timestamp != flow.iperfData.timestamp) {
Paul Greyson7cc528b2013-04-08 15:39:04 -0700316
317 while (flow.iperfData.samples.length > pointsToDisplay + iperfData.samples.length) {
318 flow.iperfData.samples.shift();
319 }
320
Paul Greyson2c35f572013-04-04 16:23:48 -0700321 iperfData.samples.forEach(function (s) {
322 flow.iperfData.samples.push(s);
323 });
Paul Greyson95db7a12013-04-04 14:57:58 -0700324 }
Paul Greyson2c35f572013-04-04 16:23:48 -0700325 flow.iperfData.timestamp = iperfData.timestamp;
Paul Greyson95db7a12013-04-04 14:57:58 -0700326 } catch (e) {
327 console.log('bad iperf data: ' + data);
328 }
329// console.log(data);
Paul Greyson4b4c8af2013-04-04 09:02:09 -0700330 });
Paul Greysonbb403b02013-04-04 17:11:24 -0700331 }, updateRate/2); // over sample to avoid gaps
Paul Greyson4b4c8af2013-04-04 09:02:09 -0700332 }
333}
334
Paul Greyson13f02b92013-03-28 11:29:35 -0700335function updateSelectedFlows() {
336 // make sure that all of the selected flows are either
337 // 1) valid (meaning they are in the latest list of flows)
338 // 2) pending
339 if (model) {
340 var flowMap = {};
341 model.flows.forEach(function (flow) {
342 flowMap[makeFlowKey(flow)] = flow;
343 });
344
345 var newSelectedFlows = [];
346 selectedFlows.forEach(function (flow) {
347 if (flow) {
Paul Greysonf430fd02013-03-28 12:32:24 -0700348 var liveFlow = flowMap[makeFlowKey(flow)];
349 if (liveFlow) {
350 newSelectedFlows.push(liveFlow);
Paul Greysonaa812562013-03-28 12:43:12 -0700351 liveFlow.deletePending = flow.deletePending;
Paul Greyson2c35f572013-04-04 16:23:48 -0700352 liveFlow.iperfFetchInterval = flow.iperfFetchInterval;
353 liveFlow.iperfDisplayInterval = flow.iperfDisplayInterval;
Paul Greysonaa812562013-03-28 12:43:12 -0700354 } else if (flow.createPending) {
Paul Greyson13f02b92013-03-28 11:29:35 -0700355 newSelectedFlows.push(flow);
Paul Greysonbb403b02013-04-04 17:11:24 -0700356 } else if (hasIPerf(flow)) {
357 clearIPerf(flow);
Paul Greyson13f02b92013-03-28 11:29:35 -0700358 }
Paul Greyson13f02b92013-03-28 11:29:35 -0700359 }
360 });
361 selectedFlows = newSelectedFlows;
362 }
Paul Greyson4b4c8af2013-04-04 09:02:09 -0700363 selectedFlows.forEach(function (flow) {
Paul Greysonbb403b02013-04-04 17:11:24 -0700364 if (!hasIPerf(flow)) {
Paul Greyson4b4c8af2013-04-04 09:02:09 -0700365 startIPerfForFlow(flow);
366 }
367 });
Paul Greyson6f918402013-03-28 12:18:30 -0700368 while (selectedFlows.length < 3) {
369 selectedFlows.push(null);
370 }
Paul Greyson13f02b92013-03-28 11:29:35 -0700371
372 updateSelectedFlowsTable();
373 updateSelectedFlowsTopology();
374}
375
376function selectFlow(flow) {
Paul Greysonc30f75e2013-03-28 11:45:15 -0700377 var flowKey = makeFlowKey(flow);
378 var alreadySelected = false;
379 selectedFlows.forEach(function (f) {
380 if (f && makeFlowKey(f) === flowKey) {
381 alreadySelected = true;
382 }
383 });
384
385 if (!alreadySelected) {
386 selectedFlows.unshift(flow);
387 selectedFlows = selectedFlows.slice(0, 3);
388 updateSelectedFlows();
389 }
Paul Greyson13f02b92013-03-28 11:29:35 -0700390}
391
Paul Greysonbb403b02013-04-04 17:11:24 -0700392function hasIPerf(flow) {
393 return flow && flow.iperfFetchInterval;
394}
395
396function clearIPerf(flow) {
Jonathan Hart4cf35b82013-04-04 20:55:49 -0700397 console.log('clearing iperf interval for: ' + flow.flowId);
Paul Greysonbb403b02013-04-04 17:11:24 -0700398 clearInterval(flow.iperfFetchInterval);
399 delete flow.iperfFetchInterval;
400 clearInterval(flow.iperfDisplayInterval);
401 delete flow.iperfDisplayInterval;
402 delete flow.iperfData;
403}
404
Paul Greysonaa812562013-03-28 12:43:12 -0700405function deselectFlow(flow, ifCreatePending) {
Paul Greyson13f02b92013-03-28 11:29:35 -0700406 var flowKey = makeFlowKey(flow);
407 var newSelectedFlows = [];
408 selectedFlows.forEach(function (flow) {
Paul Greysonf430fd02013-03-28 12:32:24 -0700409 if (!flow ||
410 flowKey !== makeFlowKey(flow) ||
Paul Greysonaa812562013-03-28 12:43:12 -0700411 flowKey === makeFlowKey(flow) && ifCreatePending && !flow.createPending ) {
Paul Greyson13f02b92013-03-28 11:29:35 -0700412 newSelectedFlows.push(flow);
Paul Greyson4b4c8af2013-04-04 09:02:09 -0700413 } else {
Paul Greysonbb403b02013-04-04 17:11:24 -0700414 if (hasIPerf(flow)) {
415 clearIPerf(flow);
Paul Greyson4b4c8af2013-04-04 09:02:09 -0700416 }
Paul Greyson13f02b92013-03-28 11:29:35 -0700417 }
418 });
419 selectedFlows = newSelectedFlows;
420 while (selectedFlows.length < 3) {
421 selectedFlows.push(null);
422 }
423
424 updateSelectedFlows();
425}
426
Paul Greysonaa812562013-03-28 12:43:12 -0700427function deselectFlowIfCreatePending(flow) {
Paul Greysonf430fd02013-03-28 12:32:24 -0700428 deselectFlow(flow, true);
429}
430
Paul Greyson29aa98d2013-03-28 00:09:31 -0700431function showFlowChooser() {
432 function rowEnter(d) {
Paul Greyson127d7fb2013-03-25 23:39:20 -0700433 var row = d3.select(this);
434
Paul Greyson127d7fb2013-03-25 23:39:20 -0700435 row.append('div')
Paul Greyson8247c3f2013-03-28 00:24:02 -0700436 .classed('black-eye', true).
Paul Greyson29aa98d2013-03-28 00:09:31 -0700437 on('click', function () {
Paul Greyson13f02b92013-03-28 11:29:35 -0700438 selectFlow(d);
Paul Greyson127d7fb2013-03-25 23:39:20 -0700439 });
440
441 row.append('div')
Paul Greyson29aa98d2013-03-28 00:09:31 -0700442 .classed('flowId', true)
443 .text(function (d) {
Jonathan Hart3888b042013-04-04 20:04:26 -0700444 return d.flowId;
Paul Greyson29aa98d2013-03-28 00:09:31 -0700445 });
Paul Greyson127d7fb2013-03-25 23:39:20 -0700446
447 row.append('div')
Paul Greyson29aa98d2013-03-28 00:09:31 -0700448 .classed('srcDPID', true)
449 .text(function (d) {
Jonathan Hart3888b042013-04-04 20:04:26 -0700450 return d.srcDpid;
Paul Greyson29aa98d2013-03-28 00:09:31 -0700451 });
452
Paul Greyson127d7fb2013-03-25 23:39:20 -0700453
454 row.append('div')
Paul Greyson29aa98d2013-03-28 00:09:31 -0700455 .classed('dstDPID', true)
456 .text(function (d) {
Jonathan Hart3888b042013-04-04 20:04:26 -0700457 return d.dstDpid;
Paul Greyson29aa98d2013-03-28 00:09:31 -0700458 });
Paul Greyson127d7fb2013-03-25 23:39:20 -0700459
Paul Greyson127d7fb2013-03-25 23:39:20 -0700460 }
461
Paul Greyson29aa98d2013-03-28 00:09:31 -0700462 var flows = d3.select('#flowChooser')
463 .append('div')
464 .style('pointer-events', 'auto')
Paul Greyson127d7fb2013-03-25 23:39:20 -0700465 .selectAll('.selectedFlow')
Paul Greyson29aa98d2013-03-28 00:09:31 -0700466 .data(model.flows)
Paul Greyson127d7fb2013-03-25 23:39:20 -0700467 .enter()
468 .append('div')
469 .classed('selectedFlow', true)
470 .each(rowEnter);
471
Paul Greyson29aa98d2013-03-28 00:09:31 -0700472 setTimeout(function () {
473 d3.select(document.body).on('click', function () {
474 d3.select('#flowChooser').html('');
475 d3.select(document.body).on('click', null);
476 });
477 }, 0);
478}
479
Paul Greyson29aa98d2013-03-28 00:09:31 -0700480
Paul Greyson127d7fb2013-03-25 23:39:20 -0700481
Paul Greysond1a22d92013-03-19 12:15:19 -0700482function updateHeader(model) {
Paul Greysonb48943b2013-03-19 13:27:57 -0700483 d3.select('#lastUpdate').text(new Date());
Paul Greyson952ccb62013-03-18 22:22:08 -0700484 d3.select('#activeSwitches').text(model.edgeSwitches.length + model.aggregationSwitches.length + model.coreSwitches.length);
485 d3.select('#activeFlows').text(model.flows.length);
486}
487
488function toRadians (angle) {
489 return angle * (Math.PI / 180);
Paul Greyson740bdaf2013-03-18 16:10:48 -0700490}
491
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700492var widths = {
493 edge: 6,
494 aggregation: 12,
495 core: 18
496}
497
Paul Greysonc17278a2013-03-23 10:17:12 -0700498function createRingsFromModel(model) {
Paul Greyson740bdaf2013-03-18 16:10:48 -0700499 var rings = [{
500 radius: 3,
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700501 width: widths.edge,
Paul Greyson952ccb62013-03-18 22:22:08 -0700502 switches: model.edgeSwitches,
Paul Greysonde7fad52013-03-19 12:47:32 -0700503 className: 'edge',
504 angles: []
Paul Greyson740bdaf2013-03-18 16:10:48 -0700505 }, {
Paul Greysond1a22d92013-03-19 12:15:19 -0700506 radius: 2.25,
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700507 width: widths.aggregation,
Paul Greyson952ccb62013-03-18 22:22:08 -0700508 switches: model.aggregationSwitches,
Paul Greysonde7fad52013-03-19 12:47:32 -0700509 className: 'aggregation',
510 angles: []
Paul Greyson740bdaf2013-03-18 16:10:48 -0700511 }, {
Paul Greyson127d7fb2013-03-25 23:39:20 -0700512 radius: 0.75,
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700513 width: widths.core,
Paul Greyson952ccb62013-03-18 22:22:08 -0700514 switches: model.coreSwitches,
Paul Greysonde7fad52013-03-19 12:47:32 -0700515 className: 'core',
516 angles: []
Paul Greyson740bdaf2013-03-18 16:10:48 -0700517 }];
518
Paul Greysonde7fad52013-03-19 12:47:32 -0700519
520 var aggRanges = {};
521
522 // arrange edge switches at equal increments
523 var k = 360 / rings[0].switches.length;
524 rings[0].switches.forEach(function (s, i) {
525 var angle = k * i;
526
527 rings[0].angles[i] = angle;
528
529 // record the angle for the agg switch layout
530 var dpid = s.dpid.split(':');
Paul Greyson832d2202013-03-21 13:27:56 -0700531 dpid[7] = '01'; // the last component of the agg switch is always '01'
Paul Greysonde7fad52013-03-19 12:47:32 -0700532 var aggdpid = dpid.join(':');
533 var aggRange = aggRanges[aggdpid];
534 if (!aggRange) {
535 aggRange = aggRanges[aggdpid] = {};
536 aggRange.min = aggRange.max = angle;
537 } else {
538 aggRange.max = angle;
539 }
Paul Greysonde7fad52013-03-19 12:47:32 -0700540 });
541
542 // arrange aggregation switches to "fan out" to edge switches
543 k = 360 / rings[1].switches.length;
544 rings[1].switches.forEach(function (s, i) {
545// rings[1].angles[i] = k * i;
546 var range = aggRanges[s.dpid];
547
Paul Greyson832d2202013-03-21 13:27:56 -0700548 rings[1].angles[i] = (range.min + range.max)/2;
Paul Greysonde7fad52013-03-19 12:47:32 -0700549 });
550
Paul Greyson3f890b62013-03-22 17:39:36 -0700551 // find the association between core switches and aggregation switches
552 var aggregationSwitchMap = {};
553 model.aggregationSwitches.forEach(function (s, i) {
Paul Greysonc17278a2013-03-23 10:17:12 -0700554 aggregationSwitchMap[s.dpid] = i;
Paul Greyson3f890b62013-03-22 17:39:36 -0700555 });
556
Paul Greyson3f890b62013-03-22 17:39:36 -0700557 // put core switches next to linked aggregation switches
Paul Greysonde7fad52013-03-19 12:47:32 -0700558 k = 360 / rings[2].switches.length;
559 rings[2].switches.forEach(function (s, i) {
Paul Greyson3f890b62013-03-22 17:39:36 -0700560// rings[2].angles[i] = k * i;
Paul Greysonc17278a2013-03-23 10:17:12 -0700561 var associatedAggregationSwitches = model.configuration.association[s.dpid];
562 // TODO: go between if there are multiple
563 var index = aggregationSwitchMap[associatedAggregationSwitches[0]];
564
565 rings[2].angles[i] = rings[1].angles[index];
Paul Greysonde7fad52013-03-19 12:47:32 -0700566 });
567
Paul Greyson644d92a2013-03-23 18:00:40 -0700568 // TODO: construct this form initially rather than converting. it works better because
569 // it allows binding by dpid
570 var testRings = [];
571 rings.forEach(function (ring) {
572 var testRing = [];
573 ring.switches.forEach(function (s, i) {
574 var testSwitch = {
575 dpid: s.dpid,
576 state: s.state,
577 radius: ring.radius,
578 width: ring.width,
579 className: ring.className,
580 angle: ring.angles[i],
581 controller: s.controller
Paul Greyson127d7fb2013-03-25 23:39:20 -0700582 };
Paul Greyson644d92a2013-03-23 18:00:40 -0700583 testRing.push(testSwitch);
584 });
Paul Greyson6d9ed862013-03-23 17:37:15 -0700585
586
Paul Greyson644d92a2013-03-23 18:00:40 -0700587 testRings.push(testRing);
588 });
Paul Greyson6d9ed862013-03-23 17:37:15 -0700589
590
Paul Greyson644d92a2013-03-23 18:00:40 -0700591// return rings;
592 return testRings;
Paul Greysonc17278a2013-03-23 10:17:12 -0700593}
594
Paul Greyson40c8a592013-03-27 14:10:33 -0700595function makeLinkKey(link) {
596 return link['src-switch'] + '=>' + link['dst-switch'];
597}
598
Paul Greyson13f02b92013-03-28 11:29:35 -0700599function makeFlowKey(flow) {
Jonathan Hart3888b042013-04-04 20:04:26 -0700600 return flow.srcDpid + '=>' + flow.dstDpid;
Paul Greyson13f02b92013-03-28 11:29:35 -0700601}
602
Paul Greyson95db7a12013-04-04 14:57:58 -0700603function makeSelectedFlowKey(flow) {
604 return 'S' + makeFlowKey(flow);
605}
606
Paul Greyson40c8a592013-03-27 14:10:33 -0700607function createLinkMap(links) {
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700608 var linkMap = {};
Paul Greyson40c8a592013-03-27 14:10:33 -0700609 links.forEach(function (link) {
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700610 var srcDPID = link['src-switch'];
611 var dstDPID = link['dst-switch'];
612
613 var srcMap = linkMap[srcDPID] || {};
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700614
Paul Greyson8d1c6362013-03-27 13:05:24 -0700615 srcMap[dstDPID] = link;
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700616
617 linkMap[srcDPID] = srcMap;
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700618 });
619 return linkMap;
620}
621
Paul Greyson5cc35f02013-03-28 10:07:36 -0700622// removes links from the pending list that are now in the model
623function reconcilePendingLinks(model) {
Paul Greyson40c8a592013-03-27 14:10:33 -0700624 var links = [];
625 model.links.forEach(function (link) {
626 links.push(link);
627 delete pendingLinks[makeLinkKey(link)]
628 })
629 var linkId;
630 for (linkId in pendingLinks) {
631 links.push(pendingLinks[linkId]);
632 }
Paul Greyson5cc35f02013-03-28 10:07:36 -0700633 return links
634}
Paul Greyson40c8a592013-03-27 14:10:33 -0700635
Paul Greyson5cc35f02013-03-28 10:07:36 -0700636updateTopology = function() {
637
638 // DRAW THE SWITCHES
639 var rings = svg.selectAll('.ring').data(createRingsFromModel(model));
640
641 var links = reconcilePendingLinks(model);
Paul Greyson40c8a592013-03-27 14:10:33 -0700642 var linkMap = createLinkMap(links);
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700643
Paul Greyson8d1c6362013-03-27 13:05:24 -0700644 function mouseOverSwitch(data) {
Paul Greyson72f18852013-03-27 15:56:11 -0700645
646 d3.event.preventDefault();
647
Paul Greyson5cc35f02013-03-28 10:07:36 -0700648 d3.select(document.getElementById(data.dpid + '-label')).classed('nolabel', false);
649
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700650 if (data.highlighted) {
651 return;
652 }
653
654 // only highlight valid link or flow destination by checking for class of existing highlighted circle
Paul Greyson421bfcd2013-03-27 22:22:09 -0700655 var highlighted = svg.selectAll('.highlight')[0];
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700656 if (highlighted.length == 1) {
Paul Greyson421bfcd2013-03-27 22:22:09 -0700657 var s = d3.select(highlighted[0]).select('circle');
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700658 // only allow links
659 // edge->edge (flow)
660 // aggregation->core
661 // core->core
662 if (data.className == 'edge' && !s.classed('edge') ||
663 data.className == 'core' && !s.classed('core') && !s.classed('aggregation') ||
664 data.className == 'aggregation' && !s.classed('core')) {
665 return;
666 }
667
668 // don't highlight if there's already a link or flow
669 // var map = linkMap[data.dpid];
670 // console.log(map);
671 // console.log(s.data()[0].dpid);
672 // console.log(map[s.data()[0].dpid]);
673 // if (map && map[s.data()[0].dpid]) {
674 // return;
675 // }
676
677 // the second highlighted switch is the target for a link or flow
678 data.target = true;
679 }
680
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700681 var node = d3.select(document.getElementById(data.dpid));
Paul Greyson421bfcd2013-03-27 22:22:09 -0700682 node.classed('highlight', true).select('circle').transition().duration(100).attr("r", widths.core);
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700683 data.highlighted = true;
684 node.moveToFront();
685 }
686
Paul Greyson8d1c6362013-03-27 13:05:24 -0700687 function mouseOutSwitch(data) {
Paul Greyson5cc35f02013-03-28 10:07:36 -0700688 d3.select(document.getElementById(data.dpid + '-label')).classed('nolabel', true);
689
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700690 if (data.mouseDown)
691 return;
692
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700693 var node = d3.select(document.getElementById(data.dpid));
Paul Greyson421bfcd2013-03-27 22:22:09 -0700694 node.classed('highlight', false).select('circle').transition().duration(100).attr("r", widths[data.className]);
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700695 data.highlighted = false;
696 data.target = false;
697 }
698
Paul Greyson8d1c6362013-03-27 13:05:24 -0700699 function mouseDownSwitch(data) {
700 mouseOverSwitch(data);
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700701 data.mouseDown = true;
Paul Greyson72f18852013-03-27 15:56:11 -0700702 d3.select('#topology').classed('linking', true);
703
Paul Greyson421bfcd2013-03-27 22:22:09 -0700704 d3.select('svg')
705 .append('svg:path')
706 .attr('id', 'linkVector')
707 .attr('d', function () {
708 var s = d3.select(document.getElementById(data.dpid));
709
710 var pt = document.querySelector('svg').createSVGPoint();
711 pt.x = s.attr('x');
712 pt.y = s.attr('y');
713 pt = pt.matrixTransform(s[0][0].getCTM());
714
715 return line([pt, pt]);
716 });
717
718
Paul Greyson72f18852013-03-27 15:56:11 -0700719 if (data.className === 'core') {
720 d3.selectAll('.edge').classed('nodrop', true);
721 }
722 if (data.className === 'edge') {
723 d3.selectAll('.core').classed('nodrop', true);
724 d3.selectAll('.aggregation').classed('nodrop', true);
725 }
726 if (data.className === 'aggregation') {
727 d3.selectAll('.edge').classed('nodrop', true);
728 d3.selectAll('.aggregation').classed('nodrop', true);
729 }
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700730 }
731
Paul Greyson8d1c6362013-03-27 13:05:24 -0700732 function mouseUpSwitch(data) {
733 if (data.mouseDown) {
734 data.mouseDown = false;
Paul Greyson72f18852013-03-27 15:56:11 -0700735 d3.select('#topology').classed('linking', false);
Paul Greyson8d1c6362013-03-27 13:05:24 -0700736 d3.event.stopPropagation();
Paul Greyson72f18852013-03-27 15:56:11 -0700737 d3.selectAll('.nodrop').classed('nodrop', false);
Paul Greyson8d1c6362013-03-27 13:05:24 -0700738 }
739 }
740
741 function doubleClickSwitch(data) {
Paul Greyson084779b2013-03-27 13:55:49 -0700742 var circle = d3.select(document.getElementById(data.dpid)).select('circle');
Paul Greyson8d1c6362013-03-27 13:05:24 -0700743 if (data.state == 'ACTIVE') {
744 var prompt = 'Deactivate ' + data.dpid + '?';
745 if (confirm(prompt)) {
746 switchDown(data);
Paul Greyson084779b2013-03-27 13:55:49 -0700747 setPending(circle);
Paul Greyson8d1c6362013-03-27 13:05:24 -0700748 }
749 } else {
750 var prompt = 'Activate ' + data.dpid + '?';
751 if (confirm(prompt)) {
752 switchUp(data);
Paul Greyson084779b2013-03-27 13:55:49 -0700753 setPending(circle);
Paul Greyson8d1c6362013-03-27 13:05:24 -0700754 }
755 }
756 }
757
Paul Greyson740bdaf2013-03-18 16:10:48 -0700758 function ringEnter(data, i) {
Paul Greyson644d92a2013-03-23 18:00:40 -0700759 if (!data.length) {
Paul Greyson740bdaf2013-03-18 16:10:48 -0700760 return;
761 }
762
Paul Greysonc17278a2013-03-23 10:17:12 -0700763 // create the nodes
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700764 var nodes = d3.select(this).selectAll("g")
Paul Greyson644d92a2013-03-23 18:00:40 -0700765 .data(data, function (data) {
766 return data.dpid;
767 })
Paul Greyson740bdaf2013-03-18 16:10:48 -0700768 .enter().append("svg:g")
Paul Greyson644d92a2013-03-23 18:00:40 -0700769 .attr("id", function (data, i) {
770 return data.dpid;
Paul Greyson23b0cd32013-03-18 23:45:48 -0700771 })
Paul Greyson644d92a2013-03-23 18:00:40 -0700772 .attr("transform", function(data, i) {
773 return "rotate(" + data.angle+ ")translate(" + data.radius * 150 + ")rotate(" + (-data.angle) + ")";
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700774 });
775
Paul Greysonc17278a2013-03-23 10:17:12 -0700776 // add the cirles representing the switches
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700777 nodes.append("svg:circle")
Paul Greyson644d92a2013-03-23 18:00:40 -0700778 .attr("transform", function(data, i) {
Paul Greysond1a22d92013-03-19 12:15:19 -0700779 var m = document.querySelector('#viewbox').getTransformToElement().inverse();
Paul Greysonf8f43172013-03-18 23:00:30 -0700780 if (data.scale) {
781 m = m.scale(data.scale);
782 }
783 return "matrix( " + m.a + " " + m.b + " " + m.c + " " + m.d + " " + m.e + " " + m.f + " )";
Paul Greyson952ccb62013-03-18 22:22:08 -0700784 })
Paul Greyson644d92a2013-03-23 18:00:40 -0700785 .attr("x", function (data) {
786 return -data.width / 2;
787 })
788 .attr("y", function (data) {
789 return -data.width / 2;
790 })
791 .attr("r", function (data) {
792 return data.width;
Paul Greyson127d7fb2013-03-25 23:39:20 -0700793 });
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700794
Paul Greysonc17278a2013-03-23 10:17:12 -0700795 // setup the mouseover behaviors
Paul Greyson8d1c6362013-03-27 13:05:24 -0700796 nodes.on('mouseover', mouseOverSwitch);
797 nodes.on('mouseout', mouseOutSwitch);
798 nodes.on('mouseup', mouseUpSwitch);
799 nodes.on('mousedown', mouseDownSwitch);
800
801 // only do switch up/down for core switches
802 if (i == 2) {
803 nodes.on('dblclick', doubleClickSwitch);
804 }
Paul Greyson740bdaf2013-03-18 16:10:48 -0700805 }
806
Paul Greysonc17278a2013-03-23 10:17:12 -0700807 // append switches
808 rings.enter().append("svg:g")
Paul Greyson740bdaf2013-03-18 16:10:48 -0700809 .attr("class", "ring")
810 .each(ringEnter);
Paul Greysonf8f43172013-03-18 23:00:30 -0700811
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700812
Paul Greysonc17278a2013-03-23 10:17:12 -0700813 function ringUpdate(data, i) {
Paul Greyson127d7fb2013-03-25 23:39:20 -0700814 var nodes = d3.select(this).selectAll("g")
Paul Greyson644d92a2013-03-23 18:00:40 -0700815 .data(data, function (data) {
816 return data.dpid;
Paul Greyson127d7fb2013-03-25 23:39:20 -0700817 });
Paul Greyson347fb742013-03-27 13:40:29 -0700818 nodes.select('circle')
819 .each(function (data) {
820 // if there's a pending state changed and then the state changes, clear the pending class
821 var circle = d3.select(this);
822 if (data.state === 'ACTIVE' && circle.classed('inactive') ||
823 data.state === 'INACTIVE' && circle.classed('active')) {
824 circle.classed('pending', false);
825 }
826 })
827 .attr('class', function (data) {
Paul Greyson8d1c6362013-03-27 13:05:24 -0700828 if (data.state === 'ACTIVE' && data.controller) {
Paul Greyson347fb742013-03-27 13:40:29 -0700829 return data.className + ' active ' + controllerColorMap[data.controller];
Paul Greysonc17278a2013-03-23 10:17:12 -0700830 } else {
Paul Greyson347fb742013-03-27 13:40:29 -0700831 return data.className + ' inactive ' + 'colorInactive';
Paul Greysonc17278a2013-03-23 10:17:12 -0700832 }
Paul Greyson127d7fb2013-03-25 23:39:20 -0700833 });
Paul Greysonc17278a2013-03-23 10:17:12 -0700834 }
835
836 // update switches
837 rings.each(ringUpdate);
838
Paul Greyson968d1b42013-03-23 16:58:41 -0700839
840 // Now setup the labels
841 // This is done separately because SVG draws in node order and we want the labels
842 // always on top
843 var labelRings = svg.selectAll('.labelRing').data(createRingsFromModel(model));
844
Paul Greyson421bfcd2013-03-27 22:22:09 -0700845 d3.select(document.body).on('mousemove', function () {
846 if (!d3.select('#topology').classed('linking')) {
847 return;
848 }
849 var linkVector = document.getElementById('linkVector');
850 if (!linkVector) {
851 return;
852 }
853 linkVector = d3.select(linkVector);
854
855 var highlighted = svg.selectAll('.highlight')[0];
856 var s1 = null, s2 = null;
857 if (highlighted.length > 1) {
858 var s1 = d3.select(highlighted[0]);
859 var s2 = d3.select(highlighted[1]);
860
861 } else if (highlighted.length > 0) {
862 var s1 = d3.select(highlighted[0]);
863 }
864 var src = s1;
865 if (s2 && !s2.data()[0].target) {
866 src = s2;
867 }
868 if (src) {
869 linkVector.attr('d', function () {
870 var srcPt = document.querySelector('svg').createSVGPoint();
871 srcPt.x = src.attr('x');
872 srcPt.y = src.attr('y');
873 srcPt = srcPt.matrixTransform(src[0][0].getCTM());
874
875 var svg = document.getElementById('topology');
876 var mouse = d3.mouse(viewbox);
877 var dstPt = document.querySelector('svg').createSVGPoint();
878 dstPt.x = mouse[0];
879 dstPt.y = mouse[1];
880 dstPt = dstPt.matrixTransform(viewbox.getCTM());
881
882 return line([srcPt, dstPt]);
883 });
884 }
885 });
886
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700887 d3.select(document.body).on('mouseup', function () {
888 function clearHighlight() {
889 svg.selectAll('circle').each(function (data) {
890 data.mouseDown = false;
Paul Greyson72f18852013-03-27 15:56:11 -0700891 d3.select('#topology').classed('linking', false);
Paul Greyson8d1c6362013-03-27 13:05:24 -0700892 mouseOutSwitch(data);
Paul Greyson421bfcd2013-03-27 22:22:09 -0700893 });
894 d3.select('#linkVector').remove();
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700895 };
896
Paul Greyson72f18852013-03-27 15:56:11 -0700897 d3.selectAll('.nodrop').classed('nodrop', false);
898
Paul Greyson084779b2013-03-27 13:55:49 -0700899 function removeLink(link) {
900 var path1 = document.getElementById(link['src-switch'] + '=>' + link['dst-switch']);
901 var path2 = document.getElementById(link['dst-switch'] + '=>' + link['src-switch']);
902
903 if (path1) {
904 setPending(d3.select(path1));
905 }
906 if (path2) {
907 setPending(d3.select(path2));
908 }
909
910 linkDown(link);
911 }
912
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700913
Paul Greyson421bfcd2013-03-27 22:22:09 -0700914 var highlighted = svg.selectAll('.highlight')[0];
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700915 if (highlighted.length == 2) {
Paul Greyson421bfcd2013-03-27 22:22:09 -0700916 var s1Data = highlighted[0].__data__;
917 var s2Data = highlighted[1].__data__;
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700918
919 var srcData, dstData;
920 if (s1Data.target) {
921 dstData = s1Data;
922 srcData = s2Data;
923 } else {
924 dstData = s2Data;
925 srcData = s1Data;
926 }
927
928 if (s1Data.className == 'edge' && s2Data.className == 'edge') {
929 var prompt = 'Create flow from ' + srcData.dpid + ' to ' + dstData.dpid + '?';
930 if (confirm(prompt)) {
Paul Greyson13f02b92013-03-28 11:29:35 -0700931 addFlow(srcData, dstData);
932
933 var flow = {
934 dataPath: {
935 srcPort: {
936 dpid: {
937 value: srcData.dpid
938 }
939 },
940 dstPort: {
941 dpid: {
942 value: dstData.dpid
943 }
944 }
945 },
Jonathan Hart3888b042013-04-04 20:04:26 -0700946 srcDpid: srcData.dpid,
947 dstDpid: dstData.dpid,
Paul Greysonaa812562013-03-28 12:43:12 -0700948 createPending: true
Paul Greyson13f02b92013-03-28 11:29:35 -0700949 };
950
951 selectFlow(flow);
952
953 setTimeout(function () {
Paul Greysonaa812562013-03-28 12:43:12 -0700954 deselectFlowIfCreatePending(flow);
Paul Greyson6f918402013-03-28 12:18:30 -0700955 }, pendingTimeout);
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700956 }
957 } else {
958 var map = linkMap[srcData.dpid];
959 if (map && map[dstData.dpid]) {
960 var prompt = 'Remove link between ' + srcData.dpid + ' and ' + dstData.dpid + '?';
961 if (confirm(prompt)) {
Paul Greyson084779b2013-03-27 13:55:49 -0700962 removeLink(map[dstData.dpid]);
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700963 }
964 } else {
Paul Greyson8d1c6362013-03-27 13:05:24 -0700965 map = linkMap[dstData.dpid];
966 if (map && map[srcData.dpid]) {
967 var prompt = 'Remove link between ' + dstData.dpid + ' and ' + srcData.dpid + '?';
968 if (confirm(prompt)) {
Paul Greyson084779b2013-03-27 13:55:49 -0700969 removeLink(map[srcData.dpid]);
Paul Greyson8d1c6362013-03-27 13:05:24 -0700970 }
971 } else {
972 var prompt = 'Create link between ' + srcData.dpid + ' and ' + dstData.dpid + '?';
973 if (confirm(prompt)) {
Paul Greyson40c8a592013-03-27 14:10:33 -0700974 var link1 = {
975 'src-switch': srcData.dpid,
Paul Greyson2913af82013-03-27 14:53:17 -0700976 'src-port': 1,
Paul Greyson40c8a592013-03-27 14:10:33 -0700977 'dst-switch': dstData.dpid,
Paul Greyson2913af82013-03-27 14:53:17 -0700978 'dst-port': 1,
Paul Greyson40c8a592013-03-27 14:10:33 -0700979 pending: true
980 };
981 pendingLinks[makeLinkKey(link1)] = link1;
982 var link2 = {
983 'src-switch': dstData.dpid,
Paul Greyson2913af82013-03-27 14:53:17 -0700984 'src-port': 1,
Paul Greyson40c8a592013-03-27 14:10:33 -0700985 'dst-switch': srcData.dpid,
Paul Greyson2913af82013-03-27 14:53:17 -0700986 'dst-port': 1,
Paul Greyson40c8a592013-03-27 14:10:33 -0700987 pending: true
988 };
989 pendingLinks[makeLinkKey(link2)] = link2;
Paul Greyson5cc35f02013-03-28 10:07:36 -0700990 updateTopology();
Paul Greyson40c8a592013-03-27 14:10:33 -0700991
Paul Greyson2913af82013-03-27 14:53:17 -0700992 linkUp(link1);
Paul Greyson40c8a592013-03-27 14:10:33 -0700993
Paul Greyson5cc35f02013-03-28 10:07:36 -0700994 // remove the pending links after 10s
Paul Greyson40c8a592013-03-27 14:10:33 -0700995 setTimeout(function () {
996 delete pendingLinks[makeLinkKey(link1)];
997 delete pendingLinks[makeLinkKey(link2)];
998
Paul Greyson5cc35f02013-03-28 10:07:36 -0700999 updateTopology();
Paul Greyson6f918402013-03-28 12:18:30 -07001000 }, pendingTimeout);
Paul Greyson8d1c6362013-03-27 13:05:24 -07001001 }
Paul Greyson4e6dc3a2013-03-27 11:37:14 -07001002 }
1003 }
1004 }
1005
1006 clearHighlight();
1007 } else {
1008 clearHighlight();
1009 }
1010
1011 });
1012
Paul Greyson9066ab02013-03-23 18:15:41 -07001013 function labelRingEnter(data) {
Paul Greyson644d92a2013-03-23 18:00:40 -07001014 if (!data.length) {
Paul Greyson968d1b42013-03-23 16:58:41 -07001015 return;
1016 }
1017
1018 // create the nodes
1019 var nodes = d3.select(this).selectAll("g")
Paul Greyson644d92a2013-03-23 18:00:40 -07001020 .data(data, function (data) {
1021 return data.dpid;
1022 })
Paul Greyson968d1b42013-03-23 16:58:41 -07001023 .enter().append("svg:g")
1024 .classed('nolabel', true)
Paul Greyson9066ab02013-03-23 18:15:41 -07001025 .attr("id", function (data) {
Paul Greyson644d92a2013-03-23 18:00:40 -07001026 return data.dpid + '-label';
Paul Greyson968d1b42013-03-23 16:58:41 -07001027 })
Paul Greyson644d92a2013-03-23 18:00:40 -07001028 .attr("transform", function(data, i) {
1029 return "rotate(" + data.angle+ ")translate(" + data.radius * 150 + ")rotate(" + (-data.angle) + ")";
Paul Greyson4e6dc3a2013-03-27 11:37:14 -07001030 })
Paul Greyson968d1b42013-03-23 16:58:41 -07001031
1032 // add the text nodes which show on mouse over
1033 nodes.append("svg:text")
Paul Greyson127d7fb2013-03-25 23:39:20 -07001034 .text(function (data) {return data.dpid;})
Paul Greyson9066ab02013-03-23 18:15:41 -07001035 .attr("x", function (data) {
1036 if (data.angle <= 90 || data.angle >= 270 && data.angle <= 360) {
1037 if (data.className == 'edge') {
Paul Greyson1eb2dd12013-03-23 18:22:00 -07001038 return - data.width*3 - 4;
Paul Greyson9066ab02013-03-23 18:15:41 -07001039 } else {
Paul Greyson1eb2dd12013-03-23 18:22:00 -07001040 return - data.width - 4;
Paul Greyson9066ab02013-03-23 18:15:41 -07001041 }
1042 } else {
1043 if (data.className == 'edge') {
1044 return data.width*3 + 4;
1045 } else {
1046 return data.width + 4;
1047 }
1048 }
1049 })
1050 .attr("y", function (data) {
1051 var y;
1052 if (data.angle <= 90 || data.angle >= 270 && data.angle <= 360) {
1053 if (data.className == 'edge') {
1054 y = data.width*3/2 + 4;
1055 } else {
1056 y = data.width/2 + 4;
1057 }
1058 } else {
1059 if (data.className == 'edge') {
1060 y = data.width*3/2 + 4;
1061 } else {
1062 y = data.width/2 + 4;
1063 }
1064 }
1065 return y - 6;
1066 })
1067 .attr("text-anchor", function (data) {
1068 if (data.angle <= 90 || data.angle >= 270 && data.angle <= 360) {
1069 return "end";
1070 } else {
1071 return "start";
1072 }
1073 })
1074 .attr("transform", function(data) {
Paul Greyson968d1b42013-03-23 16:58:41 -07001075 var m = document.querySelector('#viewbox').getTransformToElement().inverse();
1076 if (data.scale) {
1077 m = m.scale(data.scale);
1078 }
1079 return "matrix( " + m.a + " " + m.b + " " + m.c + " " + m.d + " " + m.e + " " + m.f + " )";
1080 })
1081 }
1082
1083 labelRings.enter().append("svg:g")
1084 .attr("class", "textRing")
1085 .each(labelRingEnter);
1086
Paul Greysonc17278a2013-03-23 10:17:12 -07001087 // switches should not change during operation of the ui so no
1088 // rings.exit()
1089
1090
Paul Greysond1a22d92013-03-19 12:15:19 -07001091 // DRAW THE LINKS
Paul Greysond1a22d92013-03-19 12:15:19 -07001092
Paul Greysonc17278a2013-03-23 10:17:12 -07001093 // key on link dpids since these will come/go during demo
Paul Greyson40c8a592013-03-27 14:10:33 -07001094 var links = d3.select('svg').selectAll('.link').data(links, function (d) {
Paul Greysonc17278a2013-03-23 10:17:12 -07001095 return d['src-switch']+'->'+d['dst-switch'];
1096 });
1097
1098 // add new links
Paul Greysonb367de22013-03-23 11:09:11 -07001099 links.enter().append("svg:path")
Paul Greyson56378ed2013-03-26 23:17:36 -07001100 .attr("class", "link");
1101
Paul Greyson084779b2013-03-27 13:55:49 -07001102 links.attr('id', function (d) {
Paul Greyson40c8a592013-03-27 14:10:33 -07001103 return makeLinkKey(d);
Paul Greyson084779b2013-03-27 13:55:49 -07001104 })
Paul Greyson56378ed2013-03-26 23:17:36 -07001105 .attr("d", function (d) {
Paul Greyson084779b2013-03-27 13:55:49 -07001106 var src = d3.select(document.getElementById(d['src-switch']));
1107 var dst = d3.select(document.getElementById(d['dst-switch']));
Paul Greysonc17278a2013-03-23 10:17:12 -07001108
Paul Greyson084779b2013-03-27 13:55:49 -07001109 var srcPt = document.querySelector('svg').createSVGPoint();
1110 srcPt.x = src.attr('x');
1111 srcPt.y = src.attr('y');
1112 srcPt = srcPt.matrixTransform(src[0][0].getCTM());
Paul Greysond1a22d92013-03-19 12:15:19 -07001113
Paul Greyson084779b2013-03-27 13:55:49 -07001114 var dstPt = document.querySelector('svg').createSVGPoint();
1115 dstPt.x = dst.attr('x');
Paul Greyson421bfcd2013-03-27 22:22:09 -07001116 dstPt.y = dst.attr('y');
Paul Greyson084779b2013-03-27 13:55:49 -07001117 dstPt = dstPt.matrixTransform(dst[0][0].getCTM());
Paul Greysond1a22d92013-03-19 12:15:19 -07001118
Paul Greyson084779b2013-03-27 13:55:49 -07001119 var midPt = document.querySelector('svg').createSVGPoint();
1120 midPt.x = (srcPt.x + dstPt.x)/2;
1121 midPt.y = (srcPt.y + dstPt.y)/2;
Paul Greysond1a22d92013-03-19 12:15:19 -07001122
Paul Greyson084779b2013-03-27 13:55:49 -07001123 return line([srcPt, midPt, dstPt]);
1124 })
Paul Greyson40c8a592013-03-27 14:10:33 -07001125 .attr("marker-mid", function(d) { return "url(#arrow)"; })
1126 .classed('pending', function (d) {
1127 return d.pending;
1128 });
Paul Greysonc17278a2013-03-23 10:17:12 -07001129
Paul Greyson56378ed2013-03-26 23:17:36 -07001130
Paul Greysonc17278a2013-03-23 10:17:12 -07001131 // remove old links
1132 links.exit().remove();
Paul Greysond1a22d92013-03-19 12:15:19 -07001133}
1134
Paul Greyson5cc35f02013-03-28 10:07:36 -07001135function updateControllers() {
Paul Greysond1a22d92013-03-19 12:15:19 -07001136 var controllers = d3.select('#controllerList').selectAll('.controller').data(model.controllers);
Paul Greyson3e142162013-03-19 13:56:17 -07001137 controllers.enter().append('div')
Paul Greysone262a292013-03-23 10:35:23 -07001138 .each(function (c) {
1139 controllerColorMap[c] = colors.pop();
1140 d3.select(document.body).classed(controllerColorMap[c] + '-selected', true);
1141 })
1142 .text(function (d) {
1143 return d;
Paul Greyson2913af82013-03-27 14:53:17 -07001144 })
1145 .append('div')
Paul Greyson8247c3f2013-03-28 00:24:02 -07001146 .attr('class', 'black-eye');
Paul Greysonbcd3c772013-03-21 13:16:44 -07001147
Paul Greysone262a292013-03-23 10:35:23 -07001148 controllers.attr('class', function (d) {
Paul Greysoneed36352013-03-23 11:19:11 -07001149 var color = 'colorInactive';
Paul Greysonbcd3c772013-03-21 13:16:44 -07001150 if (model.activeControllers.indexOf(d) != -1) {
1151 color = controllerColorMap[d];
Paul Greysond1a22d92013-03-19 12:15:19 -07001152 }
Paul Greysonbcd3c772013-03-21 13:16:44 -07001153 var className = 'controller ' + color;
1154 return className;
Paul Greysond1a22d92013-03-19 12:15:19 -07001155 });
Paul Greysond1a22d92013-03-19 12:15:19 -07001156
Paul Greysone262a292013-03-23 10:35:23 -07001157 // this should never be needed
1158 // controllers.exit().remove();
Paul Greysond1a22d92013-03-19 12:15:19 -07001159
Paul Greyson2913af82013-03-27 14:53:17 -07001160 controllers.on('dblclick', function (c) {
1161 if (model.activeControllers.indexOf(c) != -1) {
1162 var prompt = 'Dectivate ' + c + '?';
1163 if (confirm(prompt)) {
1164 controllerDown(c);
1165 setPending(d3.select(this));
1166 };
1167 } else {
1168 var prompt = 'Activate ' + c + '?';
1169 if (confirm(prompt)) {
1170 controllerUp(c);
1171 setPending(d3.select(this));
1172 };
1173 }
1174 });
1175
Paul Greyson8247c3f2013-03-28 00:24:02 -07001176 controllers.select('.black-eye').on('click', function (c) {
Paul Greysonc3e21a02013-03-21 13:56:05 -07001177 var allSelected = true;
1178 for (var key in controllerColorMap) {
1179 if (!d3.select(document.body).classed(controllerColorMap[key] + '-selected')) {
1180 allSelected = false;
1181 break;
1182 }
1183 }
1184 if (allSelected) {
1185 for (var key in controllerColorMap) {
1186 d3.select(document.body).classed(controllerColorMap[key] + '-selected', key == c)
1187 }
1188 } else {
1189 for (var key in controllerColorMap) {
1190 d3.select(document.body).classed(controllerColorMap[key] + '-selected', true)
1191 }
1192 }
1193
1194 // var selected = d3.select(document.body).classed(controllerColorMap[c] + '-selected');
1195 // d3.select(document.body).classed(controllerColorMap[c] + '-selected', !selected);
Paul Greysond1a22d92013-03-19 12:15:19 -07001196 });
Paul Greyson8d1c6362013-03-27 13:05:24 -07001197
1198
Paul Greyson740bdaf2013-03-18 16:10:48 -07001199}
1200
Paul Greyson2c35f572013-04-04 16:23:48 -07001201var modelString;
Paul Greyson29aa98d2013-03-28 00:09:31 -07001202function sync(svg) {
Paul Greysonbcd3c772013-03-21 13:16:44 -07001203 var d = Date.now();
Paul Greysonb48943b2013-03-19 13:27:57 -07001204 updateModel(function (newModel) {
Paul Greyson4e6dc3a2013-03-27 11:37:14 -07001205// console.log('Update time: ' + (Date.now() - d)/1000 + 's');
Paul Greyson740bdaf2013-03-18 16:10:48 -07001206
Paul Greysone5991b52013-04-04 01:34:04 -07001207 if (newModel) {
1208 var modelChanged = false;
Paul Greyson2c35f572013-04-04 16:23:48 -07001209 var newModelString = JSON.stringify(newModel);
1210 if (!modelString || newModelString != modelString) {
Paul Greysone5991b52013-04-04 01:34:04 -07001211 modelChanged = true;
1212 model = newModel;
Paul Greyson2c35f572013-04-04 16:23:48 -07001213 modelString = newModelString;
Paul Greysone5991b52013-04-04 01:34:04 -07001214 } else {
1215 // console.log('no change');
1216 }
Paul Greysonb48943b2013-03-19 13:27:57 -07001217
Paul Greysone5991b52013-04-04 01:34:04 -07001218 if (modelChanged) {
1219 updateControllers();
1220 updateSelectedFlows();
1221 updateTopology();
1222 }
Paul Greyson5cc35f02013-03-28 10:07:36 -07001223
Paul Greysone5991b52013-04-04 01:34:04 -07001224 updateHeader(newModel);
1225 }
Paul Greyson740bdaf2013-03-18 16:10:48 -07001226
1227 // do it again in 1s
1228 setTimeout(function () {
Paul Greysona36a9232013-03-22 22:41:27 -07001229 sync(svg)
Paul Greysond1a22d92013-03-19 12:15:19 -07001230 }, 1000);
Paul Greyson6f86d1e2013-03-18 14:40:39 -07001231 });
1232}
Paul Greyson740bdaf2013-03-18 16:10:48 -07001233
Paul Greyson38d8bde2013-03-22 22:07:35 -07001234svg = createTopologyView();
Paul Greyson29aa98d2013-03-28 00:09:31 -07001235updateSelectedFlows();
1236
1237d3.select('#showFlowChooser').on('click', function () {
1238 showFlowChooser();
1239});
1240
Paul Greyson72f18852013-03-27 15:56:11 -07001241
Paul Greyson38d8bde2013-03-22 22:07:35 -07001242// workaround for Chrome v25 bug
1243// if executed immediately, the view box transform logic doesn't work properly
1244// fixed in Chrome v27
1245setTimeout(function () {
1246 // workaround for another Chrome v25 bug
1247 // viewbox transform stuff doesn't work in combination with browser zoom
Paul Greysonc17278a2013-03-23 10:17:12 -07001248 // also works in Chrome v27
Paul Greyson38d8bde2013-03-22 22:07:35 -07001249 d3.select('#svg-container').style('zoom', window.document.body.clientWidth/window.document.width);
Paul Greyson29aa98d2013-03-28 00:09:31 -07001250 sync(svg);
Paul Greyson38d8bde2013-03-22 22:07:35 -07001251}, 100);