blob: e8754263cffec68c26f227e91c0633cc0c4be72f [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({
26 'text-anchor': 'middle'
27 })
28 .text(d => d.name)
Matteo Scandolo45fba732016-02-22 14:53:44 -080029
30 nodes.each(function(n){
31 let currentNode = d3.select(this);
32 // cicle trouch node to add Tags and Public IP
33 if(n.name === 'LAN' && angular.isDefined(n.subscriberTag)){
34 currentNode.append('text')
35 .attr({
36 'text-anchor': 'middle',
37 y: 40
38 })
39 .text(() => `C-Tag: ${n.subscriberTag.cTag}`);
40
41 currentNode.append('text')
42 .attr({
43 'text-anchor': 'middle',
44 y: 60
45 })
46 .text(() => `S-Tag: ${n.subscriberTag.sTag}`);
47 }
Matteo Scandolo012dddb2016-02-22 16:53:22 -080048
49 if(n.name === 'WAN' && angular.isDefined(n.subscriberIP)){
50 currentNode.append('text')
51 .attr({
52 'text-anchor': 'middle',
53 y: 40
54 })
55 .text(() => `Public IP: ${n.subscriberIP}`);
56 }
Matteo Scandolo45fba732016-02-22 14:53:44 -080057 });
Matteo Scandolo9fe01af2016-02-09 16:01:49 -080058 }
59
60 this.addRack = (nodes) => {
Matteo Scandolo170d3be2016-02-11 08:58:04 -080061
Matteo Scandolo35d53c82016-02-16 14:44:51 -080062 // loop because of D3
63 // rack will be only one
64 nodes.each(d => {
65 let [w, h] = RackHelper.getRackSize(d.computeNodes);
Matteo Scandoload5b2282016-02-16 11:50:51 -080066
Matteo Scandolo26d17e12016-02-23 13:47:14 -080067 // TODO update instead of delete and redraw
68 nodes.select('g').remove();
69
Matteo Scandoload5b2282016-02-16 11:50:51 -080070 let rack = nodes
Matteo Scandolo35d53c82016-02-16 14:44:51 -080071 .append('g');
72
73 rack
74 .attr({
75 transform: `translate(0,0)`
76 })
77 .transition()
78 .duration(serviceTopologyConfig.duration)
79 .attr({
Matteo Scandolo26d17e12016-02-23 13:47:14 -080080 transform: () => `translate(${- (w / 2)}, ${- (h / 2)})`
Matteo Scandoload5b2282016-02-16 11:50:51 -080081 });
82
83 rack
84 .append('rect')
85 .attr({
Matteo Scandolo35d53c82016-02-16 14:44:51 -080086 width: 0,
87 height: 0
88 })
89 .transition()
90 .duration(serviceTopologyConfig.duration)
91 .attr({
Matteo Scandoload5b2282016-02-16 11:50:51 -080092 width: w,
93 height: h
94 });
95
Matteo Scandolo35d53c82016-02-16 14:44:51 -080096 rack.append('text')
Matteo Scandoload5b2282016-02-16 11:50:51 -080097 .attr({
98 'text-anchor': 'middle',
Matteo Scandolo35d53c82016-02-16 14:44:51 -080099 y: - 10,
100 x: w / 2,
101 opacity: 0
Matteo Scandoload5b2282016-02-16 11:50:51 -0800102 })
Matteo Scandolo35d53c82016-02-16 14:44:51 -0800103 .text(d => d.name)
104 .transition()
105 .duration(serviceTopologyConfig.duration)
106 .attr({
107 opacity: 1
108 })
Matteo Scandoload5b2282016-02-16 11:50:51 -0800109
Matteo Scandolo35d53c82016-02-16 14:44:51 -0800110 this.drawComputeNodes(rack, d.computeNodes);
111
Matteo Scandolo170d3be2016-02-11 08:58:04 -0800112 });
Matteo Scandolo35d53c82016-02-16 14:44:51 -0800113
Matteo Scandolo170d3be2016-02-11 08:58:04 -0800114 };
115
116 this.drawComputeNodes = (container, nodes) => {
Matteo Scandoload5b2282016-02-16 11:50:51 -0800117
Matteo Scandolo170d3be2016-02-11 08:58:04 -0800118 let elements = container.selectAll('.compute-nodes')
119 .data(nodes, d => {
120 if(!angular.isString(d.d3Id)){
121 d.d3Id = `compute-node-${++computeNodeId}`;
122 }
123 return d.d3Id;
124 });
125
Matteo Scandolo35d53c82016-02-16 14:44:51 -0800126 let {width, height} = container.node().getBoundingClientRect();
127
128 var nodeContainer = elements.enter().append('g');
129
130 nodeContainer
Matteo Scandolo170d3be2016-02-11 08:58:04 -0800131 .attr({
Matteo Scandolo35d53c82016-02-16 14:44:51 -0800132 transform: `translate(${width / 2}, ${ height / 2})`,
Matteo Scandoload5b2282016-02-16 11:50:51 -0800133 class: 'compute-node',
Matteo Scandolo35d53c82016-02-16 14:44:51 -0800134 })
135 .transition()
136 .duration(serviceTopologyConfig.duration)
137 .attr({
Matteo Scandoload5b2282016-02-16 11:50:51 -0800138 transform: (d) => `translate(${RackHelper.getComputeNodePosition(nodes, d.d3Id.replace('compute-node-', '') - 1)})`
Matteo Scandolo170d3be2016-02-11 08:58:04 -0800139 });
140
141 nodeContainer.append('rect')
Matteo Scandoload5b2282016-02-16 11:50:51 -0800142 .attr({
Matteo Scandolo35d53c82016-02-16 14:44:51 -0800143 width: 0,
144 height: 0
145 })
146 .transition()
147 .duration(serviceTopologyConfig.duration)
148 .attr({
Matteo Scandoload5b2282016-02-16 11:50:51 -0800149 width: d => RackHelper.getComputeNodeSize(d.instances)[0],
150 height: d => RackHelper.getComputeNodeSize(d.instances)[1],
151 });
152
153 nodeContainer.append('text')
154 .attr({
155 'text-anchor': 'start',
156 y: 15, //FIXME
Matteo Scandolo35d53c82016-02-16 14:44:51 -0800157 x: 10, //FIXME
158 opacity: 0
Matteo Scandoload5b2282016-02-16 11:50:51 -0800159 })
Matteo Scandolo35d53c82016-02-16 14:44:51 -0800160 .text(d => d.humanReadableName.split('.')[0])
161 .transition()
162 .duration(serviceTopologyConfig.duration)
163 .attr({
164 opacity: 1
165 })
Matteo Scandolo170d3be2016-02-11 08:58:04 -0800166
167 // if there are Compute Nodes
168 if(nodeContainer.length > 0){
Matteo Scandolo51031482016-02-17 13:54:11 -0800169 // draw instances for each compute node
170 nodeContainer.each(function(a){
171 _this.drawInstances(d3.select(this), a.instances);
172 })
Matteo Scandolo170d3be2016-02-11 08:58:04 -0800173 }
174
175 };
176
Matteo Scandolo7030ceb2016-02-16 13:29:26 -0800177 // NOTE Stripping unuseful names to shorten labels.
178 // This is not elegant
179 const formatInstanceName = (name) => {
180 return name
181 .replace('app_', '')
182 .replace('service_', '')
Matteo Scandolo26d17e12016-02-23 13:47:14 -0800183 // .replace('ovs_', '')
Matteo Scandolo7030ceb2016-02-16 13:29:26 -0800184 .replace('mysite_', '')
185 .replace('_instance', '');
186 };
187
Matteo Scandolo26d17e12016-02-23 13:47:14 -0800188 const getInstanceStatusColor = (instance) => {
189 function startWith(val, string){
190 return string.substring(0, val.length) === val;
191 }
192
193 if(startWith('0 - ', instance.backend_status)){
194 return 'provisioning';
195 }
196 if(startWith('1 - ', instance.backend_status)){
197 return 'good';
198 }
199 if(startWith('2 - ', instance.backend_status)){
200 return 'bad';
201 }
202 else {
203 return '';
204 }
205 };
206
Matteo Scandolof0d6e692016-02-24 11:14:01 -0800207 const drawContainer = (container, docker) => {
208
209 const containerBox = container.append('g')
210 .attr({
211 class: 'container',
212 transform: `translate(${serviceTopologyConfig.instance.margin}, 115)`
213 });
214
215 containerBox.append('rect')
216 .attr({
217 width: 250 - (serviceTopologyConfig.container.margin * 2),
218 height: serviceTopologyConfig.container.height,
219 });
220
221 containerBox.append('text')
222 .attr({
223 y: 20,
224 x: serviceTopologyConfig.instance.margin,
225 class: 'name'
226 })
227 .text(docker.name)
228
229 // add stats
230 const interestingMeters = ['memory', 'memory.usage', 'cpu_util'];
231
232 interestingMeters.forEach((m, i) => {
233 const meter = lodash.find(docker.stats, {meter: m});
234 // if there is no meter stats skip rendering
235 if(!angular.isDefined(meter)){
236 return;
237 }
238 containerBox.append('text')
239 .attr({
240 y: 40 + (i * 15),
241 x: serviceTopologyConfig.instance.margin,
242 opacity: 0
243 })
244 .text(`${meter.description}: ${Math.round(meter.value)} ${meter.unit}`)
245 .transition()
246 .duration(serviceTopologyConfig.duration)
247 .attr({
248 opacity: 1
249 });
250 });
251
252 // add port stats
253 const ports = ['eth0', 'eth1'];
254 const interestingPortMeters = [
255 {
256 meter: 'network.incoming.bytes.rate',
257 label: 'Incoming'
258 },
259 {
260 meter: 'network.outgoing.bytes.rate',
261 label: 'Outgoing'
262 }
263 ];
264
265 ports.forEach((p, j) => {
266
267 // if there are no port stats skip rendering
268 if(docker.port[p].length === 0){
269 return;
270 }
271
272 containerBox.append('text')
273 .attr({
274 y: 90,
275 x: serviceTopologyConfig.instance.margin + (120 * j),
276 class: 'name'
277 })
278 .text(`${docker.name}-${p}`)
279
280 interestingPortMeters.forEach((m, i) => {
281
282 const meter = lodash.find(docker.port[p], {meter: m.meter});
283 // if there is no meter stats skip rendering
284 if(!angular.isDefined(meter)){
285 return;
286 }
287 containerBox.append('text')
288 .attr({
289 y: 105 + (i * 15),
290 x: serviceTopologyConfig.instance.margin + (120 * j),
291 opacity: 0
292 })
293 .text(`${m.label}: ${Math.round(meter.value)} ${meter.unit}`)
294 .transition()
295 .duration(serviceTopologyConfig.duration)
296 .attr({
297 opacity: 1
298 });
299 });
300 });
301 }
302
Matteo Scandolo26d17e12016-02-23 13:47:14 -0800303 const showInstanceStats = (container, instance) => {
304
305 // NOTE this should be dinamically positioned
306 // base on the number of element present
307 const statsContainer = container.append('g')
308 .attr({
Matteo Scandolof0d6e692016-02-24 11:14:01 -0800309 transform: `translate(200, -120)`,
Matteo Scandolo26d17e12016-02-23 13:47:14 -0800310 class: 'stats-container'
311 });
312
313
314 statsContainer.append('line')
315 .attr({
Matteo Scandolof0d6e692016-02-24 11:14:01 -0800316 x1: -160,
317 y1: 120,
Matteo Scandolo26d17e12016-02-23 13:47:14 -0800318 x2: 0,
319 y2: 50,
Matteo Scandolo6aa165f2016-02-23 14:03:03 -0800320 stroke: 'black',
321 opacity: 0
322 })
323 .transition()
324 .duration(serviceTopologyConfig.duration)
325 .attr({
326 opacity: 1
Matteo Scandolo26d17e12016-02-23 13:47:14 -0800327 })
328
329 // NOTE rect should be dinamically sized base on the presence of a container
330 let statsHeight = 110;
Matteo Scandolof0d6e692016-02-24 11:14:01 -0800331 let statsWidth = 250;
Matteo Scandolo26d17e12016-02-23 13:47:14 -0800332
333 if (instance.container){
334 statsHeight += serviceTopologyConfig.container.height + (serviceTopologyConfig.container.margin * 2)
335 }
336
337 statsContainer.append('rect')
338 .attr({
339 width: statsWidth,
Matteo Scandolo6aa165f2016-02-23 14:03:03 -0800340 height: statsHeight,
341 opacity: 0
342 })
343 .transition()
344 .duration(serviceTopologyConfig.duration)
345 .attr({
346 opacity: 1
Matteo Scandolo26d17e12016-02-23 13:47:14 -0800347 });
348
349 // add instance info
350 statsContainer.append('text')
351 .attr({
352 y: 15,
353 x: serviceTopologyConfig.instance.margin,
Matteo Scandolo6aa165f2016-02-23 14:03:03 -0800354 class: 'name',
355 opacity: 0
Matteo Scandolo26d17e12016-02-23 13:47:14 -0800356 })
357 .text(instance.humanReadableName)
Matteo Scandolo6aa165f2016-02-23 14:03:03 -0800358 .transition()
359 .duration(serviceTopologyConfig.duration)
360 .attr({
361 opacity: 1
362 })
Matteo Scandolo26d17e12016-02-23 13:47:14 -0800363
364 statsContainer.append('text')
365 .attr({
366 y: 30,
367 x: serviceTopologyConfig.instance.margin,
Matteo Scandolo6aa165f2016-02-23 14:03:03 -0800368 class: 'ip',
369 opacity: 0
Matteo Scandolo26d17e12016-02-23 13:47:14 -0800370 })
371 .text(instance.ip)
Matteo Scandolo6aa165f2016-02-23 14:03:03 -0800372 .transition()
373 .duration(serviceTopologyConfig.duration)
374 .attr({
375 opacity: 1
376 })
Matteo Scandolo26d17e12016-02-23 13:47:14 -0800377
378 // add stats
379 const interestingMeters = ['memory', 'memory.usage', 'cpu', 'vcpus'];
380
381 interestingMeters.forEach((m, i) => {
382 const meter = lodash.find(instance.stats, {meter: m});
383 statsContainer.append('text')
384 .attr({
385 y: 55 + (i * 15),
Matteo Scandolo6aa165f2016-02-23 14:03:03 -0800386 x: serviceTopologyConfig.instance.margin,
387 opacity: 0
Matteo Scandolo26d17e12016-02-23 13:47:14 -0800388 })
Matteo Scandolof0d6e692016-02-24 11:14:01 -0800389 .text(`${meter.description}: ${Math.round(meter.value)} ${meter.unit}`)
Matteo Scandolo6aa165f2016-02-23 14:03:03 -0800390 .transition()
391 .duration(serviceTopologyConfig.duration)
392 .attr({
393 opacity: 1
394 });
Matteo Scandolo26d17e12016-02-23 13:47:14 -0800395 });
396
397 if(instance.container){
398 // draw container
Matteo Scandolof0d6e692016-02-24 11:14:01 -0800399 drawContainer(statsContainer, instance.container);
Matteo Scandolo26d17e12016-02-23 13:47:14 -0800400 }
401
402 };
403
Matteo Scandolo170d3be2016-02-11 08:58:04 -0800404 this.drawInstances = (container, instances) => {
Matteo Scandolo51031482016-02-17 13:54:11 -0800405
Matteo Scandoloc49ff702016-02-17 15:11:33 -0800406 // TODO check for stats field in instance and draw popup
407
Matteo Scandolo35d53c82016-02-16 14:44:51 -0800408 let {width, height} = container.node().getBoundingClientRect();
409
Matteo Scandolo170d3be2016-02-11 08:58:04 -0800410 let elements = container.selectAll('.instances')
411 .data(instances, d => angular.isString(d.d3Id) ? d.d3Id : d.d3Id = `instance-${++instanceId}`)
412
Matteo Scandolo35d53c82016-02-16 14:44:51 -0800413 var instanceContainer = elements.enter().append('g');
414
415 instanceContainer
Matteo Scandolo170d3be2016-02-11 08:58:04 -0800416 .attr({
Matteo Scandolo35d53c82016-02-16 14:44:51 -0800417 transform: `translate(${width / 2}, ${ height / 2})`,
Matteo Scandolo26d17e12016-02-23 13:47:14 -0800418 class: d => `instance ${d.selected ? 'active' : ''} ${getInstanceStatusColor(d)}`,
Matteo Scandolo35d53c82016-02-16 14:44:51 -0800419 })
420 .transition()
421 .duration(serviceTopologyConfig.duration)
422 .attr({
Matteo Scandolo51031482016-02-17 13:54:11 -0800423 transform: (d, i) => `translate(${RackHelper.getInstancePosition(i)})`
Matteo Scandolo170d3be2016-02-11 08:58:04 -0800424 });
425
426 instanceContainer.append('rect')
Matteo Scandoload5b2282016-02-16 11:50:51 -0800427 .attr({
Matteo Scandolo35d53c82016-02-16 14:44:51 -0800428 width: 0,
429 height: 0
430 })
431 .transition()
432 .duration(serviceTopologyConfig.duration)
433 .attr({
Matteo Scandoload5b2282016-02-16 11:50:51 -0800434 width: serviceTopologyConfig.instance.width,
435 height: serviceTopologyConfig.instance.height
436 });
437
438 instanceContainer.append('text')
439 .attr({
Matteo Scandolo7030ceb2016-02-16 13:29:26 -0800440 'text-anchor': 'middle',
441 y: 23, //FIXME
Matteo Scandolo35d53c82016-02-16 14:44:51 -0800442 x: 40, //FIXME
443 opacity: 0
Matteo Scandoload5b2282016-02-16 11:50:51 -0800444 })
Matteo Scandolo26d17e12016-02-23 13:47:14 -0800445 .text(d => formatInstanceName(d.humanReadableName))
Matteo Scandolo35d53c82016-02-16 14:44:51 -0800446 .transition()
447 .duration(serviceTopologyConfig.duration)
448 .attr({
449 opacity: 1
450 });
Matteo Scandolo7030ceb2016-02-16 13:29:26 -0800451
Matteo Scandolo26d17e12016-02-23 13:47:14 -0800452 // if stats are attached and instance is active,
453 // draw stats
Matteo Scandolo6aa165f2016-02-23 14:03:03 -0800454 instanceContainer.each(function(instance, i){
Matteo Scandolo26d17e12016-02-23 13:47:14 -0800455
456 const container = d3.select(this);
457
458 if(angular.isDefined(instance.stats) && instance.selected){
Matteo Scandolo6aa165f2016-02-23 14:03:03 -0800459 showInstanceStats(container, instance, i);
Matteo Scandolo26d17e12016-02-23 13:47:14 -0800460 }
461 });
462
Matteo Scandolo7030ceb2016-02-16 13:29:26 -0800463 instanceContainer
Matteo Scandolo26d17e12016-02-23 13:47:14 -0800464 .on('click', function(d){
465 console.log(`Draw vignette with stats for instance: ${d.name}`);
Matteo Scandolo7030ceb2016-02-16 13:29:26 -0800466 });
Matteo Scandolo170d3be2016-02-11 08:58:04 -0800467 };
Matteo Scandolo9fe01af2016-02-09 16:01:49 -0800468
469 this.addPhisical = (nodes) => {
470 nodes.append('rect')
471 .attr(serviceTopologyConfig.square);
472
473 nodes.append('text')
474 .attr({
475 'text-anchor': 'middle',
476 y: serviceTopologyConfig.square.y - 10
477 })
478 .text(d => d.name);
479 }
480
481 this.addDevice = (nodes) => {
482 nodes.append('circle')
483 .attr(serviceTopologyConfig.circle);
484
485 nodes.append('text')
486 .attr({
487 'text-anchor': 'end',
488 x: - serviceTopologyConfig.circle.r - 10,
489 y: serviceTopologyConfig.circle.r / 2
490 })
Matteo Scandolo50eeec62016-02-23 10:04:36 -0800491 .text(d => d.name || d.mac);
Matteo Scandolo9fe01af2016-02-09 16:01:49 -0800492 }
493 });
494})();