blob: 256162d9140caa36da0365309ba78adf8751080f [file] [log] [blame]
Paul Greysonc090d142013-04-09 16:59:03 -07001(function () {
2
Paul Greyson981e8c22013-04-09 17:43:59 -07003var projection = d3.geo.mercator()
Paul Greyson4e348b92013-04-09 21:02:06 -07004 .center([82, 46])
Paul Greyson5f20cae2013-04-10 09:54:30 -07005 .scale(10000)
Paul Greyson981e8c22013-04-09 17:43:59 -07006 .rotate([-180,0]);
Paul Greysonc090d142013-04-09 16:59:03 -07007
Paul Greyson15e5da22013-04-10 00:16:27 -07008var switchXML;
9
Paul Greyson981e8c22013-04-09 17:43:59 -070010function createMap(svg, cb) {
Paul Greysonc090d142013-04-09 16:59:03 -070011 topology = svg.append('svg:svg').attr('id', 'viewBox').attr('viewBox', '0 0 1000 1000').
12 attr('id', 'viewbox');
13
14 var map = topology.append("g").attr('id', 'map');
15
Paul Greysonc090d142013-04-09 16:59:03 -070016 var path = d3.geo.path().projection(projection);
17
18 d3.json('data/world.json', function(error, topology) {
19 map.selectAll('path')
20 .data(topojson.object(topology, topology.objects.world).geometries)
21 .enter()
22 .append('path')
23 .attr('d', path)
24
Paul Greyson15e5da22013-04-10 00:16:27 -070025 d3.xml('assets/switch.svg', function (xml) {
26 switchXML = document.importNode(xml.documentElement, true);;
27 cb();
28 });
Paul Greysonc090d142013-04-09 16:59:03 -070029 });
Paul Greyson981e8c22013-04-09 17:43:59 -070030}
Paul Greysonc090d142013-04-09 16:59:03 -070031
Paul Greyson4e348b92013-04-09 21:02:06 -070032/***************************************************************************************************
Paul Greysonc090d142013-04-09 16:59:03 -070033
Paul Greyson4e348b92013-04-09 21:02:06 -070034***************************************************************************************************/
35var switchMap;
36function makeSwitchMap() {
37 switchMap = {};
38 model.coreSwitches.forEach(function (s) {
39 switchMap[s.dpid] = s;
40 });
41 model.aggregationSwitches.forEach(function (s) {
42 switchMap[s.dpid] = s;
43 });
44 model.edgeSwitches.forEach(function (s) {
45 switchMap[s.dpid] = s;
46 });
47}
48
49/***************************************************************************************************
50create a map from edge->aggregation and aggreation->core switches
51***************************************************************************************************/
52var switchAssociations;
53function makeAssociations() {
54 switchAssociations = {};
55
56 var key;
57 for (key in model.configuration.association) {
58 var aggregationSwitches = model.configuration.association[key];
59 aggregationSwitches.forEach(function (s) {
60 switchAssociations[s] = key;
61 });
62 }
63}
64
65/***************************************************************************************************
66get the upstream switch. this only makes sense for aggregation and edge switches
67***************************************************************************************************/
68function getUpstream(dpid, className) {
69 if (className === 'aggregation') {
70 return switchAssociations[dpid];
71 } else if (className === 'edge') {
72 var aggregationDpid = dpid.split(':');
73 aggregationDpid[7] = '01'; // the last component of the agg switch is always '01'
74 return aggregationDpid.join(':');
75 }
76}
77
78
79
80/*****************a**********************************************************************************
81create a map to hold the fanout information for the switches
82***************************************************************************************************/
83var fanouts;
84function makeFanouts() {
85 fanouts = {};
86 model.coreSwitches.forEach(function (s) {
87 fanouts[s.dpid] = model.configuration.geo[s.dpid];
88 fanouts[s.dpid].count = 0;
89 });
90
91 model.aggregationSwitches.forEach(function (s) {
92 fanouts[s.dpid] = {count: 0};
93 var upstreamFanout = fanouts[getUpstream(s.dpid, 'aggregation')];
94 upstreamFanout.count += 1;
95 });
96
97 model.edgeSwitches.forEach(function (s) {
98 fanouts[s.dpid] = {};
99 var upstreamFanout = fanouts[getUpstream(s.dpid, 'edge')];
100 upstreamFanout.count += 1;
101 });
102}
103
104
105var projection;
106var switchLayer;
107var labelsLayer;
108var linksLayer;
Paul Greyson981e8c22013-04-09 17:43:59 -0700109createTopologyView = function (cb) {
110 var svg = createRootSVG();
Paul Greysonc090d142013-04-09 16:59:03 -0700111
Paul Greyson4e348b92013-04-09 21:02:06 -0700112 createMap(svg, function () {
113 switchLayer = topology.append('g');
114 labelsLayer = topology.append('g');
115 linksLayer = topology.append('g');
Paul Greysonbbd708b2013-04-09 22:37:31 -0700116 flowLayer = topology.append('g');
117
Paul Greyson4e348b92013-04-09 21:02:06 -0700118
119 cb();
120 });
Paul Greyson981e8c22013-04-09 17:43:59 -0700121}
Paul Greysonc090d142013-04-09 16:59:03 -0700122
Paul Greyson4a73a8b2013-04-10 11:47:25 -0700123function drawCoreFlowCounts() {
124 var links = {};
125 model.links.forEach(function (l) {
126 links[makeLinkKey(l)] = l;
127 });
128
129 var flowCounts = [];
130 countCoreLinkFlows().forEach(function (count) {
131 var l = links[count.key];
132 if (l) {
133 var src = d3.select(document.getElementById(l['src-switch']));
134 var dst = d3.select(document.getElementById(l['dst-switch']));
135
136 if (!src.empty() && !dst.empty()) {
137 var x1 = parseFloat(src.attr('x'));
138 var x2 = parseFloat(dst.attr('x'));
139 var y1 = parseFloat(src.attr('y'));
140 var y2 = parseFloat(dst.attr('y'));
141
142 var slope = (y2 - y1)/(x2 - x1);
143
144 var offset = 15;
145 var xOffset = offset;
146 var yOffset = slope*offset;
147
148 var d = Math.sqrt(xOffset*xOffset + yOffset*yOffset);
149 var scaler = offset/d;
150
151 count.pt = {
152 x: x1 + (x2 - x1)/2 + xOffset*scaler,
153 y: y1 + (y2 - y1)/2 + yOffset*scaler
154 }
155 }
156 flowCounts.push(count);
157 }
158 });
159
160
161 var counts = linksLayer.selectAll('.flowCount').data(flowCounts, function (d) {
162 return d.key;
163 });
164
165 counts.enter().append('svg:text')
166 .attr('class', 'flowCount')
167 .attr('x', function (d) {
168 return d.pt.x;
169 })
170 .attr('y', function (d) {
171 return d.pt.y;
172 });
173
174 counts.text(function (d) {
175 return d.value;
176 });
177
178 counts.exit().remove();
179}
180
Paul Greyson45aceb22013-04-09 22:17:03 -0700181function drawLinkLines() {
Paul Greyson4e348b92013-04-09 21:02:06 -0700182
183 // key on link dpids since these will come/go during demo
184 var linkLines = linksLayer.selectAll('.link').data(links, function (d) {
185 return d['src-switch']+'->'+d['dst-switch'];
186 });
187
188 // add new links
189 linkLines.enter().append("svg:path").attr("class", "link");
190
191 linkLines.attr('id', function (d) {
192 return makeLinkKey(d);
193 }).attr("d", function (d) {
194 var src = d3.select(document.getElementById(d['src-switch']));
195 var dst = d3.select(document.getElementById(d['dst-switch']));
196
197 if (src.empty() || dst.empty()) {
198 return "M0,0";
199 }
200
201 var srcPt = document.querySelector('svg').createSVGPoint();
202 srcPt.x = src.attr('x');
203 srcPt.y = src.attr('y');
204
205 var dstPt = document.querySelector('svg').createSVGPoint();
206 dstPt.x = dst.attr('x');
207 dstPt.y = dst.attr('y');
208
209 var midPt = document.querySelector('svg').createSVGPoint();
210 midPt.x = (srcPt.x + dstPt.x)/2;
211 midPt.y = (srcPt.y + dstPt.y)/2;
212
213 return line([srcPt, midPt, dstPt]);
214 })
215 .attr("marker-mid", function(d) { return "url(#arrow)"; })
216 .classed('pending', function (d) {
217 return d.pending;
218 });
219
220 // remove old links
221 linkLines.exit().remove();
222}
223
Paul Greyson4a73a8b2013-04-10 11:47:25 -0700224
Paul Greyson4e348b92013-04-09 21:02:06 -0700225var fanOutAngles = {
Paul Greyson5f20cae2013-04-10 09:54:30 -0700226 aggregation: 100,
227 edge: 5
228}
229
230var fanOutDistances = {
231 aggregation: 60,
232 edge: 140
Paul Greyson4e348b92013-04-09 21:02:06 -0700233}
234
235function makeSwitchesModel(switches, className) {
Paul Greyson981e8c22013-04-09 17:43:59 -0700236 var switchesModel = [];
237 switches.forEach(function (s) {
Paul Greyson4e348b92013-04-09 21:02:06 -0700238 var geo = model.configuration.geo[s.dpid];
239
240 var pos, label;
241 if (geo) {
242 pos = projection([geo.lng, geo.lat]);
243 label = geo.label;
244 } else {
245 var upstream = getUpstream(s.dpid, className);
246 if (upstream) {
247 var upstreamGeo = fanouts[upstream];
248 pos = projection([upstreamGeo.lng, upstreamGeo.lat]);
249
250 var fanOutAngle = upstreamGeo.fanOutAngle;
251 fanOutAngle -= (upstreamGeo.count - 1) * fanOutAngles[className]/2;
252
253 var angle = toRadians(fanOutAngle);
Paul Greyson5f20cae2013-04-10 09:54:30 -0700254 var xOff = Math.sin(angle) * fanOutDistances[className];
255 var yOff = Math.cos(angle) * fanOutDistances[className];
Paul Greyson4e348b92013-04-09 21:02:06 -0700256
257 pos = [pos[0] + xOff, pos[1] + yOff];
258
259 var fakeGeo = projection.invert(pos);
260
261 var fanout = fanouts[s.dpid];
262 fanout.fanOutAngle = fanOutAngle;
263 fanout.lng = fakeGeo[0];
264 fanout.lat = fakeGeo[1];
265
266 upstreamGeo.fanOutAngle += fanOutAngles[className];
267
268 } else {
269 pos = projection([-98, 39]);
270 }
271 }
272
Paul Greyson981e8c22013-04-09 17:43:59 -0700273 switchesModel.push({
274 dpid: s.dpid,
275 state: s.state,
Paul Greyson4e348b92013-04-09 21:02:06 -0700276 className: className,
Paul Greyson981e8c22013-04-09 17:43:59 -0700277 controller: s.controller,
Paul Greyson4e348b92013-04-09 21:02:06 -0700278 label: label,
279 x: pos[0],
280 y: pos[1]
Paul Greyson981e8c22013-04-09 17:43:59 -0700281 });
282 });
283
284 return switchesModel;
Paul Greysonc090d142013-04-09 16:59:03 -0700285}
286
Paul Greyson4e348b92013-04-09 21:02:06 -0700287function switchEnter(d) {
288 var g = d3.select(this);
Paul Greyson15e5da22013-04-10 00:16:27 -0700289 var width;
Paul Greysonc090d142013-04-09 16:59:03 -0700290
Paul Greyson15e5da22013-04-10 00:16:27 -0700291 // attempt to draw an svg switch
292 if (false && d.className == 'core') {
293 width = 30;
294 g.select(function () {
295 return this.appendChild(switchXML.cloneNode(true));
296 })
297 .classed(d.className, true)
298 .attr('x', d.x - 30)
299 .attr('y', d.y - 30);
300
301 } else {
302 width = widths[d.className];
303 g.append('svg:circle').attr('r', width)
304 .classed(d.className, true)
305 .attr('cx', d.x)
306 .attr('cy', d.y);
307 }
308
Paul Greyson981e8c22013-04-09 17:43:59 -0700309
Paul Greyson4e348b92013-04-09 21:02:06 -0700310 if (d.label) {
311 g.append('svg:text')
Paul Greyson45aceb22013-04-09 22:17:03 -0700312 .classed('label', true)
Paul Greyson4e348b92013-04-09 21:02:06 -0700313 .text(d.label)
Paul Greyson4a73a8b2013-04-10 11:47:25 -0700314 .attr("text-anchor", function (d) {
315 return d.x > 500 ? "end" : "start";
316 })
317 .attr('x', function (d) {
318 return d.x > 500 ? d.x - width*.8 : d.x + width*.8;
319 })
320 .attr('y', d.y - width*.8);
Paul Greyson981e8c22013-04-09 17:43:59 -0700321 }
Paul Greyson4e348b92013-04-09 21:02:06 -0700322}
Paul Greyson981e8c22013-04-09 17:43:59 -0700323
Paul Greyson45aceb22013-04-09 22:17:03 -0700324function labelsEnter(switches) {
325 return labelsLayer.selectAll('g').data(switches, function (d) {
326 return d.dpid;
327 }).enter().append('svg:g')
328 .classed('nolabel', true)
329 .attr("id", function (data) {
330 return data.dpid + '-label';
331 })
332 .append("svg:text")
333 .text(function (data) {return data.dpid;})
334 .attr('x', function (d) {
335 return d.x;
336 })
337 .attr('y', function (d) {
338 return d.y;
339 })
340 .attr("text-anchor", function (d) {
341 return d.x > 500 ? "end" : "start";
342 })
343
344}
345
Paul Greyson4e348b92013-04-09 21:02:06 -0700346function switchesEnter(switches) {
Paul Greyson5e976662013-04-12 08:52:04 -0700347 switches.enter()
Paul Greyson4e348b92013-04-09 21:02:06 -0700348 .append('svg:g')
349 .attr("id", function (d) {
350 return d.dpid;
351 })
352 .attr('x', function (d) {
353 return d.x;
354 })
355 .attr('y', function (d) {
356 return d.y;
357 })
358 .each(switchEnter);
359}
360
Paul Greyson981e8c22013-04-09 17:43:59 -0700361
Paul Greyson4e348b92013-04-09 21:02:06 -0700362function switchesUpdate(switches) {
Paul Greyson5e976662013-04-12 08:52:04 -0700363 switches.each(function (d) {
Paul Greyson981e8c22013-04-09 17:43:59 -0700364 // if there's a pending state changed and then the state changes, clear the pending class
365 var circle = d3.select(this);
Paul Greyson4e348b92013-04-09 21:02:06 -0700366 if (d.state === 'ACTIVE' && circle.classed('inactive') ||
367 d.state === 'INACTIVE' && circle.classed('active')) {
Paul Greyson981e8c22013-04-09 17:43:59 -0700368 circle.classed('pending', false);
369 }
370 })
Paul Greyson4e348b92013-04-09 21:02:06 -0700371 .attr('class', function (d) {
372 if (d.state === 'ACTIVE' && d.controller) {
Paul Greysonb9e879e2013-04-09 21:16:16 -0700373 return 'active ' + controllerColorMap[d.controller];
Paul Greyson981e8c22013-04-09 17:43:59 -0700374 } else {
Paul Greysonb9e879e2013-04-09 21:16:16 -0700375 return 'inactive ' + 'colorInactive';
Paul Greyson981e8c22013-04-09 17:43:59 -0700376 }
377 });
Paul Greyson4e348b92013-04-09 21:02:06 -0700378}
Paul Greyson981e8c22013-04-09 17:43:59 -0700379
Paul Greyson4e348b92013-04-09 21:02:06 -0700380drawTopology = function () {
381
382 makeSwitchMap();
383 makeAssociations();
384 makeFanouts();
385
386 var coreSwitches = makeSwitchesModel(model.coreSwitches, 'core');
387 var aggregationSwitches = makeSwitchesModel(model.aggregationSwitches, 'aggregation');
388 var edgeSwitches = makeSwitchesModel(model.edgeSwitches, 'edge');
389
Paul Greyson5e976662013-04-12 08:52:04 -0700390 var switches = switchLayer.selectAll('g')
391 .data(coreSwitches.concat(aggregationSwitches).concat(edgeSwitches), function (d) {
392 return d.dpid;
393 });
Paul Greyson14919c42013-04-10 17:50:36 -0700394 switchesEnter(switches)
395 switchesUpdate(switches);
Paul Greyson4e348b92013-04-09 21:02:06 -0700396
Paul Greyson45aceb22013-04-09 22:17:03 -0700397 drawLinkLines();
Paul Greyson4e348b92013-04-09 21:02:06 -0700398
Paul Greyson4a73a8b2013-04-10 11:47:25 -0700399 drawCoreFlowCounts();
400
Paul Greyson45aceb22013-04-09 22:17:03 -0700401 labelsEnter(switches);
Paul Greysonc090d142013-04-09 16:59:03 -0700402}
403
404})();