blob: dcd7f4e18c28b237afda57632ce02d3f70d51616 [file] [log] [blame]
Matteo Scandolo9fe01af2016-02-09 16:01:49 -08001(function () {
2 'use strict';
3
4 const shapes = {
5 cloud: ' M 79.72 49.60 C 86.00 37.29 98.57 29.01 111.96 26.42 C 124.27 24.11 137.53 26.15 148.18 32.90 C 158.08 38.78 165.39 48.87 167.65 60.20 C 176.20 57.90 185.14 56.01 194.00 57.73 C 206.08 59.59 217.92 66.01 224.37 76.66 C 227.51 81.54 228.85 87.33 229.23 93.06 C 237.59 93.33 246.22 95.10 253.04 100.19 C 256.69 103.13 259.87 107.67 258.91 112.59 C 257.95 118.43 252.78 122.38 247.78 124.82 C 235.27 130.43 220.23 130.09 207.98 123.93 C 199.33 127.88 189.76 129.43 180.30 128.57 C 173.70 139.92 161.70 147.65 148.86 149.93 C 133.10 153.26 116.06 148.15 104.42 137.08 C 92.98 143.04 78.96 143.87 66.97 139.04 C 57.75 135.41 49.70 128.00 46.60 118.43 C 43.87 109.95 45.81 100.29 51.30 93.32 C 57.38 85.18 67.10 80.44 76.99 78.89 C 74.38 69.20 74.87 58.52 79.72 49.60 Z'
6 }
7
Matteo Scandolo170d3be2016-02-11 08:58:04 -08008 var computeNodeId = 0;
9 var instanceId = 0;
10
Matteo Scandolo04564952016-02-24 11:22:48 -080011 angular.module('xos.diagnostic')
Matteo Scandolo26d17e12016-02-23 13:47:14 -080012 .service('NodeDrawer', function(d3, serviceTopologyConfig, RackHelper, lodash){
Matteo Scandolo51031482016-02-17 13:54:11 -080013
14 var _this = this;
15
Matteo Scandolo9fe01af2016-02-09 16:01:49 -080016 this.addNetworks = (nodes) => {
17 nodes.append('path')
18 .attr({
19 d: shapes.cloud,
20 transform: 'translate(-63, -52), scale(0.5)',
21 class: 'cloud'
22 });
23
24 nodes.append('text')
25 .attr({
Matteo Scandolo19acf7c2016-03-07 16:07:13 -080026 'text-anchor': 'middle',
27 y: -5,
28 x: 5,
Matteo Scandolo9fe01af2016-02-09 16:01:49 -080029 })
30 .text(d => d.name)
Matteo Scandolo45fba732016-02-22 14:53:44 -080031
Matteo Scandolo19acf7c2016-03-07 16:07:13 -080032 nodes.append('text')
33 .attr({
34 'text-anchor': 'middle',
35 y: 8,
36 x: 5,
37 class: 'small'
38 })
39 .text(d => d.subtitle)
40
Matteo Scandolo45fba732016-02-22 14:53:44 -080041 nodes.each(function(n){
42 let currentNode = d3.select(this);
43 // cicle trouch node to add Tags and Public IP
44 if(n.name === 'LAN' && angular.isDefined(n.subscriberTag)){
45 currentNode.append('text')
46 .attr({
47 'text-anchor': 'middle',
48 y: 40
49 })
50 .text(() => `C-Tag: ${n.subscriberTag.cTag}`);
51
52 currentNode.append('text')
53 .attr({
54 'text-anchor': 'middle',
55 y: 60
56 })
57 .text(() => `S-Tag: ${n.subscriberTag.sTag}`);
58 }
Matteo Scandolo012dddb2016-02-22 16:53:22 -080059
60 if(n.name === 'WAN' && angular.isDefined(n.subscriberIP)){
61 currentNode.append('text')
62 .attr({
63 'text-anchor': 'middle',
64 y: 40
65 })
66 .text(() => `Public IP: ${n.subscriberIP}`);
67 }
Matteo Scandolo45fba732016-02-22 14:53:44 -080068 });
Matteo Scandolo9fe01af2016-02-09 16:01:49 -080069 }
70
71 this.addRack = (nodes) => {
Matteo Scandolo170d3be2016-02-11 08:58:04 -080072
Matteo Scandolo35d53c82016-02-16 14:44:51 -080073 // loop because of D3
74 // rack will be only one
75 nodes.each(d => {
76 let [w, h] = RackHelper.getRackSize(d.computeNodes);
Matteo Scandoload5b2282016-02-16 11:50:51 -080077
Matteo Scandolo26d17e12016-02-23 13:47:14 -080078 // TODO update instead of delete and redraw
79 nodes.select('g').remove();
80
Matteo Scandoload5b2282016-02-16 11:50:51 -080081 let rack = nodes
Matteo Scandolo35d53c82016-02-16 14:44:51 -080082 .append('g');
83
84 rack
85 .attr({
86 transform: `translate(0,0)`
87 })
88 .transition()
89 .duration(serviceTopologyConfig.duration)
90 .attr({
Matteo Scandolo26d17e12016-02-23 13:47:14 -080091 transform: () => `translate(${- (w / 2)}, ${- (h / 2)})`
Matteo Scandoload5b2282016-02-16 11:50:51 -080092 });
93
94 rack
95 .append('rect')
96 .attr({
Matteo Scandolo35d53c82016-02-16 14:44:51 -080097 width: 0,
98 height: 0
99 })
100 .transition()
101 .duration(serviceTopologyConfig.duration)
102 .attr({
Matteo Scandoload5b2282016-02-16 11:50:51 -0800103 width: w,
104 height: h
105 });
106
Matteo Scandolo35d53c82016-02-16 14:44:51 -0800107 rack.append('text')
Matteo Scandoload5b2282016-02-16 11:50:51 -0800108 .attr({
109 'text-anchor': 'middle',
Matteo Scandolo35d53c82016-02-16 14:44:51 -0800110 y: - 10,
111 x: w / 2,
112 opacity: 0
Matteo Scandoload5b2282016-02-16 11:50:51 -0800113 })
Matteo Scandolo35d53c82016-02-16 14:44:51 -0800114 .text(d => d.name)
115 .transition()
116 .duration(serviceTopologyConfig.duration)
117 .attr({
118 opacity: 1
119 })
Matteo Scandoload5b2282016-02-16 11:50:51 -0800120
Matteo Scandolo35d53c82016-02-16 14:44:51 -0800121 this.drawComputeNodes(rack, d.computeNodes);
122
Matteo Scandolo170d3be2016-02-11 08:58:04 -0800123 });
Matteo Scandolo35d53c82016-02-16 14:44:51 -0800124
Matteo Scandolo170d3be2016-02-11 08:58:04 -0800125 };
126
127 this.drawComputeNodes = (container, nodes) => {
Matteo Scandoload5b2282016-02-16 11:50:51 -0800128
Matteo Scandolo170d3be2016-02-11 08:58:04 -0800129 let elements = container.selectAll('.compute-nodes')
130 .data(nodes, d => {
131 if(!angular.isString(d.d3Id)){
132 d.d3Id = `compute-node-${++computeNodeId}`;
133 }
134 return d.d3Id;
135 });
136
Matteo Scandolo35d53c82016-02-16 14:44:51 -0800137 let {width, height} = container.node().getBoundingClientRect();
138
139 var nodeContainer = elements.enter().append('g');
140
141 nodeContainer
Matteo Scandolo170d3be2016-02-11 08:58:04 -0800142 .attr({
Matteo Scandolo35d53c82016-02-16 14:44:51 -0800143 transform: `translate(${width / 2}, ${ height / 2})`,
Matteo Scandoload5b2282016-02-16 11:50:51 -0800144 class: 'compute-node',
Matteo Scandolo35d53c82016-02-16 14:44:51 -0800145 })
146 .transition()
147 .duration(serviceTopologyConfig.duration)
148 .attr({
Matteo Scandoload5b2282016-02-16 11:50:51 -0800149 transform: (d) => `translate(${RackHelper.getComputeNodePosition(nodes, d.d3Id.replace('compute-node-', '') - 1)})`
Matteo Scandolo170d3be2016-02-11 08:58:04 -0800150 });
151
152 nodeContainer.append('rect')
Matteo Scandoload5b2282016-02-16 11:50:51 -0800153 .attr({
Matteo Scandolo35d53c82016-02-16 14:44:51 -0800154 width: 0,
155 height: 0
156 })
157 .transition()
158 .duration(serviceTopologyConfig.duration)
159 .attr({
Matteo Scandoload5b2282016-02-16 11:50:51 -0800160 width: d => RackHelper.getComputeNodeSize(d.instances)[0],
161 height: d => RackHelper.getComputeNodeSize(d.instances)[1],
162 });
163
164 nodeContainer.append('text')
165 .attr({
166 'text-anchor': 'start',
167 y: 15, //FIXME
Matteo Scandolo35d53c82016-02-16 14:44:51 -0800168 x: 10, //FIXME
169 opacity: 0
Matteo Scandoload5b2282016-02-16 11:50:51 -0800170 })
Matteo Scandolo35d53c82016-02-16 14:44:51 -0800171 .text(d => d.humanReadableName.split('.')[0])
172 .transition()
173 .duration(serviceTopologyConfig.duration)
174 .attr({
175 opacity: 1
176 })
Matteo Scandolo170d3be2016-02-11 08:58:04 -0800177
178 // if there are Compute Nodes
179 if(nodeContainer.length > 0){
Matteo Scandolo51031482016-02-17 13:54:11 -0800180 // draw instances for each compute node
181 nodeContainer.each(function(a){
182 _this.drawInstances(d3.select(this), a.instances);
183 })
Matteo Scandolo170d3be2016-02-11 08:58:04 -0800184 }
185
186 };
187
Matteo Scandolo7030ceb2016-02-16 13:29:26 -0800188 // NOTE Stripping unuseful names to shorten labels.
189 // This is not elegant
190 const formatInstanceName = (name) => {
191 return name
192 .replace('app_', '')
193 .replace('service_', '')
Matteo Scandolo26d17e12016-02-23 13:47:14 -0800194 // .replace('ovs_', '')
Matteo Scandolo7030ceb2016-02-16 13:29:26 -0800195 .replace('mysite_', '')
196 .replace('_instance', '');
197 };
198
Matteo Scandolo26d17e12016-02-23 13:47:14 -0800199 const getInstanceStatusColor = (instance) => {
200 function startWith(val, string){
201 return string.substring(0, val.length) === val;
202 }
203
204 if(startWith('0 - ', instance.backend_status)){
205 return 'provisioning';
206 }
207 if(startWith('1 - ', instance.backend_status)){
208 return 'good';
209 }
210 if(startWith('2 - ', instance.backend_status)){
211 return 'bad';
212 }
213 else {
214 return '';
215 }
216 };
217
Matteo Scandolof0d6e692016-02-24 11:14:01 -0800218 const drawContainer = (container, docker) => {
219
220 const containerBox = container.append('g')
221 .attr({
222 class: 'container',
223 transform: `translate(${serviceTopologyConfig.instance.margin}, 115)`
224 });
225
226 containerBox.append('rect')
227 .attr({
228 width: 250 - (serviceTopologyConfig.container.margin * 2),
229 height: serviceTopologyConfig.container.height,
230 });
231
232 containerBox.append('text')
233 .attr({
234 y: 20,
235 x: serviceTopologyConfig.instance.margin,
236 class: 'name'
237 })
238 .text(docker.name)
239
240 // add stats
241 const interestingMeters = ['memory', 'memory.usage', 'cpu_util'];
242
243 interestingMeters.forEach((m, i) => {
244 const meter = lodash.find(docker.stats, {meter: m});
245 // if there is no meter stats skip rendering
246 if(!angular.isDefined(meter)){
247 return;
248 }
249 containerBox.append('text')
250 .attr({
251 y: 40 + (i * 15),
252 x: serviceTopologyConfig.instance.margin,
253 opacity: 0
254 })
255 .text(`${meter.description}: ${Math.round(meter.value)} ${meter.unit}`)
256 .transition()
257 .duration(serviceTopologyConfig.duration)
258 .attr({
259 opacity: 1
260 });
261 });
262
263 // add port stats
264 const ports = ['eth0', 'eth1'];
265 const interestingPortMeters = [
266 {
267 meter: 'network.incoming.bytes.rate',
268 label: 'Incoming'
269 },
270 {
271 meter: 'network.outgoing.bytes.rate',
272 label: 'Outgoing'
273 }
274 ];
275
276 ports.forEach((p, j) => {
277
278 // if there are no port stats skip rendering
279 if(docker.port[p].length === 0){
280 return;
281 }
282
283 containerBox.append('text')
284 .attr({
285 y: 90,
286 x: serviceTopologyConfig.instance.margin + (120 * j),
287 class: 'name'
288 })
289 .text(`${docker.name}-${p}`)
290
291 interestingPortMeters.forEach((m, i) => {
292
293 const meter = lodash.find(docker.port[p], {meter: m.meter});
294 // if there is no meter stats skip rendering
295 if(!angular.isDefined(meter)){
296 return;
297 }
298 containerBox.append('text')
299 .attr({
300 y: 105 + (i * 15),
301 x: serviceTopologyConfig.instance.margin + (120 * j),
302 opacity: 0
303 })
304 .text(`${m.label}: ${Math.round(meter.value)} ${meter.unit}`)
305 .transition()
306 .duration(serviceTopologyConfig.duration)
307 .attr({
308 opacity: 1
309 });
310 });
311 });
312 }
313
Matteo Scandolo26d17e12016-02-23 13:47:14 -0800314 const showInstanceStats = (container, instance) => {
315
316 // NOTE this should be dinamically positioned
317 // base on the number of element present
318 const statsContainer = container.append('g')
319 .attr({
Matteo Scandolof0d6e692016-02-24 11:14:01 -0800320 transform: `translate(200, -120)`,
Matteo Scandolo26d17e12016-02-23 13:47:14 -0800321 class: 'stats-container'
322 });
323
324
325 statsContainer.append('line')
326 .attr({
Matteo Scandolof0d6e692016-02-24 11:14:01 -0800327 x1: -160,
328 y1: 120,
Matteo Scandolo26d17e12016-02-23 13:47:14 -0800329 x2: 0,
330 y2: 50,
Matteo Scandolo6aa165f2016-02-23 14:03:03 -0800331 stroke: 'black',
332 opacity: 0
333 })
334 .transition()
335 .duration(serviceTopologyConfig.duration)
336 .attr({
337 opacity: 1
Matteo Scandolo26d17e12016-02-23 13:47:14 -0800338 })
339
340 // NOTE rect should be dinamically sized base on the presence of a container
341 let statsHeight = 110;
Matteo Scandolof0d6e692016-02-24 11:14:01 -0800342 let statsWidth = 250;
Matteo Scandolo26d17e12016-02-23 13:47:14 -0800343
344 if (instance.container){
345 statsHeight += serviceTopologyConfig.container.height + (serviceTopologyConfig.container.margin * 2)
346 }
347
348 statsContainer.append('rect')
349 .attr({
350 width: statsWidth,
Matteo Scandolo6aa165f2016-02-23 14:03:03 -0800351 height: statsHeight,
352 opacity: 0
353 })
354 .transition()
355 .duration(serviceTopologyConfig.duration)
356 .attr({
357 opacity: 1
Matteo Scandolo26d17e12016-02-23 13:47:14 -0800358 });
359
360 // add instance info
361 statsContainer.append('text')
362 .attr({
363 y: 15,
364 x: serviceTopologyConfig.instance.margin,
Matteo Scandolo6aa165f2016-02-23 14:03:03 -0800365 class: 'name',
366 opacity: 0
Matteo Scandolo26d17e12016-02-23 13:47:14 -0800367 })
368 .text(instance.humanReadableName)
Matteo Scandolo6aa165f2016-02-23 14:03:03 -0800369 .transition()
370 .duration(serviceTopologyConfig.duration)
371 .attr({
372 opacity: 1
373 })
Matteo Scandolo26d17e12016-02-23 13:47:14 -0800374
375 statsContainer.append('text')
376 .attr({
377 y: 30,
378 x: serviceTopologyConfig.instance.margin,
Matteo Scandolo6aa165f2016-02-23 14:03:03 -0800379 class: 'ip',
380 opacity: 0
Matteo Scandolo26d17e12016-02-23 13:47:14 -0800381 })
382 .text(instance.ip)
Matteo Scandolo6aa165f2016-02-23 14:03:03 -0800383 .transition()
384 .duration(serviceTopologyConfig.duration)
385 .attr({
386 opacity: 1
387 })
Matteo Scandolo26d17e12016-02-23 13:47:14 -0800388
389 // add stats
390 const interestingMeters = ['memory', 'memory.usage', 'cpu', 'vcpus'];
391
392 interestingMeters.forEach((m, i) => {
393 const meter = lodash.find(instance.stats, {meter: m});
394 statsContainer.append('text')
395 .attr({
396 y: 55 + (i * 15),
Matteo Scandolo6aa165f2016-02-23 14:03:03 -0800397 x: serviceTopologyConfig.instance.margin,
398 opacity: 0
Matteo Scandolo26d17e12016-02-23 13:47:14 -0800399 })
Matteo Scandolof0d6e692016-02-24 11:14:01 -0800400 .text(`${meter.description}: ${Math.round(meter.value)} ${meter.unit}`)
Matteo Scandolo6aa165f2016-02-23 14:03:03 -0800401 .transition()
402 .duration(serviceTopologyConfig.duration)
403 .attr({
404 opacity: 1
405 });
Matteo Scandolo26d17e12016-02-23 13:47:14 -0800406 });
407
408 if(instance.container){
409 // draw container
Matteo Scandolof0d6e692016-02-24 11:14:01 -0800410 drawContainer(statsContainer, instance.container);
Matteo Scandolo26d17e12016-02-23 13:47:14 -0800411 }
412
413 };
414
Matteo Scandolo170d3be2016-02-11 08:58:04 -0800415 this.drawInstances = (container, instances) => {
Matteo Scandolo51031482016-02-17 13:54:11 -0800416
Matteo Scandoloc49ff702016-02-17 15:11:33 -0800417 // TODO check for stats field in instance and draw popup
418
Matteo Scandolo35d53c82016-02-16 14:44:51 -0800419 let {width, height} = container.node().getBoundingClientRect();
420
Matteo Scandolo170d3be2016-02-11 08:58:04 -0800421 let elements = container.selectAll('.instances')
422 .data(instances, d => angular.isString(d.d3Id) ? d.d3Id : d.d3Id = `instance-${++instanceId}`)
423
Matteo Scandolo35d53c82016-02-16 14:44:51 -0800424 var instanceContainer = elements.enter().append('g');
425
426 instanceContainer
Matteo Scandolo170d3be2016-02-11 08:58:04 -0800427 .attr({
Matteo Scandolo35d53c82016-02-16 14:44:51 -0800428 transform: `translate(${width / 2}, ${ height / 2})`,
Matteo Scandolo26d17e12016-02-23 13:47:14 -0800429 class: d => `instance ${d.selected ? 'active' : ''} ${getInstanceStatusColor(d)}`,
Matteo Scandolo35d53c82016-02-16 14:44:51 -0800430 })
431 .transition()
432 .duration(serviceTopologyConfig.duration)
433 .attr({
Matteo Scandolo51031482016-02-17 13:54:11 -0800434 transform: (d, i) => `translate(${RackHelper.getInstancePosition(i)})`
Matteo Scandolo170d3be2016-02-11 08:58:04 -0800435 });
436
437 instanceContainer.append('rect')
Matteo Scandoload5b2282016-02-16 11:50:51 -0800438 .attr({
Matteo Scandolo35d53c82016-02-16 14:44:51 -0800439 width: 0,
440 height: 0
441 })
442 .transition()
443 .duration(serviceTopologyConfig.duration)
444 .attr({
Matteo Scandoload5b2282016-02-16 11:50:51 -0800445 width: serviceTopologyConfig.instance.width,
446 height: serviceTopologyConfig.instance.height
447 });
448
449 instanceContainer.append('text')
450 .attr({
Matteo Scandolo7030ceb2016-02-16 13:29:26 -0800451 'text-anchor': 'middle',
452 y: 23, //FIXME
Matteo Scandolo35d53c82016-02-16 14:44:51 -0800453 x: 40, //FIXME
454 opacity: 0
Matteo Scandoload5b2282016-02-16 11:50:51 -0800455 })
Matteo Scandolo26d17e12016-02-23 13:47:14 -0800456 .text(d => formatInstanceName(d.humanReadableName))
Matteo Scandolo35d53c82016-02-16 14:44:51 -0800457 .transition()
458 .duration(serviceTopologyConfig.duration)
459 .attr({
460 opacity: 1
461 });
Matteo Scandolo7030ceb2016-02-16 13:29:26 -0800462
Matteo Scandolo26d17e12016-02-23 13:47:14 -0800463 // if stats are attached and instance is active,
464 // draw stats
Matteo Scandolo6aa165f2016-02-23 14:03:03 -0800465 instanceContainer.each(function(instance, i){
Matteo Scandolo26d17e12016-02-23 13:47:14 -0800466
467 const container = d3.select(this);
468
469 if(angular.isDefined(instance.stats) && instance.selected){
Matteo Scandolo6aa165f2016-02-23 14:03:03 -0800470 showInstanceStats(container, instance, i);
Matteo Scandolo26d17e12016-02-23 13:47:14 -0800471 }
472 });
473
Matteo Scandolo7030ceb2016-02-16 13:29:26 -0800474 instanceContainer
Matteo Scandolo26d17e12016-02-23 13:47:14 -0800475 .on('click', function(d){
476 console.log(`Draw vignette with stats for instance: ${d.name}`);
Matteo Scandolo7030ceb2016-02-16 13:29:26 -0800477 });
Matteo Scandolo170d3be2016-02-11 08:58:04 -0800478 };
Matteo Scandolo9fe01af2016-02-09 16:01:49 -0800479
480 this.addPhisical = (nodes) => {
481 nodes.append('rect')
482 .attr(serviceTopologyConfig.square);
483
484 nodes.append('text')
485 .attr({
486 'text-anchor': 'middle',
487 y: serviceTopologyConfig.square.y - 10
488 })
Matteo Scandolod4ea8772016-03-01 15:20:29 -0800489 .text(d => {
490 return d.name || d.humanReadableName
491 });
Matteo Scandolo9fe01af2016-02-09 16:01:49 -0800492 }
493
494 this.addDevice = (nodes) => {
495 nodes.append('circle')
496 .attr(serviceTopologyConfig.circle);
497
498 nodes.append('text')
499 .attr({
500 'text-anchor': 'end',
501 x: - serviceTopologyConfig.circle.r - 10,
502 y: serviceTopologyConfig.circle.r / 2
503 })
Matteo Scandolo50eeec62016-02-23 10:04:36 -0800504 .text(d => d.name || d.mac);
Matteo Scandolo9fe01af2016-02-09 16:01:49 -0800505 }
506 });
507})();