blob: b518bd2ee63e9adcd83fd62c06b55165deb88ecb [file] [log] [blame]
Masayoshi Kobayashie64d44b2014-04-23 11:40:02 -07001function convert_to_topodata(switches, links, registry){
2 var controllers={};
3 var nr_controllers=0;
4 var sws=[];
5 var ls=[];
6 var topo = new Array();
7 switches.forEach(function(item) {
8 var sw={}
9 sw.name=item.dpid;
10 sw.group=-1;
11 sws.push(sw);
12 });
13 for (var r in registry){
14 if ( ! (registry[r][0]['controllerId'] in controllers) ){
15 controllers[registry[r][0]['controllerId']] = ++nr_controllers;
16 }
17 }
18 for (var i = 0; i < sws.length; i++){
19 if (sws[i].name in registry){
20 sws[i].group=controllers[registry[sws[i].name][0]['controllerId']];
21 sws[i].controller=registry[sws[i].name][0]['controllerId'];
22 }
23 }
24 links.forEach(function(item) {
25 var link={};
26 for (var i = 0; i < sws.length; i++){
27 if(sws[i].name == item['src-switch'])
28 break;
29 }
30 link.source=i;
31 for (var i = 0; i < sws.length; i++){
32 if(sws[i].name == item['dst-switch'])
33 break;
34 }
35 link.target=i;
36 ls.push(link);
37 });
38 topo['nodes']=sws;
39 topo['links']=ls;
40 return topo;
41}
42
43var width = 1280;
44var height = 1280;
45var radius = 8;
46function gui(switch_url, link_url, registry_url){
47 var svg = d3.select("#topology")
48 .append("svg:svg")
49 .attr("width", width)
50 .attr("height", height);
51
52 var force = d3.layout.force()
53 .charge(-500)
54 .linkDistance(100)
55 .size([width, height]);
56
57 var node_drag = d3.behavior.drag()
58 .on("dragstart", dragstart)
59 .on("drag", dragmove)
60 .on("dragend", dragend);
61
62 var color = d3.scale.category20();
63 var topodata;
64 var nodes = force.nodes();
65 var links = force.links();
66
67 d3.json(switch_url, function(error, rest_switches) {
68 d3.json(link_url, function(error, rest_links) {
69 d3.json(registry_url, function(error, rest_registry) {
70 topodata = convert_to_topodata(rest_switches, rest_links, rest_registry);
71 init(topodata, nodes, links);
72 path = svg.append("svg:g").selectAll("path").data(links);
73 circle = svg.append("svg:g").selectAll("circle").data(nodes);
74 text = svg.append("svg:g").selectAll("text").data(nodes);
75 draw();
76 });
77 });
78 });
79
80 setInterval(function(){
81 d3.json(switch_url, function(error, rest_switches) {
82 d3.json(link_url, function(error, rest_links) {
83 d3.json(registry_url, function(error, rest_registry) {
84 topodata = convert_to_topodata(rest_switches, rest_links, rest_registry);
85 var changed = update(topodata, nodes, links);
86 path = svg.selectAll("path").data(links)
87 circle = svg.selectAll("circle").data(nodes);
88 text = svg.selectAll("text").data(nodes);
89 if ( changed ){
90 draw();
91 }
92 });
93 });
94 });
95 }, 3000);
96
97 function draw(){
98 force.stop();
99 svg.append("svg:text")
100 .attr("x", 50)
101 .attr("y", 20)
102 .text(function(){return "Switch: " + force.nodes().length + " (Active: " + nr_active_sw() + ")/ Link: " + force.links().length});
103
104 path.enter().append("svg:path")
105 .attr("class", function(d) { return "link"; })
106 .attr("marker-end", function(d) {
107 if(d.type == 1){
108 return "url(#TriangleRed)";
109 } else {
110 return "url(#Triangle)";
111 }
112 });
113
114 circle.enter().append("svg:circle")
115 .attr("r", function(d) {
116 if (d.group == 1000){
117 return radius;
118 }else{
119 return radius;
120 }
121 })
122 .call(node_drag);
123
124 text.enter().append("svg:text")
125 .attr("x", radius)
126 .attr("y", ".31em")
127 .text(function(d) {
128 l=d.name.split(":").length
129 return d.name.split(":")[l-2] + ":" + d.name.split(":")[l-1]
130 });
131
132 circle.append("title")
133 .text(function(d) { return d.name; });
134
135 circle.attr("fill", function(d) {
136 if (d.group == 1){
137 return "red"
138 }else if (d.group == 2){
139 return "blue"
140 }else if (d.group == 3){
141 return "green"
142 }else if (d.group == 4){
143 return "orange"
144 }else if (d.group == 5){
145 return "cyan"
146 }else if (d.group == 6){
147 return "magenta"
148 }else if (d.group == 7){
149 return "yellow"
150 }else if (d.group == 8){
151 return "purple"
152 }else{
153 return "gray"
154 }
155 });
156
157 circle.on('mouseover', function(d){
158 var nodeSelection = d3.select(this).style({opacity:'0.8'});
159 nodeSelection.select("text").style({opacity:'1.0'});
160 });
161
162
163 path.attr("stroke", function(d) {
164 if(d.type == 1){
165 return "red"
166 } else {
167 return "black"
168 }
169 }).attr("stroke-width", function(d) {
170 if(d.type == 1){
171 return "2px";
172 } else {
173 return "1.5px";
174 }
175 }).attr("marker-end", function(d) {
176 if(d.type == 1){
177 return "url(#TriangleRed)";
178 } else {
179 return "url(#Triangle)";
180 }
181 });
182 path.exit().remove();
183 circle.exit().remove();
184 text.exit().remove();
185 force.on("tick", tick);
186 force.start();
187 }
188 function nr_active_sw(){
189 var n=0;
190 var nodes = force.nodes();
191 for(var i=0;i<nodes.length;i++){
192 if(nodes[i].group!=0)
193 n++;
194 };
195 return n;
196 }
197 function dragstart(d, i) {
198 force.stop() // stops the force auto positioning before you start dragging
199 }
200 function dragmove(d, i) {
201 d.px += d3.event.dx;
202 d.py += d3.event.dy;
203 d.x += d3.event.dx;
204 d.y += d3.event.dy;
205 tick(); // this is the key to make it work together with updating both px,py,x,y on d !
206 }
207
208 function dragend(d, i) {
209 d.fixed = true; // of course set the node to fixed so the force doesn't include the node in its auto positioning stuff
210 tick();
211 force.resume();
212 }
213 function tick() {
214 path.attr("d", function(d) {
215 var dx = d.target.x - d.source.x,
216 dy = d.target.y - d.source.y,
217 dr = 1/d.linknum; //linknum is defined above
218 dr = 0; // 0 for direct line
219 return "M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 0,1 " + d.target.x + "," + d.target.y;
220 });
221 path.attr("stroke", function(d) {
222 if(d.type == 1){
223 return "red"
224 } else {
225 return "black"
226 }
227 }).attr("stroke-width", function(d) {
228 if(d.type == 1){
229 return "3px";
230 } else {
231 return "1.5px";
232 }
233 }).attr("marker-end", function(d) {
234 if(d.type == 1){
235 return "url(#TriangleRed)";
236 } else {
237 return "url(#Triangle)";
238 }
239 });
240// circle.attr("cx", function(d) { return d.x; }).attr("cy", function(d) { return d.y; });
241 circle.attr("transform", function(d) {
242 x = Math.max(radius, Math.min(width - radius, d.x));
243 y = Math.max(radius, Math.min(height - radius, d.y));
244// return "translate(" + d.x + "," + d.y + ")";
245 return "translate(" + x + "," + y + ")";
246 })
247 circle.attr("fill", function(d) {
248 if (d.group == 1){
249 return "red"
250 }else if (d.group == 2){
251 return "blue"
252 }else if (d.group == 3){
253 return "green"
254 }else if (d.group == 4){
255 return "orange"
256 }else if (d.group == 5){
257 return "cyan"
258 }else if (d.group == 6){
259 return "magenta"
260 }else if (d.group == 7){
261 return "yellow"
262 }else if (d.group == 8){
263 return "purple"
264 }else{
265 return "gray"
266 }
267 });
268// text.attr("x", function(d) { return d.x; }).attr("y", function(d) { return d.y; });
269 text.attr("transform", function(d) {
270 return "translate(" + d.x + "," + d.y + ")";
271 });
272 }
273
274}
275
276
277function init(topodata, nodes, links){
278 topodata.nodes.forEach(function(item) {
279 nodes.push(item);
280 });
281 topodata.links.forEach(function(item) {
282 links.push(item);
283 });
284 links.sort(compare_link);
285 // distinguish links that have the same src & dst node by 'linknum'
286 for (var i=1; i < links.length; i++) {
287 if (links[i].source == links[i-1].source &&
288 links[i].target == links[i-1].target) {
289 links[i].linknum = links[i-1].linknum + 1;
290 } else {
291 links[i].linknum = 1;
292 };
293 };
294}
295
296function compare_link (a, b){
297 if (a.source > b.source)
298 return 1;
299 else if (a.source < b.source)
300 return -1;
301 else {
302 if (a.target > b.target)
303 return 1;
304 else if (a.target < b.target)
305 return -1;
306 else
307 return 0;
308 }
309}
310
311/* Return nodes that is not in the current list of nodes */
312Array.prototype.node_diff = function(arr) {
313 return this.filter(function(i) {
314 for (var j = 0; j < arr.length ; j++) {
315 if (arr[j].name === i.name)
316 return false;
317 }
318 return true;
319 });
320};
321
322/* Return removed links */
323function gone_links (topo_json, links){
324 gone = []
325 for (var i = 0; i < links.length ; i ++){
326 var found = 0;
327 for (var j = 0; j < topo_json.links.length ; j ++){
328 if (links[i].source.name == topo_json.nodes[topo_json.links[j].source].name &&
329 links[i].target.name == topo_json.nodes[topo_json.links[j].target].name ){
330 found = 1;
331 break;
332 }
333 }
334 if ( found == 0 ){
335 gone.push(links[i]);
336 }
337 }
338 return gone;
339}
340
341/* Return added links */
342function added_links (topo_json, links) {
343 added = [];
344 for (var j = 0; j < topo_json.links.length ; j ++){
345 var found = 0;
346 for (var i = 0; i < links.length ; i ++){
347 if (links[i].source.name == topo_json.nodes[topo_json.links[j].source].name &&
348 links[i].target.name == topo_json.nodes[topo_json.links[j].target].name ){
349 found = 1;
350 break;
351 }
352 }
353 if ( found == 0 ){
354 added.push(topo_json.links[j]);
355 }
356 }
357 return added;
358}
359
360/* check if toplogy has changed and update node[] and link[] accordingly */
361function update(json, nodes, links){
362 var changed = false;
363 var n_adds = json.nodes.node_diff(nodes);
364 var n_rems = nodes.node_diff(json.nodes);
365 for (var i = 0; i < n_adds.length; i++) {
366 nodes.push(n_adds[i]);
367 changed = true;
368 }
369 for (var i = 0; i < n_rems.length; i++) {
370 for (var j = 0; j < nodes.length; j++) {
371 if ( nodes[j].name == n_rems[i].name ){
372 nodes.splice(j,1);
373 changed = true;
374 break;
375 }
376 }
377 }
378 var l_adds = added_links(json, links);
379 var l_rems = gone_links(json, links);
380 for (var i = 0; i < l_rems.length ; i++) {
381 for (var j = 0; j < links.length; j++) {
382 if (links[j].source.name == l_rems[i].source.name &&
383 links[j].target.name == l_rems[i].target.name) {
384 links.splice(j,1);
385 changed = true;
386 break;
387 }
388 }
389 }
390 // Sorce/target of an element of l_adds[] are corresponding to the index of json.node[]
391 // which is different from the index of node[] (new nodes are always added to the last)
392 // So update soure/target node indexes of l_add[] need to be fixed to point to the proper
393 // node in node[];
394 for (var i = 0; i < l_adds.length; i++) {
395 for (var j = 0; j < nodes.length; j++) {
396 if ( json.nodes[l_adds[i].source].name == nodes[j].name ){
397 l_adds[i].source = j;
398 break;
399 }
400 }
401 for (var j = 0; j < nodes.length; j++) {
402 if ( json.nodes[l_adds[i].target].name == nodes[j].name ){
403 l_adds[i].target = j;
404 break;
405 }
406 }
407 links.push(l_adds[i]);
408 changed = true;
409 }
410
411 // Update "group" attribute of nodes
412 for (var i = 0; i < nodes.length; i++) {
413 for (var j = 0; j < json.nodes.length; j++) {
414 if ( nodes[i].name == json.nodes[j].name ){
415 if (nodes[i].group != json.nodes[j].group){
416 nodes[i].group = json.nodes[j].group;
417 changed = true;
418 }
419 }
420 }
421 }
422 for (var i = 0; i < links.length; i++) {
423 for (var j = 0; j < json.links.length; j++) {
424 if (links[i].target.name == json.nodes[json.links[j].target].name &&
425 links[i].source.name == json.nodes[json.links[j].source].name ){
426 if (links[i].type != json.links[j].type){
427 links[i].type = json.links[j].type;
428 changed = true;
429 }
430 }
431 }
432 }
433 return changed
434}
435