blob: 7a11ab5b49910eea054ed494c6f710a7eb302a7c [file] [log] [blame]
Dzmitry Stremkouski88275d32019-07-23 19:42:42 +02001import hashlib
Dzmitry Stremkouskif1bcbb52019-04-11 15:48:24 +02002import requests
Dzmitry Stremkouskib71ada92019-04-05 22:37:59 +02003import subprocess
4import socket
5import salt.utils
6import logging
7import os
8import re
9import json
Dzmitry Stremkouski36290202019-05-05 21:26:25 +020010import yaml
Dzmitry Stremkouskib71ada92019-04-05 22:37:59 +020011
12__author__ = "Dzmitry Stremkouski"
13__copyright__ = "Copyright 2019, Mirantis Inc."
14__license__ = "Apache 2.0"
15
16logger = logging.getLogger(__name__)
17stream = logging.StreamHandler()
18logger.addHandler(stream)
19
Dzmitry Stremkouski36290202019-05-05 21:26:25 +020020try:
21 from yaml import CLoader as Loader, CDumper as Dumper
22except ImportError:
23 from yaml import Loader, Dumper
24
25default_vrouter_info_map = yaml.load("""
26ContrailConfig:
27- deleted
28- elements:uuid
29- elements:virtual_router_dpdk_enabled
30- elements:virtual_router_type
31VrouterAgent:
32- build_info:build-info:0:build-version
33- build_info:build-info:0:build-number
Dzmitry Stremkouski36290202019-05-05 21:26:25 +020034- config_file
35- control_ip
36- control_node_list_cfg
37- dns_server_list_cfg
38- dns_servers
39- down_interface_count
40- eth_name
41- headless_mode_cfg
42- hostname_cfg
43- hypervisor
44- mode
45- phy_if
46- platform
47- self_ip_list
48- total_interface_count
49- tunnel_type
50- vhost_cfg
51- vhost_if
52- vr_limits:max_interfaces
53- vr_limits:max_labels
54- vr_limits:max_mirror_entries
55- vr_limits:max_nexthops
56- vr_limits:max_vrfs
57- vr_limits:vrouter_max_bridge_entries
58- vr_limits:vrouter_max_flow_entries
59- vr_limits:vrouter_max_oflow_bridge_entries
60- vr_limits:vrouter_max_oflow_entries
61- xmpp_peer_list:*:ip
62- xmpp_peer_list:*:primary
63- xmpp_peer_list:*:status
64""", Loader=Loader)
65
Dzmitry Stremkouskia78a04d2019-07-13 11:05:03 +020066default_peer_filter = ["encoding", "peer_address", "state"]
67
Dzmitry Stremkouskib71ada92019-04-05 22:37:59 +020068
69def _failed_minions(out, agent, failed_minions):
70
71 ''' Verify failed minions '''
72
73 if len(failed_minions) > 0:
74 logger.error("%s check FAILED" % agent)
75 logger.error("Some minions returned non-zero exit code or empty data")
76 logger.error("Failed minions:" + str(failed_minions))
77 for minion in failed_minions:
78 logger.error(minion)
79 logger.debug(str(out[minion]['ret']))
80 __context__['retcode'] = 2
81 return False
82
83 return True
84
85
86def _minions_output(out, agent, ignore_dead, ignore_empty=False):
87
88 ''' Verify minions output and exit code '''
89
90 if not out:
91 logger.error("%s check FAILED" % agent)
92 logger.error("No response from master cmd")
93 __context__['retcode'] = 2
94 return False
95
96 if not ignore_dead:
97 jid = out.itervalues().next()['jid']
98 job_stats = __salt__['saltutil.runner']( 'jobs.print_job', arg=[jid] ) or None
99 if not job_stats:
100 logger.error("%s check FAILED" % agent)
101 logger.error("No response from master runner")
102 __context__['retcode'] = 2
103 return False
104
105 job_result = job_stats[jid]['Result']
106 job_minions = job_stats[jid]['Minions']
107 if len(job_minions) != len(job_result):
108 logger.error("%s check FAILED" % agent)
109 logger.error("Some minions are offline")
110 logger.error(list(set(job_minions) - set(job_result.keys())))
111 __context__['retcode'] = 2
112 return False
113
114 failed_minions = []
115 for minion in out:
116 if 'retcode' in out[minion]:
117 if out[minion]['retcode'] == 0:
118 if not ignore_empty:
119 if isinstance(out[minion]['ret'], bool):
120 if minion not in failed_minions:
121 failed_minions.append(minion)
122 elif len(out[minion]['ret']) == 0:
123 if minion not in failed_minions:
124 failed_minions.append(minion)
125 else:
126 if minion not in failed_minions:
127 failed_minions.append(minion)
128 else:
129 if minion not in failed_minions:
130 failed_minions.append(minion)
131
132 if not _failed_minions(out, agent, failed_minions):
133 __context__['retcode'] = 2
134 return False
135
136 return True
137
138
139def minions_check(wait_timeout=1, gather_job_wait_timeout=1, target='*', target_type='glob', ignore_dead=False):
140
141 ''' Verify minions are online '''
142
143 agent = "Minions"
144 out = __salt__['saltutil.cmd']( tgt=target,
145 tgt_type=target_type,
146 fun='test.ping',
147 timeout=wait_timeout,
148 gather_job_timeout=gather_job_wait_timeout
149 ) or None
150
151 return _minions_output(out, agent, ignore_dead, ignore_empty=True)
152
153
154def time_diff_check(time_diff=1, target='*', target_type='glob', ignore_dead=False, **kwargs):
155
156 ''' Verify time diff on servers '''
157
158 agent = "Time diff"
159 out = __salt__['saltutil.cmd']( tgt=target,
160 tgt_type=target_type,
161 fun='status.time',
162 arg=['%s'],
163 timeout=3
164 ) or None
165
166 if not _minions_output(out, agent, ignore_dead):
167 __context__['retcode'] = 2
168 return False
169
170 minions_times = {}
171 env_times = []
172 verified_minions = []
173
174 for minion in out:
175 verified_minions.append(minion)
176 if out[minion]['retcode'] == 0:
177 minion_time = int(out[minion]['ret'])
178 if str(minion_time) not in minions_times:
179 minions_times[str(minion_time)] = []
180 minions_times[str(minion_time)].append(minion)
181 env_times.append(minion_time)
182
183 env_times.sort()
184 diff = env_times[-1] - env_times[0]
185
186 if diff > time_diff:
187 __context__['retcode'] = 2
188 if kwargs.get("debug", False):
189 return False, minions_times
190 else:
191 return False
192
193 if kwargs.get("debug", False):
194 logger.info(verified_minions)
195 return True
196
197
Dzmitry Stremkouski2c709f22019-04-22 02:27:54 +0200198def contrail_check(target='I@opencontrail:control or I@opencontrail:collector or I@opencontrail:compute', target_type='compound', ignore_dead=False, **kwargs):
Dzmitry Stremkouskib71ada92019-04-05 22:37:59 +0200199
200 ''' Verify contrail status returns nothing critical '''
201
202 agent = "Contrail status"
203 out = __salt__['saltutil.cmd']( tgt=target,
204 tgt_type=target_type,
205 fun='cmd.run',
206 arg=['contrail-status'],
207 timeout=5
208 ) or None
209
210 if not _minions_output(out, agent, ignore_dead):
211 __context__['retcode'] = 2
212 return False
213
214 failed_minions = []
215 pattern = '^(==|$|\S+\s+(active|backup|inactive\s\(disabled\son\sboot\)))'
216 prog = re.compile(pattern)
217
218 validated = []
219 for minion in out:
220 for line in out[minion]['ret'].split('\n'):
221 if not prog.match(line) and minion not in failed_minions:
222 failed_minions.append(minion)
223 validated.append(minion)
224
225 if not _failed_minions(out, agent, failed_minions):
226 __context__['retcode'] = 2
227 return False
228
229 if kwargs.get("debug", False):
230 logger.info(validated)
231 return True
232
233
234def galera_check(cluster_size=3, target='I@galera:master or I@galera:slave', target_type='compound', ignore_dead=False, **kwargs):
235
236 ''' Verify galera cluster size and state '''
237
238 agent = "Galera status"
239 out = __salt__['saltutil.cmd']( tgt=target,
240 tgt_type=target_type,
241 fun='mysql.status',
242 timeout=3
243 ) or None
244
245 if not _minions_output(out, agent, ignore_dead):
246 __context__['retcode'] = 2
247 return False
248
249 failed_minions = []
250
251 validated = []
252 for minion in out:
253 if int(out[minion]['ret']['wsrep_cluster_size']) != int(cluster_size) and minion not in failed_minions:
254 failed_minions.append(minion)
255 if out[minion]['ret']['wsrep_evs_state'] != 'OPERATIONAL' and minion not in failed_minions:
256 failed_minions.append(minion)
257 validated.append(minion)
258
259 if not _failed_minions(out, agent, failed_minions):
260 __context__['retcode'] = 2
261 return False
262
263 if kwargs.get("debug", False):
264 logger.info(validated)
265 logger.info("Cluster size: " + str(out[validated[0]]['ret']['wsrep_cluster_size']))
266 logger.info("Cluster state: " + str(out[validated[0]]['ret']['wsrep_evs_state']))
267 return True
268
269
270def _quote_str(s, l=False, r=False):
271
272 ''' Quting rabbitmq erl objects for json import '''
273
274 if len(s) > 0:
275 if l:
276 s = s.lstrip()
277 if r:
278 s = s.rstrip()
279 if (s[0] == "'") and (s[-1] != "'") and r and not l:
280 s += "'"
281 if (s[0] == '"') and (s[-1] != '"') and r and not l:
282 s += '"'
283 if (s[-1] == "'") and (s[0] != "'") and l and not r:
284 s = "'" + s
285 if (s[-1] == '"') and (s[0] != '"') and l and not r:
286 s = '"' + s
287 if (s[-1] != "'") and (s[-1] != '"') and (s[0] != "'") and (s[0] != '"'):
288 s = '"' + s.replace('"', '\\\"') + '"'
289 else:
290 if (not l) and (not r) and s[0] != '"' and not s[-1] != '"':
291 s= s.replace('"', '\\\"')
292 return s.replace("'", '"')
293 else:
294 return s
295
296
297def _sanitize_rmqctl_output(string):
298
299 ''' Sanitizing rabbitmq erl objects for json import '''
300
301 rabbitctl_json = ""
302 for line in string.split(','):
303 copy = line
304 left = ""
305 right = ""
306 mid = copy
307 lpar = False
308 rpar = False
309 if re.search('([\[\{\s]+)(.*)', copy):
310 mid = re.sub('^([\[\{\s]+)','', copy)
311 left = copy[:-len(mid)]
312 copy = mid
313 lpar = True
314 if re.search('(.*)([\]\}\s]+)$', copy):
315 mid = re.sub('([\]\}\s]+)$','', copy)
316 right = copy[len(mid):]
317 copy = mid
318 rpar = True
319 result = left + _quote_str(mid, l=lpar, r=rpar) + right
320 if (not rpar) and lpar and (len(left.strip()) > 0) and (left.strip()[-1] == '{'):
321 result += ":"
322 else:
323 result += ","
324 rabbitctl_json += result
325
326 rabbitctl_json = rabbitctl_json[:-1]
327 new_rabbitctl_json = rabbitctl_json
328 for s in re.findall('"[^:\[{\]}]+"\s*:\s*("[^\[{\]}]+")', rabbitctl_json):
329 if '"' in s[1:][:-1]:
330 orig = s
331 changed = '"' + s.replace('\\', '\\\\').replace('"', '\\\"') + '"'
332 new_rabbitctl_json = new_rabbitctl_json.replace(orig, changed)
333 return new_rabbitctl_json
334
335
Dzmitry Stremkouskif1bcbb52019-04-11 15:48:24 +0200336def rabbitmq_list_queues(vhost='/'):
337
338 ''' JSON formatted RabbitMQ queues list '''
339
340 proc = subprocess.Popen(['rabbitmqctl', 'list_queues' , '-p', vhost], stdout=subprocess.PIPE)
341 stdout, stderr = proc.communicate()
342
343 queues = {}
344 for line in stdout.split('\n'):
345 if re.findall('[0-9]$', line):
346 queue_name, num = re.sub(r"\s+", " ", line).split()
347 queues[queue_name] = int(num)
348
349 return queues
350
351
352def rabbitmq_list_vhosts():
353
354 ''' JSON formatted RabbitMQ vhosts list '''
355
356 proc = subprocess.Popen(['rabbitmqctl', 'list_vhosts'], stdout=subprocess.PIPE)
357 stdout, stderr = proc.communicate()
358
359 vhosts = []
360 for line in stdout.split('\n'):
361 if re.findall('^/', line):
362 vhosts.append(line)
363
364 return vhosts
365
366
Dzmitry Stremkouskib71ada92019-04-05 22:37:59 +0200367def rabbitmq_cmd(cmd):
368
369 ''' JSON formatted RabbitMQ command output '''
370
371 supported_commands = ['status', 'cluster_status', 'list_hashes', 'list_ciphers']
372 if cmd not in supported_commands:
373 logger.error("Command is not supported yet, sorry")
374 logger.error("Supported commands are: " + str(supported_commands))
375 __context__['retcode'] = 2
376 return False
377
378 proc = subprocess.Popen(['rabbitmqctl', cmd], stdout=subprocess.PIPE)
379 stdout, stderr = proc.communicate()
380
381 rabbitmqctl_cutoff = stdout[int(stdout.find('[')):int(stdout.rfind(']'))+1].replace('\n','')
382 return json.loads(_sanitize_rmqctl_output(rabbitmqctl_cutoff))
383
384
385def rabbitmq_check(target='I@rabbitmq:server', target_type='compound', ignore_dead=False, **kwargs):
386
387 ''' Verify rabbit cluster and it's alarms '''
388
389 agent = "RabbitMQ status"
390 out = __salt__['saltutil.cmd']( tgt=target,
391 tgt_type=target_type,
392 fun='health_checks.rabbitmq_cmd',
393 arg=['cluster_status'],
394 timeout=3
395 ) or None
396
397 if not _minions_output(out, agent, ignore_dead):
398 __context__['retcode'] = 2
399 return False
400
401 failed_minions = []
402
403 for minion in out:
404 rabbitmqctl_json = out[minion]['ret']
405 running_nodes = []
406 available_nodes = []
407 alarms = []
408 for el in rabbitmqctl_json:
409 if 'alarms' in el:
410 alarms = el['alarms']
411 if 'nodes' in el:
412 available_nodes = el['nodes'][0]['disc']
413 if 'running_nodes' in el:
414 running_nodes = el['running_nodes']
415
416 if running_nodes.sort() == available_nodes.sort():
417 nodes_alarms = []
418 for node in running_nodes:
419 for el in alarms:
420 if node in el:
421 if len(el[node]) > 0:
422 nodes_alarms.append(el[node])
423 if len(nodes_alarms) > 0:
424 failed_minions.append(minion)
425 else:
426 failed_minions.append(minion)
427
428 if not _failed_minions(out, agent, failed_minions):
429 __context__['retcode'] = 2
430 return False
431
432 if kwargs.get("debug", False):
433 logger.info(running_nodes)
434 return True
435
436
437def haproxy_status(socket_path='/run/haproxy/admin.sock', buff_size = 8192, encoding = 'UTF-8', stats_filter=[]):
438
439 ''' JSON formatted haproxy status '''
440
441 stat_cmd = 'show stat\n'
442
443 if not os.path.exists(socket_path):
444 logger.error('Socket %s does not exist or haproxy not running' % socket_path)
445 __context__['retcode'] = 2
446 return False
447
448 client = socket.socket( socket.AF_UNIX, socket.SOCK_STREAM)
449 client.connect(socket_path)
450 stat_cmd = 'show stat\n'
451
452 client.send(bytearray(stat_cmd, encoding))
453 output = client.recv(buff_size)
454
455 res = ""
456 while output:
457 res += output.decode(encoding)
458 output = client.recv(buff_size)
459 client.close()
460
461 haproxy_stats = {}
462 res_list = res.split('\n')
463 fields = res_list[0][2:].split(',')
464 stats_list = []
465 for line in res_list[1:]:
466 if len(line.strip()) > 0:
467 stats_list.append(line)
468
469 for i in range(len(stats_list)):
470 element = {}
471 for n in fields:
472 element[n] = stats_list[i].split(',')[fields.index(n)]
473 server_name = element.pop('pxname')
474 server_type = element.pop('svname')
475 if stats_filter:
476 filtered_element = element.copy()
477 for el in element:
478 if el not in stats_filter:
479 filtered_element.pop(el)
480 element = filtered_element
481 if server_name not in haproxy_stats:
482 haproxy_stats[server_name] = {}
483 if server_type == "FRONTEND" or server_type == "BACKEND":
484 haproxy_stats[server_name][server_type] = element
485 else:
486 if 'UPSTREAM' not in haproxy_stats[server_name]:
487 haproxy_stats[server_name]['UPSTREAM'] = {}
488 haproxy_stats[server_name]['UPSTREAM'][server_type] = element
489
490 return haproxy_stats
491
492
493def haproxy_check(target='I@haproxy:proxy', target_type='compound', ignore_dead=False, ignore_services=[], ignore_upstreams=[], ignore_no_upstream=False, **kwargs):
494
495 ''' Verify haproxy backends status '''
496
497 agent = "haproxy status"
498 out = __salt__['saltutil.cmd']( tgt=target,
499 tgt_type=target_type,
500 fun='health_checks.haproxy_status',
501 arg=["stats_filter=['status']"],
502 timeout=3
503 ) or None
504
505 if not _minions_output(out, agent, ignore_dead):
506 __context__['retcode'] = 2
507 return False
508
509 failed_minions = []
510 verified_minions = []
511 for minion in out:
512 verified_minions.append(minion)
513 haproxy_json = out[minion]['ret']
514 for service in haproxy_json:
515 if service not in ignore_services:
516 if haproxy_json[service]['FRONTEND']['status'] != 'OPEN':
517 if minion not in failed_minions:
518 failed_minions.append(minion)
519 if haproxy_json[service]['BACKEND']['status'] != 'UP':
520 if minion not in failed_minions:
521 failed_minions.append(minion)
522 if 'UPSTREAM' in haproxy_json[service]:
523 for upstream in haproxy_json[service]['UPSTREAM']:
524 if upstream not in ignore_upstreams:
525 if haproxy_json[service]['UPSTREAM'][upstream]['status'] != 'UP':
526 if minion not in failed_minions:
527 failed_minions.append(minion)
528 else:
529 if not ignore_no_upstream:
530 if minion not in failed_minions:
531 failed_minions.append(minion)
532
533 if not _failed_minions(out, agent, failed_minions):
534 __context__['retcode'] = 2
535 return False
536
537 if kwargs.get("debug", False):
538 logger.info(verified_minions)
539 return True
540
541
542def df_check(target='*', target_type='glob', verify='space', space_limit=80, inode_limit=80, ignore_dead=False, ignore_partitions=[], **kwargs):
543
544 ''' Verify storage space/inodes status '''
545
546 supported_options = ['space', 'inodes']
547 if verify not in supported_options:
548 logger.error('Unsupported "verify" option.')
549 logger.error('Supported options are: %s' % str(supported_options))
550 __context__['retcode'] = 2
551 return False
552
553 if verify == 'space':
554 fun_cmd = 'disk.usage'
555 json_arg = 'capacity'
556 limit = space_limit
557 elif verify == 'inodes':
558 fun_cmd = 'disk.inodeusage'
559 json_arg = 'use'
560 limit = inode_limit
561
562 agent = "df status"
563 out = __salt__['saltutil.cmd']( tgt=target,
564 tgt_type=target_type,
565 fun=fun_cmd,
566 timeout=3
567 ) or None
568
569 if not _minions_output(out, agent, ignore_dead):
570 __context__['retcode'] = 2
571 return False
572
573 failed_minions = []
574 verified_minions = []
575 for minion in out:
576 verified_minions.append(minion)
577 df_json = out[minion]['ret']
578 for disk in df_json:
579 if disk not in ignore_partitions:
580 if int(df_json[disk][json_arg][:-1]) > int(limit):
581 if minion not in failed_minions:
582 failed_minions.append(minion)
583
584 if not _failed_minions(out, agent, failed_minions):
585 __context__['retcode'] = 2
586 return False
587
588 if kwargs.get("debug", False):
589 logger.info(verified_minions)
590 return True
591
592
593def load_check(target='*', target_type='glob', la1=3, la5=3, la15=3, ignore_dead=False, **kwargs):
594
595 ''' Verify load average status '''
596
597 agent = "load average status"
598 out = __salt__['saltutil.cmd']( tgt=target,
599 tgt_type=target_type,
600 fun='status.loadavg',
601 timeout=3
602 ) or None
603
604 if not _minions_output(out, agent, ignore_dead):
605 __context__['retcode'] = 2
606 return False
607
608 failed_minions = []
609 verified_minions = []
610 for minion in out:
611 verified_minions.append(minion)
612 la_json = out[minion]['ret']
613 if float(la_json['1-min']) > float(la1):
614 if minion not in failed_minions:
615 failed_minions.append(minion)
616 if float(la_json['5-min']) > float(la5):
617 if minion not in failed_minions:
618 failed_minions.append(minion)
619 if float(la_json['15-min']) > float(la15):
620 if minion not in failed_minions:
621 failed_minions.append(minion)
622
623 if not _failed_minions(out, agent, failed_minions):
624 __context__['retcode'] = 2
625 return False
626
627 if kwargs.get("debug", False):
628 logger.info(verified_minions)
629 return True
630
631
632def netdev_check(target='*', target_type='glob', rx_drop_limit=0, tx_drop_limit=0, ignore_devices=[], ignore_dead=False, **kwargs):
633
634 ''' Verify netdev rx/tx drop status '''
635
636 agent = "netdev rx/tx status"
637 out = __salt__['saltutil.cmd']( tgt=target,
638 tgt_type=target_type,
639 fun='status.netdev',
640 timeout=3
641 ) or None
642
643 if not _minions_output(out, agent, ignore_dead):
644 __context__['retcode'] = 2
645 return False
646
Dzmitry Stremkouski2c709f22019-04-22 02:27:54 +0200647 failed_minions = {}
Dzmitry Stremkouskib71ada92019-04-05 22:37:59 +0200648 verified_minions = []
649 for minion in out:
650 verified_minions.append(minion)
651 dev_json = out[minion]['ret']
652 for netdev in dev_json:
653 if netdev not in ignore_devices:
654 if int(dev_json[netdev]['rx_drop']) > int(rx_drop_limit):
655 if minion not in failed_minions:
Dzmitry Stremkouski2c709f22019-04-22 02:27:54 +0200656 failed_minions[minion] = {}
657 if netdev not in failed_minions[minion]:
658 failed_minions[minion][netdev] = {}
659 failed_minions[minion][netdev]['rx_drop'] = int(dev_json[netdev]['rx_drop'])
Dzmitry Stremkouskib71ada92019-04-05 22:37:59 +0200660 if int(dev_json[netdev]['tx_drop']) > int(tx_drop_limit):
661 if minion not in failed_minions:
Dzmitry Stremkouski2c709f22019-04-22 02:27:54 +0200662 failed_minions[minion] = {}
663 if netdev not in failed_minions[minion]:
664 failed_minions[minion][netdev] = {}
665 failed_minions[minion][netdev]['tx_drop'] = int(dev_json[netdev]['tx_drop'])
Dzmitry Stremkouskib71ada92019-04-05 22:37:59 +0200666
667 if not _failed_minions(out, agent, failed_minions):
668 __context__['retcode'] = 2
669 return False
670
671 if kwargs.get("debug", False):
672 logger.info(verified_minions)
673 return True
674
675
676def mem_check(target='*', target_type='glob', used_limit=80, ignore_dead=False, **kwargs):
677
678 ''' Verify available memory status '''
679
680 agent = "available memory status"
681 out = __salt__['saltutil.cmd']( tgt=target,
682 tgt_type=target_type,
683 fun='status.meminfo',
684 timeout=3
685 ) or None
686
687 if not _minions_output(out, agent, ignore_dead):
688 __context__['retcode'] = 2
689 return False
690
691 failed_minions = []
692 verified_minions = []
693 for minion in out:
694 mem_avail = int(out[minion]['ret']['MemAvailable']['value'])
695 mem_total = int(out[minion]['ret']['MemTotal']['value'])
696 used_pct = float((mem_total - mem_avail) * 100 / mem_total)
697 if used_pct > float(used_limit):
698 if minion not in failed_minions:
699 failed_minions.append(minion)
700 else:
701 verified_minions.append( { minion : str(used_pct) + '%' } )
702
703 if not _failed_minions(out, agent, failed_minions):
704 __context__['retcode'] = 2
705 return False
706
707 if kwargs.get("debug", False):
708 logger.info(verified_minions)
709 return True
710
711
712def ntp_status(params = ['-4', '-p', '-n']):
713
714 ''' JSON formatted ntpq command output '''
715
716 ntp_states = [
717 { 'indicator': '#', 'comment': 'source selected, distance exceeds maximum value' },
718 { 'indicator': 'o', 'comment': 'source selected, Pulse Per Second (PPS) used' },
719 { 'indicator': '+', 'comment': 'source selected, included in final set' },
720 { 'indicator': 'x', 'comment': 'source false ticker' },
721 { 'indicator': '.', 'comment': 'source selected from end of candidate list' },
722 { 'indicator': '-', 'comment': 'source discarded by cluster algorithm' },
723 { 'indicator': '*', 'comment': 'current time source' },
724 { 'indicator': ' ', 'comment': 'source discarded high stratum, failed sanity' }
725 ]
726 ntp_state_indicators = []
727 for state in ntp_states:
728 ntp_state_indicators.append(state['indicator'])
729 source_types = {}
730 source_types['l'] = "local (such as a GPS, WWVB)"
731 source_types['u'] = "unicast (most common)"
732 source_types['m'] = "multicast"
733 source_types['b'] = "broadcast"
734 source_types['-'] = "netaddr"
735
736 proc = subprocess.Popen(['ntpq'] + params, stdout=subprocess.PIPE)
737 stdout, stderr = proc.communicate()
738
739 ntp_lines = stdout.split('\n')
740 fields = re.sub("\s+", " ", ntp_lines[0]).split()
741 fields[fields.index('st')] = 'stratum'
742 fields[fields.index('t')] = 'source_type'
743
744 ntp_peers = {}
745 for line in ntp_lines[2:]:
746 if len(line.strip()) > 0:
747 element = {}
748 values = re.sub("\s+", " ", line).split()
749 for i in range(len(values)):
750 if fields[i] == 'source_type':
751 element[fields[i]] = { 'indicator': values[i], 'comment': source_types[values[i]] }
752 elif fields[i] in ['stratum', 'when', 'poll', 'reach']:
753 if values[i] == '-':
754 element[fields[i]] = int(-1)
755 else:
756 element[fields[i]] = int(values[i])
757 elif fields[i] in ['delay', 'offset', 'jitter']:
758 element[fields[i]] = float(values[i])
759 else:
760 element[fields[i]] = values[i]
761 peer = element.pop('remote')
762 peer_state = peer[0]
763 if peer_state in ntp_state_indicators:
764 peer = peer[1:]
765 else:
766 peer_state = 'f'
767 element['current'] = False
768 if peer_state == '*':
769 element['current'] = True
770 for state in ntp_states:
771 if state['indicator'] == peer_state:
772 element['state'] = state.copy()
773 if peer_state == 'f' and state['indicator'] == ' ':
774 fail_state = state.copy()
775 fail_state.pop('indicator')
776 fail_state['indicator'] = 'f'
777 element['state'] = fail_state
778 ntp_peers[peer] = element
779
780 return ntp_peers
781
782
783def ntp_check(min_peers=1, max_stratum=3, target='*', target_type='glob', ignore_dead=False, **kwargs):
784
785 ''' Verify NTP peers status '''
786
787 agent = "ntpd peers status"
788 out = __salt__['saltutil.cmd']( tgt=target,
789 tgt_type=target_type,
790 fun='health_checks.ntp_status',
791 timeout=3
792 ) or None
793
794 if not _minions_output(out, agent, ignore_dead):
795 __context__['retcode'] = 2
796 return False
797
798 failed_minions = []
799 verified_minions = []
800 for minion in out:
801 ntp_json = out[minion]['ret']
802 good_peers = []
803 for peer in ntp_json:
804 if ntp_json[peer]['stratum'] < int(max_stratum) + 1:
805 good_peers.append(peer)
806 if len(good_peers) > int(min_peers) - 1:
807 if minion not in verified_minions:
808 verified_minions.append(minion)
809 else:
810 if minion not in failed_minions:
811 failed_minions.append(minion)
812
813 if not _failed_minions(out, agent, failed_minions):
814 __context__['retcode'] = 2
815 return False
816
817 if kwargs.get("debug", False):
818 logger.info(verified_minions)
Dzmitry Stremkouskif1bcbb52019-04-11 15:48:24 +0200819
Dzmitry Stremkouskib71ada92019-04-05 22:37:59 +0200820 return True
Dzmitry Stremkouskif1bcbb52019-04-11 15:48:24 +0200821
822
823def gluster_pool_list():
824
825 ''' JSON formatted GlusterFS pool list command output '''
826
827 proc = subprocess.Popen(['gluster', 'pool', 'list'], stdout=subprocess.PIPE)
828 stdout, stderr = proc.communicate()
829
830 regex = re.compile('^(\S+)\s+(\S+)\s+(\S+)$')
831 fields = regex.findall(stdout.split('\n')[0])[0]
832
833 pool = {}
834
835 for line in stdout.split('\n')[1:]:
836 if len(line.strip()) > 0:
837 peer = {}
838 values = regex.findall(line.strip())[0]
839 for i in range(len(fields)):
840 peer[fields[i].lower()] = values[i]
841 uuid = peer.pop('uuid')
842 pool[uuid] = peer
843
844 return pool
845
846
847def gluster_volume_status():
848
849 ''' JSON formatted GlusterFS volumes status command output '''
850
851 proc = subprocess.Popen(['gluster', 'volume', 'status', 'all', 'detail'], stdout=subprocess.PIPE)
852 stdout, stderr = proc.communicate()
853
854 begin_volume = False
855 brick_lookup = False
856 volumes = {}
857 volume_name = ""
858
859 for line in stdout.split('\n'):
860 if 'Status of volume' in line:
861 volume_name = line.split(':')[1].strip()
862 volumes[volume_name] = { 'bricks': [] }
863 begin_volume = True
864 elif len(line.strip()) == 0:
865 if begin_volume:
866 begin_volume = False
867 elif '--------' in line:
868 brick_lookup = True
869 elif brick_lookup and line.split(':')[0].strip() == 'Brick':
870 brick_host, brick_path = re.findall('^Brick\ *:\ (.*)', line)[0].split()[1].split(':')
871 volumes[volume_name]['bricks'].append({ 'host': brick_host, 'path': brick_path })
872 brick_lookup = False
873 else:
874 brick_key, brick_value = line.split(':')
875 brick_key = brick_key.strip().lower().replace(' ', '_')
876 brick_value = brick_value.strip()
877 volumes[volume_name]['bricks'][len(volumes[volume_name]['bricks']) - 1][brick_key] = brick_value
878
879 return volumes
880
881
882def gluster_pool_check(target='I@glusterfs:server', target_type='compound', expected_size=3, ignore_dead=False, **kwargs):
883
884 ''' Check GlusterFS peer status '''
885
886 agent = "glusterfs peer status"
887 out = __salt__['saltutil.cmd']( tgt=target,
888 tgt_type=target_type,
889 fun='health_checks.gluster_pool_list',
890 timeout=3,
891 kwargs='[batch=True]'
892 ) or None
893
894 if not _minions_output(out, agent, ignore_dead):
895 __context__['retcode'] = 2
896 return False
897
898 failed_minions = []
899 verified_minions = []
900 for minion in out:
901 verified_minions.append(minion)
902 gluster_json = out[minion]['ret']
903 alive_peers = []
904 for peer in gluster_json:
905 if gluster_json[peer]['state'] == 'Connected':
906 alive_peers.append(peer)
907 else:
908 if minion not in failed_minions:
909 failed_minions.append(minion)
910 if len(alive_peers) < expected_size:
911 if minion not in failed_minions:
912 failed_minions.append(minion)
913
914 if not _failed_minions(out, agent, failed_minions):
915 __context__['retcode'] = 2
916 return False
917
918 if kwargs.get("debug", False):
919 logger.info(verified_minions)
920
921 return True
922
923
924def gluster_volumes_check(target='I@glusterfs:server', target_type='compound', expected_size=3, ignore_volumes=[], ignore_dead=False, **kwargs):
925
926 ''' Check GlusterFS volumes status '''
927
928 agent = "glusterfs volumes status"
929 out = __salt__['saltutil.cmd']( tgt=target,
930 tgt_type=target_type,
931 fun='health_checks.gluster_volume_status',
932 timeout=3,
933 kwargs='[batch=True]'
934 ) or None
935
936 if not _minions_output(out, agent, ignore_dead):
937 __context__['retcode'] = 2
938 return False
939
940 failed_minions = []
941 verified_minions = []
942 verified_volumes = []
943 for minion in out:
944 verified_minions.append(minion)
945 gluster_json = out[minion]['ret']
946 for volume in gluster_json:
947 if volume in ignore_volumes:
948 continue
949 else:
950 verified_volumes.append(volume)
951 alive_bricks = 0
952 if 'bricks' not in gluster_json[volume]:
953 if minion not in failed_minions:
954 failed_minions.append(minion)
955 bricks = gluster_json[volume]['bricks']
956 if len(bricks) < expected_size:
957 if minion not in failed_minions:
958 failed_minions.append(minion)
959 for brick in bricks:
960 if brick['online'] == 'Y':
961 alive_bricks += 1
962 else:
963 if minion not in failed_minions:
964 failed_minions.append(minion)
965 if alive_bricks < expected_size:
966 if minion not in failed_minions:
967 failed_minions.append(minion)
968
969 if not _failed_minions(out, agent, failed_minions):
970 __context__['retcode'] = 2
971 return False
972
973 if kwargs.get("debug", False):
974 logger.info("Verified minions:")
975 logger.info(verified_minions)
976 logger.info("Verified volumes:")
977 logger.info(verified_volumes)
978
979 return True
980
981
982def ceph_cmd(cmd):
983
984 ''' JSON formatted ceph command output '''
985
986 proc = subprocess.Popen(['ceph'] + cmd.split() + ['--format', 'json-pretty'], stdout=subprocess.PIPE)
987 stdout, stderr = proc.communicate()
988
989 return json.loads(stdout)
990
991
992def ceph_health_check(target='I@ceph:mon', target_type='compound', expected_status='HEALTH_OK', expected_state='active+clean', ignore_dead=False, **kwargs):
993
994 ''' Check all ceph monitors health status '''
995
996 agent = "ceph health status"
997 out = __salt__['saltutil.cmd']( tgt=target,
998 tgt_type=target_type,
999 fun='health_checks.ceph_cmd',
1000 arg=['status'],
1001 timeout=3
1002 ) or None
1003
1004 if not _minions_output(out, agent, ignore_dead):
1005 __context__['retcode'] = 2
1006 return False
1007
1008 failed_minions = []
1009 verified_minions = []
1010 for minion in out:
1011 verified_minions.append(minion)
1012 ceph_json = out[minion]['ret']
1013 fsid = ceph_json['fsid']
1014
1015 if ceph_json['health']['overall_status'] != expected_status:
1016 if minion not in failed_minions:
1017 failed_minions.append(minion)
1018
1019 if ceph_json['osdmap']['osdmap']['full']:
1020 if minion not in failed_minions:
1021 failed_minions.append(minion)
1022
1023 if ceph_json['osdmap']['osdmap']['nearfull']:
1024 if minion not in failed_minions:
1025 failed_minions.append(minion)
1026
1027 num_osds = ceph_json['osdmap']['osdmap']['num_osds']
1028 num_in_osds = ceph_json['osdmap']['osdmap']['num_in_osds']
1029 num_up_osds = ceph_json['osdmap']['osdmap']['num_up_osds']
1030 if not ( num_osds == num_in_osds == num_up_osds ):
1031 if minion not in failed_minions:
1032 failed_minions.append(minion)
1033
1034 quorum = len(ceph_json['quorum'])
1035 quorum_names = len(ceph_json['quorum_names'])
1036 mons = len(ceph_json['monmap']['mons'])
1037 if not ( quorum == quorum_names == mons ):
1038 if minion not in failed_minions:
1039 failed_minions.append(minion)
1040
1041 for mon in ceph_json['health']['timechecks']['mons']:
1042 if mon['health'] != expected_status:
1043 if minion not in failed_minions:
1044 failed_minions.append(minion)
1045
1046 for srv in ceph_json['health']['health']['health_services']:
1047 for mon in srv['mons']:
1048 if mon['health'] != expected_status:
1049 if minion not in failed_minions:
1050 failed_minions.append(minion)
1051
1052 for state in ceph_json['pgmap']['pgs_by_state']:
1053 if state['state_name'] != expected_state:
1054 if minion not in failed_minions:
1055 failed_minions.append(minion)
1056
1057 if not _failed_minions(out, agent, failed_minions):
1058 __context__['retcode'] = 2
1059 return False
1060
1061 if kwargs.get("debug", False):
1062 logger.info("Quorum:")
1063 logger.info(ceph_json['quorum_names'])
1064 logger.info("Verified minions:")
1065 logger.info(verified_minions)
1066
1067 return True
1068
1069
Dzmitry Stremkouski7cd10fc2019-04-17 11:51:59 +02001070def get_entropy():
1071
1072 ''' Retrieve entropy size for the host '''
1073
1074 with open('/proc/sys/kernel/random/entropy_avail', 'r') as f:
1075 entropy = f.read()
1076 return entropy
1077
1078
1079def entropy_check(target='*', target_type='glob', minimum_bits=700, ignore_dead=False, **kwargs):
1080
1081 ''' Check entropy size in cluster '''
1082
1083 agent = "entropy size status"
1084 out = __salt__['saltutil.cmd']( tgt=target,
1085 tgt_type=target_type,
1086 fun='health_checks.get_entropy',
1087 timeout=3
1088 ) or None
1089
1090 if not _minions_output(out, agent, ignore_dead):
1091 __context__['retcode'] = 2
1092 return False
1093
1094 failed_minions = []
1095 verified_minions = []
1096
Dzmitry Stremkouski7cd10fc2019-04-17 11:51:59 +02001097 for minion in out:
1098 verified_minions.append(minion)
1099 entropy = int(out[minion]['ret'])
1100 if entropy < minimum_bits:
1101 if not minion in failed_minions:
1102 failed_minions.append(minion)
1103
1104 if not _failed_minions(out, agent, failed_minions):
1105 __context__['retcode'] = 2
1106 return False
1107
1108 if kwargs.get("debug", False):
1109 logger.info(verified_minions)
1110
1111 return True
1112
1113
Dzmitry Stremkouskif1bcbb52019-04-11 15:48:24 +02001114def docker_registry_list(host):
1115
1116 ''' Retrieve and list docker catalog '''
1117
1118 try:
1119 if host[0:4] == 'http':
1120 url = host + '/v2/'
1121 else:
1122 url = 'http://' + host + '/v2/'
1123 repos = requests.get(url + '_catalog')
1124
1125 versions = {}
1126 for repo in repos.json()['repositories']:
1127 repo_versions = requests.get(url + repo + '/tags/list')
1128 versions[repo] = repo_versions.json().pop('tags')
1129 return versions
1130 except:
1131 return {}
Dzmitry Stremkouski7cd10fc2019-04-17 11:51:59 +02001132
1133
1134def docker_ps(list_all=0):
1135
1136 import docker
1137 client = docker.client.Client(base_url='unix://var/run/docker.sock')
1138 return client.containers(all=list_all)
1139
Dzmitry Stremkouski2c709f22019-04-22 02:27:54 +02001140
1141def zookeeper_cmd(cmd, hostname='localhost', port=2181):
1142
1143 ''' Execute zookeeper cmd via socket '''
1144
1145 buf_size = 1024
1146 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1147 sock.connect((hostname, port))
1148 sock.sendall(cmd)
1149 sock.shutdown(socket.SHUT_WR)
1150 rdata = ""
1151 while 1:
1152 data = sock.recv(buf_size)
1153 if data == "":
1154 break
1155 rdata += data
1156 sock.close()
1157 return rdata
1158
1159
1160def zookeeper_stats():
1161
1162 ''' Retrieve zookeeper stats '''
1163
1164 stats = {}
1165 stats['configuration'] = {}
1166 for line in zookeeper_cmd('conf').split('\n'):
1167 if line:
1168 key, value = line.split('=')
1169 if value.strip().isdigit():
1170 value = int(value)
1171 else:
1172 value = value.strip()
1173 stats['configuration'][key.strip().lower().replace(' ', '_')] = value
1174
1175 stats['environment'] = {}
1176 for line in zookeeper_cmd('envi').split('\n')[1:]:
1177 if line:
1178 key, value = line.split('=')
1179 if value.strip().isdigit():
1180 value = int(value)
1181 else:
1182 value = value.strip()
1183 stats['environment'][key.strip().lower().replace(' ', '_')] = value
1184
1185 stats['server'] = {}
1186 for line in zookeeper_cmd('srvr').split('\n'):
1187 if line:
1188 if re.findall('^Zookeeper version:', line, flags=re.IGNORECASE):
1189 version_str = line.split(':')[1].strip()
1190 version = version_str
1191 if '-' in version_str:
1192 version_str = version_str.split('-')[0]
1193 if '.' in version_str:
1194 version = []
1195 version_list = version_str.split('.')
1196 for elem in version_list:
1197 if elem.strip().isdigit():
1198 version.append(int(elem))
1199 stats['server']['version'] = version
1200 continue
1201 if re.findall('^Latency min/avg/max:', line, flags=re.IGNORECASE):
1202 latency_min, latency_avg, latency_max = line.split(':')[1].strip().split('/')
1203 stats['server']['latency'] = {'min':int(latency_min),'max':int(latency_max),'avg':int(latency_avg)}
1204 continue
1205 key, value = line.split(':')
1206 if value.strip().isdigit():
1207 value = int(value)
1208 else:
1209 value = value.strip()
1210 stats['server'][key.strip().lower().replace(' ', '_')] = value
1211
1212 stats['clients'] = {}
1213 for line in zookeeper_cmd('cons').split('\n'):
1214 if line:
1215 clients = re.findall('^(\s*\/)(.+)(:\d+\[\d+\])(\(.+\))$', line)[0][1:]
1216 addr = clients[0]
1217 port, direction = re.findall('^(\d+)\[(\d+)\]$', clients[1][1:])[0]
1218 client = '['+addr+']:'+str(port)
1219 stats['clients'][client] = {'direction': int(direction)}
1220 for elem in clients[2][1:-1].split(','):
1221 key, value = elem.split('=')
1222 if value.strip().isdigit():
1223 value = int(value)
1224 else:
1225 value = value.strip()
1226 stats['clients'][client][key.strip().lower().replace(' ', '_')] = value
1227
1228 return stats
1229
1230
1231def get_zookeeper_leader(target='I@opencontrail:control', target_type='compound', ignore_dead=False, **kwargs):
1232
1233 ''' Retrieve zookeeper leader '''
1234
1235 agent = "zookeeper leader retrieve"
1236 out = __salt__['saltutil.cmd']( tgt=target,
1237 tgt_type=target_type,
1238 fun='health_checks.zookeeper_stats',
1239 timeout=3
1240 ) or None
1241
1242 if not _minions_output(out, agent, ignore_dead):
1243 __context__['retcode'] = 2
1244 return False
1245
1246 leader = None
1247 for minion in out:
1248 zookeeper_mode = out[minion]['ret']['server']['mode']
1249
1250 if zookeeper_mode == 'leader':
1251 leader = minion
1252
1253 return leader
1254
1255
1256def contrail_vrouter_list(api_host='127.0.0.1', api_port=9100):
1257
1258 ''' Retrieve and list contrail vrouters.
1259 Valid targets: Contrail controllers.
1260 '''
1261
1262 try:
1263 if api_host[0:4] == 'http':
1264 url = api_host + ':' + str(api_port)
1265 else:
1266 url = 'http://' + api_host + ':' + str(api_port)
1267
1268 vrouters = requests.get(url + '/virtual-routers').json()
1269 vrouter_list = []
1270 for vr in vrouters['virtual-routers']:
1271 vr_uuid = vr['uuid']
1272 for name in vr['fq_name']:
1273 if name == "default-global-system-config":
1274 continue
1275 else:
1276 vr_name = name
1277 vrouter_list.append({'name': vr_name, 'uuid': vr_uuid})
1278 return vrouter_list
1279
1280 except:
1281 return {}
1282
1283
1284def contrail_vrouter_show(vr_uuid, api_host='127.0.0.1', api_port=9100):
1285
1286 ''' Retrieve contrail vrouter data
1287 Valid targets: Contrail controllers.
1288 '''
1289
1290 try:
1291 if api_host[0:4] == 'http':
1292 url = api_host + ':' + str(api_port)
1293 else:
1294 url = 'http://' + api_host + ':' + str(api_port)
1295
1296 return requests.get(url + '/virtual-router/' + vr_uuid).json()
1297
1298 except:
1299 return {}
1300
1301
1302def _xmletree_descend_child(given_child, tag_requested):
1303
1304 ''' Returns xmletree subelement by tag name '''
1305
1306 my_child = {}
1307
1308 for child in given_child:
1309 if child.tag == tag_requested:
1310 my_child = child
1311 break
1312
1313 return my_child
1314
1315
1316def contrail_vrouter_agent_status(api_host='127.0.0.1', api_port=8085):
1317
1318 ''' Retrieve contrail vrouter agent status '''
1319
1320 import xml.etree.ElementTree as ET
1321
1322 if api_host[0:4] == 'http':
1323 url = api_host + ':' + str(api_port)
1324 else:
1325 url = 'http://' + api_host + ':' + str(api_port)
1326
1327 try:
1328 req = requests.get(url + '/Snh_SandeshUVECacheReq?x=NodeStatus')
1329 if int(req.status_code) != 200:
1330 return "Could not fetch data from vrouter agent via %s.\nGot bad status code: %s\n%s" % (url, str(req.status_code), str(req.text))
1331 except:
1332 pass
1333
1334 try:
1335 xmletree = ET.fromstring(req.text)
1336 except:
1337 return "Could not parse xml tree %s" % str(req.text)
1338
1339 try:
1340 vrouter_data = {}
1341 child = _xmletree_descend_child(xmletree, 'NodeStatusUVE')
1342 child = _xmletree_descend_child(child, 'data')
1343 child = _xmletree_descend_child(child, 'NodeStatus')
1344 child = _xmletree_descend_child(child, 'process_status')
1345 child = _xmletree_descend_child(child, 'list')
1346 child = _xmletree_descend_child(child, 'ProcessStatus')
1347 vrouter_data['state'] = _xmletree_descend_child(child, 'state').text
1348 vrouter_data['connections'] = []
1349 child = _xmletree_descend_child(child, 'connection_infos')
1350 for elem in _xmletree_descend_child(child, 'list'):
1351 conn = {}
1352 conn['type'] = _xmletree_descend_child(elem,'type').text
1353 conn['name'] = _xmletree_descend_child(elem,'name').text
1354 conn['status'] = _xmletree_descend_child(elem,'status').text
1355 conn['description'] = _xmletree_descend_child(elem,'description').text
1356 conn['server_addrs'] = []
1357 server_addrs = _xmletree_descend_child(elem,'server_addrs')
1358 for srv in _xmletree_descend_child(server_addrs,'list'):
1359 host, port = srv.text.split(':')
1360 conn['server_addrs'].append({'host': host, 'port': port})
1361 vrouter_data['connections'].append(conn)
1362 return vrouter_data
1363 except:
1364 return "Unsupported xml tree for this function %s" % str(req.text)
1365
1366
Dzmitry Stremkouski36290202019-05-05 21:26:25 +02001367def contrail_collector_agent_status(vr_name, api_host='auto', api_port=9081):
1368
1369 ''' Retrieve contrail vrouter agent status from analyticsdb '''
1370
1371 if api_host[0:4] == 'http':
1372 url = api_host + ':' + str(api_port)
1373 elif api_host == 'auto':
1374 my_ip = __salt__['pillar.get']('_param:opencontrail_analytics_address')
1375 url = 'http://' + my_ip+ ':' + str(api_port)
1376 else:
1377 url = 'http://' + api_host + ':' + str(api_port)
1378
1379 req = requests.get(url + '/analytics/uves/vrouter/' + vr_name + '?flat')
1380 if int(req.status_code) != 200:
1381 return "Could not fetch data from vrouter agent via %s.\nGot bad status code: %s\n%s" % (url, str(req.status_code), str(req.text))
1382
1383 return json.loads(req.text)
1384
1385
Dzmitry Stremkouskia78a04d2019-07-13 11:05:03 +02001386def contrail_control_peers_summary(api_host='auto', api_port=8083):
1387
1388 ''' Retrieve contrail control peers summary '''
1389
1390 import xml.etree.ElementTree as ET
1391
1392 if api_host[0:4] == 'http':
1393 url = api_host + ':' + str(api_port)
1394 elif api_host == 'auto':
1395 my_ip = '127.0.0.1'
1396 url = 'http://' + my_ip+ ':' + str(api_port)
1397 else:
1398 url = 'http://' + api_host + ':' + str(api_port)
1399
1400 req = requests.get(url + '/Snh_ShowBgpNeighborSummaryReq')
1401 if int(req.status_code) != 200:
1402 return "Could not fetch data from contrail control via %s.\nGot bad status code: %s\n%s" % (url, str(req.status_code), str(req.text))
1403
1404 peers = []
1405 summary = req.text
1406
1407 try:
1408 xmletree = ET.fromstring(summary)
1409 for elem in xmletree.find('.//list'):
1410 attrs = {}
1411 for child in elem:
1412 attrs[child.tag] = child.text
1413 peers.append(attrs)
1414 except:
1415 return "Could not parse xml tree %s" % str(summary)
1416
1417 return peers
1418
1419
Dzmitry Stremkouski88275d32019-07-23 19:42:42 +02001420def contrail_control_peer_status(api_host='auto', api_port=8083, fields=default_peer_filter):
1421
1422 ''' Contrail control peer status '''
1423
1424 peer_status = {}
1425
1426 for peer_elem in contrail_control_peers_summary():
1427 elem = {}
1428 for attr in peer_elem:
1429 if attr in fields:
1430 elem[attr] = peer_elem[attr]
1431
1432 peer_name = peer_elem["peer"]
1433 peer_status[peer_name] = elem
1434
1435 return peer_status
1436
1437
Dzmitry Stremkouski36290202019-05-05 21:26:25 +02001438def _get_object(json_obj, obj_path):
1439
1440 ''' Retrieve subelemet of an JSON object or value '''
1441
1442 if ':' in obj_path:
1443 splitter = obj_path.split(':')
1444 k = splitter[0]
1445 v = ':'.join(splitter[1:])
1446 if k.isdigit():
1447 # Return specific element path
1448 return [ _get_object(json_obj[int(k)], v) ]
1449 elif k == '*':
1450 l = []
1451 for el in json_obj:
1452 l.append(_get_object(el, v))
1453 # Return all list elements from the path
1454 return l
1455 else:
1456 # Contrail output may have nested JSON
1457 if isinstance(json_obj, str) or isinstance(json_obj, unicode):
1458 json_obj = json.loads(json_obj)
1459 # Assume list. Return it
1460 return { k: _get_object(json_obj[k], v) }
1461 else:
1462 return { obj_path: json_obj[obj_path] }
1463
1464
1465def _deepmerge(o1, o2):
1466
1467 ''' Deep merge JSON objects '''
1468
1469 o3 = {}
1470 if type(o1) == type(o2):
1471 if type(o1) == dict or type(o1) == tuple:
1472 for k in set(o1.keys() + o2.keys()):
1473 if k in o1:
1474 if k in o2:
1475 o3[k] = _deepmerge(o1[k], o2[k])
1476 else:
1477 o3[k] = o1[k]
1478 else:
1479 o3[k] = o2[k]
1480 elif type(o1) == list or type(o1) == set:
1481 o3 = [] + o2
1482 for el in o3:
1483 i = o3.index(el)
1484 o3[i] = _deepmerge(o1[i], o2[i])
1485 else:
1486 o3 = o2
1487 else:
1488 o3 = o2
1489
1490 return o3
1491
1492
1493def contrail_vrouter_agent_info(vr_name, filter_map=default_vrouter_info_map):
1494
1495 ''' Retrieve filtered contrail vrouter agent info from analyticsdb '''
1496
1497 vr_agent_status = contrail_collector_agent_status(vr_name)
1498 vr_info = {}
1499 for conf in filter_map:
1500 vr_info[conf] = {}
1501 for el_path in filter_map[conf]:
1502 vr_info = _deepmerge(vr_info, { conf: _get_object(vr_agent_status[conf], el_path) } )
1503
1504 return vr_info
1505
1506
Dzmitry Stremkouskia78a04d2019-07-13 11:05:03 +02001507def kafka_brokers_ids():
1508
1509 ''' Retrieve kafka brokers ids '''
1510
1511 brokers_ids = []
1512 for line in zookeeper_cmd('dump').split('\n'):
1513 if line:
1514 if '/brokers/ids/' in line:
1515 brokers_ids.append(int(line.split('/')[3]))
1516
1517 return brokers_ids
1518
1519
Dzmitry Stremkouski2c709f22019-04-22 02:27:54 +02001520def libvirt_capabilities():
1521
1522 ''' JSON formatted libvirtcapabilities list '''
1523
1524 import xml.etree.ElementTree as ET
1525
1526 try:
1527 proc = subprocess.Popen(['virsh', 'capabilities'], stdout=subprocess.PIPE)
1528 stdout, stderr = proc.communicate()
1529 xmletree = ET.fromstring(stdout)
1530 except:
1531 return "Could not parse xml tree %s" % str(stdout)
1532
1533 try:
1534 capabilities = {}
1535 for elem in xmletree:
1536 if elem.tag == "guest":
1537 for el in elem:
1538 if el.tag == 'arch':
1539 _name = el.attrib['name']
1540 capabilities[_name] = []
1541 for arch in el:
1542 if arch.tag == 'machine':
1543 if 'canonical' not in arch.attrib:
1544 capabilities[_name].append(arch.text)
1545
1546 return capabilities
1547 except:
1548 return "Unsupported xml tree for this function %s" % str(stdout)
1549
Dzmitry Stremkouskia78a04d2019-07-13 11:05:03 +02001550
Dzmitry Stremkouski88275d32019-07-23 19:42:42 +02001551def keystone_keys_attractor(keys_dir='/var/lib/keystone/fernet-keys', keys_ids=range(0,-4,-1)):
Dzmitry Stremkouskia78a04d2019-07-13 11:05:03 +02001552
Dzmitry Stremkouski88275d32019-07-23 19:42:42 +02001553 ''' JSON formatted dict of keystone keys sha256 sums '''
Dzmitry Stremkouskia78a04d2019-07-13 11:05:03 +02001554
Dzmitry Stremkouski88275d32019-07-23 19:42:42 +02001555 keys = os.listdir(keys_dir)
1556 keys.sort()
1557 keys_dict = {}
1558 try:
1559 for i in keys_ids:
1560 with open("%s/%s" % (keys_dir, str(keys[i])), 'r') as key_file:
1561 _iter1 = hashlib.sha256(key_file.read()).hexdigest()
1562 _iter2 = hashlib.sha256(_iter1).hexdigest()
1563 _iter3 = hashlib.sha256(_iter2).hexdigest()
1564 keys_dict[str(keys[i])] = _iter3
1565 except:
1566 pass
Dzmitry Stremkouskia78a04d2019-07-13 11:05:03 +02001567
Dzmitry Stremkouski88275d32019-07-23 19:42:42 +02001568 return keys_dict
Dzmitry Stremkouskia78a04d2019-07-13 11:05:03 +02001569
Dzmitry Stremkouskia78a04d2019-07-13 11:05:03 +02001570
Dzmitry Stremkouski88275d32019-07-23 19:42:42 +02001571def keystone_keys_check(target='I@keystone:server', target_type='compound', ignore_dead=False, **kwargs):
1572
1573 ''' Check cluster keystone keys are in sync '''
1574
1575 keys_type = kwargs.get("keys_type", 'fernet')
1576
1577 supported_key_types = ['fernet', 'credential']
1578 if keys_type not in supported_key_types:
1579 logger.error("Unsupported keys type: %s" % str(keys_type))
1580 logger.error("Supported keys type are: %s" % str(supported_key_types))
1581 __context__['retcode'] = 2
1582 return False
1583
1584 agent = "keystone %s keys sync" % keys_type
1585 keys_dir_default = '/var/lib/keystone/%s-keys' % keys_type
1586 keys_dir = kwargs.get("keys_dir", keys_dir_default)
1587
1588 out = __salt__['saltutil.cmd']( tgt=target,
1589 tgt_type=target_type,
1590 fun='health_checks.keystone_keys_attractor',
1591 arg=["keys_dir='%s'" % keys_dir],
1592 timeout=3
1593 ) or None
1594
1595 if not _minions_output(out, agent, ignore_dead):
1596 __context__['retcode'] = 2
1597 return False
1598
1599 cluster_attractors = []
1600 failed_minions = []
1601 verified_minions = []
1602 attractor = {}
1603
1604 for minion in out:
1605 verified_minions.append(minion)
1606 attractor = out[minion]['ret']
1607 if attractor == {}:
1608 failed_minions.append(minion)
1609 if attractor not in cluster_attractors:
1610 cluster_attractors.append(attractor)
1611
1612 if not _failed_minions(out, agent, failed_minions):
1613 __context__['retcode'] = 2
1614 return False
1615
1616 if len(cluster_attractors) > 1:
1617 failed_minions = []
1618 for minion in out:
1619 failed_minions.append(minion)
1620
1621 if not _failed_minions(out, agent, failed_minions):
1622 __context__['retcode'] = 2
1623 return False
1624
1625 if kwargs.get("debug", False):
1626 logger.info("%s check done." % agent)
1627 logger.info(verified_minions)
1628
1629 return True
1630