blob: 8af20db5786051bc3f733d43e39e9cffe30622ef [file] [log] [blame]
Alex0989ecf2022-03-29 13:43:21 -05001# Author: Alex Savatieiev (osavatieiev@mirantis.com; a.savex@gmail.com)
2# Copyright 2019-2022 Mirantis, Inc.
savex4448e132018-04-25 15:51:14 +02003import abc
4import os
Alex1839bbf2019-08-22 17:17:21 -05005import re
Alex41485522019-04-12 17:26:18 -05006import time
savex4448e132018-04-25 15:51:14 +02007
Alex Savatieiev5118de02019-02-20 15:50:42 -06008from cfg_checker.common import const
Alex3ebc5632019-04-18 16:47:18 -05009from cfg_checker.common import logger_cli
Alex1839bbf2019-08-22 17:17:21 -050010from cfg_checker.common.file_utils import read_file_as_lines
Alexb2129542021-11-23 15:49:42 -060011from cfg_checker.modules.ceph.bench import _reformat_timestr
Alex3ebc5632019-04-18 16:47:18 -050012
13import jinja2
14
15import six
savex4448e132018-04-25 15:51:14 +020016
17pkg_dir = os.path.dirname(__file__)
Alex Savatieiev6d010be2019-03-11 10:36:59 -050018pkg_dir = os.path.join(pkg_dir, os.pardir, os.pardir)
savex4448e132018-04-25 15:51:14 +020019pkg_dir = os.path.normpath(pkg_dir)
20
Alex836fac82019-08-22 13:36:16 -050021# % threshhold values
22_disk_warn = 80
23_disk_critical = 90
24_ram_warn = 5
25_ram_critical = 3
Alex1839bbf2019-08-22 17:17:21 -050026_softnet_interval = 5
27
28UP = const.NODE_UP
29DOWN = const.NODE_DOWN
Alexe9908f72020-05-19 16:04:53 -050030SKIP = const.NODE_SKIP
Alex836fac82019-08-22 13:36:16 -050031
savex4448e132018-04-25 15:51:14 +020032
savex4448e132018-04-25 15:51:14 +020033def line_breaks(text):
34 # replace python linebreaks with html breaks
35 return text.replace("\n", "<br />")
36
37
Alexdcb792f2021-10-04 14:24:21 -050038def tabstops(text):
39 # replace python linebreaks with html breaks
40 return text.replace("\t", "&#9;")
41
42
Alex41485522019-04-12 17:26:18 -050043def get_sorted_keys(td):
44 # detect if we can sort by desc
45 # Yes, this is slow, but bullet-proof from empty desc
46 _desc = all([bool(td[k]['desc']) for k in td.keys()])
47 # Get sorted list
48 if not _desc:
49 return sorted(td.keys())
50 else:
51 return sorted(
52 td.keys(),
53 key=lambda k: (
Alexd0391d42019-05-21 18:48:55 -050054 td[k]['desc']['section'],
Alex41485522019-04-12 17:26:18 -050055 td[k]['desc']['app'],
56 k
57 )
58 )
59
60
61def get_max(_list):
62 return sorted(_list)[-1]
63
64
Alex836fac82019-08-22 13:36:16 -050065def make_pkg_action_label(act):
Alex41485522019-04-12 17:26:18 -050066 _act_labels = {
67 const.ACT_UPGRADE: "Upgrade possible",
68 const.ACT_NEED_UP: "Needs upgrade",
69 const.ACT_NEED_DOWN: "Needs downgrade",
Alex9e4bfaf2019-06-11 15:21:59 -050070 const.ACT_REPO: "Repo update",
Alex41485522019-04-12 17:26:18 -050071 const.ACT_NA: ""
72 }
73 return _act_labels[act]
74
75
Alex836fac82019-08-22 13:36:16 -050076def make_pkg_action_class(act):
Alex41485522019-04-12 17:26:18 -050077 _act_classes = {
78 const.ACT_UPGRADE: "possible",
79 const.ACT_NEED_UP: "needs_up",
80 const.ACT_NEED_DOWN: "needs_down",
81 const.ACT_REPO: "needs_repo",
82 const.ACT_NA: ""
83 }
84 return _act_classes[act]
85
86
Alex836fac82019-08-22 13:36:16 -050087def make_pkg_status_label(sts):
Alex41485522019-04-12 17:26:18 -050088 _status_labels = {
89 const.VERSION_OK: "OK",
90 const.VERSION_UP: "Upgraded",
91 const.VERSION_DOWN: "Downgraded",
Alex26b8a8c2019-10-09 17:09:07 -050092 const.VERSION_WARN: "WARNING",
Alex41485522019-04-12 17:26:18 -050093 const.VERSION_ERR: "ERROR",
94 const.VERSION_NA: "N/A"
95 }
96 return _status_labels[sts]
97
98
Alex836fac82019-08-22 13:36:16 -050099def make_pkg_status_class(sts):
100 return const.all_pkg_statuses[sts]
101
102
103def make_node_status(sts):
104 return const.node_status[sts]
Alex41485522019-04-12 17:26:18 -0500105
106
Alexd0391d42019-05-21 18:48:55 -0500107def make_repo_info(repos):
108 _text = ""
109 for r in repos:
110 # tag
111 _text += r['tag'] + ": "
112 # repo header
113 _text += " ".join([
114 r['subset'],
115 r['release'],
116 r['ubuntu-release'],
117 r['type'],
118 r['arch']
119 ]) + ", "
120 # maintainer w/o email
Alex3bc95f62020-03-05 17:00:04 -0600121 _text += ascii(r['maintainer'][:r['maintainer'].find('<')-1])
Alexd0391d42019-05-21 18:48:55 -0500122 # newline
123 _text += "<br />"
124 return _text
125
126
Alexdcb792f2021-10-04 14:24:21 -0500127def to_gb(bytes_str):
Alex30380a42021-12-20 16:11:20 -0600128 return "{}".format(round(int(bytes_str) / 1024 / 1024 / 1024, 2))
Alexdcb792f2021-10-04 14:24:21 -0500129
130
131def to_mb(bytes_str):
Alex30380a42021-12-20 16:11:20 -0600132 return "{}".format(round(int(bytes_str) / 1024 / 1024, 2))
133
134
135def to_kb(bytes_str):
136 return "{}".format(round(int(bytes_str) / 1024, 2))
Alexdcb792f2021-10-04 14:24:21 -0500137
138
139def get_bucket_item_name(id, cmap):
140 for buck in cmap["buckets"]:
141 if id == buck["id"]:
142 return buck["name"]
143 for dev in cmap["devices"]:
144 if id == dev["id"]:
145 return dev["name"]
146 return id
147
148
149def get_rule_steps(steps):
150 _steps = []
151 for step in steps:
152 _ops = step.pop("op").split('_')
153 if "take" in _ops:
154 _steps.append(
155 "step {} {}".format(
156 " ".join(_ops),
157 step["item_name"]
158 )
159 )
160 else:
161 _steps.append(
162 "step {} {}".format(
163 " ".join(_ops),
164 " ".join(["{} {}".format(k, v) for k, v in step.items()])
165 )
166 )
167 return _steps
168
169
Alexb2129542021-11-23 15:49:42 -0600170def time_strip(timestring):
171 return _reformat_timestr(timestring, _tchar="")
172
173
Alexdcb792f2021-10-04 14:24:21 -0500174def get_osdmap(cs):
175 _osdmap = cs
176 while True:
177 _keys = list(_osdmap.keys())
178 for _k in _keys:
179 if _k == "osdmap":
180 _osdmap = _osdmap[_k]
181 break
182 elif _k == 'epoch':
183 return _osdmap
184 return {
185 "epoch": 0,
186 "num_osds": 0,
187 "num_up_osds": 0,
188 "osd_up_since": 0,
189 "num_in_osds": 0,
190 "osd_in_since": 0,
191 "num_remapped_pgs": 0
192 }
193
194
Alexb2129542021-11-23 15:49:42 -0600195def get_pool_stats_by_id(id, pgdump):
Alexdcb792f2021-10-04 14:24:21 -0500196 _stats = {}
197 for pool in pgdump["pg_map"]["pool_stats"]:
198 if id == pool["poolid"]:
199 _stats = pool
200 return _stats
201
202
savex4448e132018-04-25 15:51:14 +0200203@six.add_metaclass(abc.ABCMeta)
204class _Base(object):
Alex1f90e7b2021-09-03 15:31:28 -0500205 def __init__(self, master=None):
savex4448e132018-04-25 15:51:14 +0200206 self.jinja2_env = self.init_jinja2_env()
Alex1f90e7b2021-09-03 15:31:28 -0500207 self.master = master
savex4448e132018-04-25 15:51:14 +0200208
209 @abc.abstractmethod
210 def __call__(self, payload):
211 pass
212
213 @staticmethod
214 def init_jinja2_env():
215 return jinja2.Environment(
216 loader=jinja2.FileSystemLoader(os.path.join(pkg_dir, 'templates')),
217 trim_blocks=True,
218 lstrip_blocks=True)
219
220
221class _TMPLBase(_Base):
222 @abc.abstractproperty
223 def tmpl(self):
224 pass
225
226 @staticmethod
227 def _count_totals(data):
228 data['counters']['total_nodes'] = len(data['nodes'])
229
Alex Savatieiev36b938d2019-01-21 11:01:18 +0100230 def __call__(self, payload):
savex4448e132018-04-25 15:51:14 +0200231 # init data structures
232 data = self.common_data()
Alex41485522019-04-12 17:26:18 -0500233 # payload should have pre-sorted structure according to report called
234 # nodes, openstack_release, mcp_release, etc...
235 data.update(payload)
savex4448e132018-04-25 15:51:14 +0200236
237 # add template specific data
238 self._extend_data(data)
239
240 # do counts global
241 self._count_totals(data)
242
243 # specific filters
savex4448e132018-04-25 15:51:14 +0200244 self.jinja2_env.filters['linebreaks'] = line_breaks
Alex41485522019-04-12 17:26:18 -0500245 self.jinja2_env.filters['get_max'] = get_max
246
247 self.jinja2_env.filters['get_sorted_keys'] = get_sorted_keys
Alex836fac82019-08-22 13:36:16 -0500248 self.jinja2_env.filters['pkg_status_label'] = make_pkg_status_label
249 self.jinja2_env.filters['pkg_status_class'] = make_pkg_status_class
250 self.jinja2_env.filters['pkg_action_label'] = make_pkg_action_label
251 self.jinja2_env.filters['pkg_action_class'] = make_pkg_action_class
252 self.jinja2_env.filters['node_status_class'] = make_node_status
Alexc6566d82019-09-23 16:07:17 -0500253 self.jinja2_env.filters['pkg_repo_info'] = make_repo_info
Alexdcb792f2021-10-04 14:24:21 -0500254 self.jinja2_env.filters['to_gb'] = to_gb
255 self.jinja2_env.filters['to_mb'] = to_mb
Alex30380a42021-12-20 16:11:20 -0600256 self.jinja2_env.filters['to_kb'] = to_kb
Alexdcb792f2021-10-04 14:24:21 -0500257 self.jinja2_env.filters['get_bucket_item_name'] = get_bucket_item_name
258 self.jinja2_env.filters['get_rule_steps'] = get_rule_steps
Alexb2129542021-11-23 15:49:42 -0600259 self.jinja2_env.filters['get_pool_stats'] = get_pool_stats_by_id
Alexdcb792f2021-10-04 14:24:21 -0500260 self.jinja2_env.filters['get_osdmap'] = get_osdmap
Alexb2129542021-11-23 15:49:42 -0600261 self.jinja2_env.filters['tstrip'] = time_strip
savex4448e132018-04-25 15:51:14 +0200262
263 # render!
Alex41485522019-04-12 17:26:18 -0500264 logger_cli.info("-> Using template: {}".format(self.tmpl))
savex4448e132018-04-25 15:51:14 +0200265 tmpl = self.jinja2_env.get_template(self.tmpl)
Alex41485522019-04-12 17:26:18 -0500266 logger_cli.info("-> Rendering")
savex4448e132018-04-25 15:51:14 +0200267 return tmpl.render(data)
268
269 def common_data(self):
270 return {
271 'counters': {},
Alex41485522019-04-12 17:26:18 -0500272 'salt_info': {},
273 'gen_date': time.strftime("%m/%d/%Y %H:%M:%S")
savex4448e132018-04-25 15:51:14 +0200274 }
275
276 def _extend_data(self, data):
277 pass
278
279
Alex41485522019-04-12 17:26:18 -0500280# HTML Package versions report
281class CSVAllPackages(_TMPLBase):
282 tmpl = "pkg_versions_csv.j2"
283
284
285# HTML Package versions report
savexce010ba2018-04-27 09:49:23 +0200286class HTMLPackageCandidates(_TMPLBase):
Alex41485522019-04-12 17:26:18 -0500287 tmpl = "pkg_versions_html.j2"
savex4448e132018-04-25 15:51:14 +0200288
289
Alexdcb792f2021-10-04 14:24:21 -0500290# HTML Ceph information report
291class HTMLCephInfo(_TMPLBase):
292 tmpl = "ceph_info_html.j2"
293
294
Alexb2129542021-11-23 15:49:42 -0600295class HTMLCephBench(_TMPLBase):
296 tmpl = "ceph_bench_html.j2"
297
298
Alex Savatieievd48994d2018-12-13 12:13:00 +0100299# Package versions report
300class HTMLModelCompare(_TMPLBase):
301 tmpl = "model_tree_cmp_tmpl.j2"
302
303 def _extend_data(self, data):
Alex Savatieiev36b938d2019-01-21 11:01:18 +0100304 # move names into separate place
Alexb8af13a2019-04-16 18:38:12 -0500305 data["names"] = data["diffs"].pop("diff_names")
306 data["tabs"] = data.pop("diffs")
Alex3ebc5632019-04-18 16:47:18 -0500307
Alex Savatieievd48994d2018-12-13 12:13:00 +0100308 # counters - mdl_diff
Alex Savatieiev4f149d02019-02-28 17:15:29 -0600309 for _tab in data["tabs"].keys():
310 data['counters'][_tab] = len(data["tabs"][_tab]["diffs"].keys())
Alex Savatieievd48994d2018-12-13 12:13:00 +0100311
312
Alex Savatieiev9b2f6512019-02-20 18:05:00 -0600313class HTMLNetworkReport(_TMPLBase):
314 tmpl = "network_check_tmpl.j2"
315
Alex836fac82019-08-22 13:36:16 -0500316 def _extend_data(self, data):
317 def get_bytes(value):
Alexb78191f2021-11-02 16:35:46 -0500318 _size_i = True if value[-1] == 'i' else False
319 _char = value[-2] if _size_i else value[-1]
Alex3bc95f62020-03-05 17:00:04 -0600320 _ord = ord(_char)
321 if _ord > 47 and _ord < 58:
322 # bytes comes with no Char
Alex836fac82019-08-22 13:36:16 -0500323 return int(value)
Alex3bc95f62020-03-05 17:00:04 -0600324 else:
325 _sizes = ["*", "K", "M", "G", "T"]
Alexb78191f2021-11-02 16:35:46 -0500326 _flo = float(value[:-2]) if _size_i else float(value[:-1])
Alex3bc95f62020-03-05 17:00:04 -0600327 _pwr = 1
328 if _char in _sizes:
329 _pwr = _sizes.index(_char)
Alexb78191f2021-11-02 16:35:46 -0500330 return int(1024**_pwr*_flo)
Alex836fac82019-08-22 13:36:16 -0500331
Alex2e213b22019-12-05 10:40:29 -0600332 def _dmidecode(_dict, type=0):
Alex3bc95f62020-03-05 17:00:04 -0600333 # _key = "dmi"
Alex2e213b22019-12-05 10:40:29 -0600334 _key_r = "dmi_r"
Alex1f90e7b2021-09-03 15:31:28 -0500335 _f_cmd = self.master.get_cmd_for_nodes
Alex2e213b22019-12-05 10:40:29 -0600336 _cmd = "dmidecode -t {}".format(type)
337 _f_cmd(_cmd, _key_r, target_dict=_dict)
338 # TODO: parse BIOS output or given type
339 pass
340
341 def _lsblk(_dict):
Alex3bc95f62020-03-05 17:00:04 -0600342 # _key = "lsblk"
Alex2e213b22019-12-05 10:40:29 -0600343 _key_r = "lsblk_raw"
Alex1f90e7b2021-09-03 15:31:28 -0500344 _f_cmd = self.master.get_cmd_for_nodes
Alex2e213b22019-12-05 10:40:29 -0600345 _columns = [
346 "NAME",
347 "HCTL",
348 "TYPE",
349 "SIZE",
350 "VENDOR",
351 "MODEL",
352 "SERIAL",
353 "REV",
354 "TRAN"
355 ]
356 _cmd = "lsblk -S --output {}".format(",".join(_columns))
357 _f_cmd(_cmd, _key_r, target_dict=_dict)
358 # TODO: parse lsblk output
359 pass
360
Alex1839bbf2019-08-22 17:17:21 -0500361 def _lscpu(_dict):
362 _key = "lscpu"
363 _key_r = "lscpu_raw"
364 # get all of the values
Alex1f90e7b2021-09-03 15:31:28 -0500365 _f_cmd = self.master.get_cmd_for_nodes
Alex1839bbf2019-08-22 17:17:21 -0500366 _cmd = "lscpu | sed -n '/\\:/s/ \\+/ /gp'"
367 _f_cmd(_cmd, _key_r, target_dict=_dict)
368 # parse them and put into dict
Alex3bc95f62020-03-05 17:00:04 -0600369 for node, dt in _dict.items():
Alex1839bbf2019-08-22 17:17:21 -0500370 dt[_key] = {}
Alexe9908f72020-05-19 16:04:53 -0500371 if dt['status'] == DOWN or dt['status'] == SKIP:
Alexe65ff4e2019-10-11 14:45:09 -0500372 continue
373 if not dt[_key_r]:
374 # no stats collected, put negatives
375 dt.pop(_key_r)
Alex1839bbf2019-08-22 17:17:21 -0500376 continue
377 lines = dt[_key_r].splitlines()
378 for line in lines:
379 li = line.split(':')
380 _var_name = li[0].lower()
381 _var_name = re.sub(' ', '_', _var_name)
382 _var_name = re.sub('|'.join(['\\(', '\\)']), '', _var_name)
383 _var_value = li[1].strip()
384 dt[_key][_var_name] = _var_value
385 dt.pop(_key_r)
386 # detect virtual nodes
387 if "hypervisor_vendor" in dt[_key]:
388 dt['node_type'] = "virtual"
389 else:
390 dt['node_type'] = "physical"
Alex836fac82019-08-22 13:36:16 -0500391
Alex1839bbf2019-08-22 17:17:21 -0500392 def _free(_dict):
393 _key = "ram"
394 _key_r = "ram_raw"
Alex1f90e7b2021-09-03 15:31:28 -0500395 _f_cmd = self.master.get_cmd_for_nodes
Alexb78191f2021-11-02 16:35:46 -0500396 _cmd = "free -h | grep 'Mem' | sed -n '/Mem/s/ \\+/ /gp'"
Alex1839bbf2019-08-22 17:17:21 -0500397 _f_cmd(_cmd, _key_r, target_dict=_dict)
398 # parse them and put into dict
Alex3bc95f62020-03-05 17:00:04 -0600399 for node, dt in _dict.items():
Alex1839bbf2019-08-22 17:17:21 -0500400 dt[_key] = {}
Alexe9908f72020-05-19 16:04:53 -0500401 if dt['status'] == DOWN or dt['status'] == SKIP:
Alexe65ff4e2019-10-11 14:45:09 -0500402 continue
403 if not dt[_key_r]:
404 # no stats collected, put negatives
405 dt.pop(_key_r)
Alex1839bbf2019-08-22 17:17:21 -0500406 continue
407 li = dt[_key_r].split()
408 dt[_key]['total'] = li[1]
409 dt[_key]['used'] = li[2]
410 dt[_key]['free'] = li[3]
411 dt[_key]['shared'] = li[4]
412 dt[_key]['cache'] = li[5]
413 dt[_key]['available'] = li[6]
414
415 _total = get_bytes(li[1])
416 _avail = get_bytes(li[6])
417 _m = _avail * 100.0 / _total
418 if _m < _ram_critical:
419 dt[_key]["status"] = "fail"
420 elif _m < _ram_warn:
421 dt[_key]["status"] = "warn"
422 else:
423 dt[_key]["status"] = ""
Alex836fac82019-08-22 13:36:16 -0500424
425 def _services(_dict):
426 _key = "services"
427 _key_r = "services_raw"
Alex1f90e7b2021-09-03 15:31:28 -0500428 _f_cmd = self.master.get_cmd_for_nodes
Alex836fac82019-08-22 13:36:16 -0500429 _cmd = "service --status-all"
430 _f_cmd(_cmd, _key_r, target_dict=_dict)
Alex3bc95f62020-03-05 17:00:04 -0600431 for node, dt in _dict.items():
Alex836fac82019-08-22 13:36:16 -0500432 dt[_key] = {}
Alexe9908f72020-05-19 16:04:53 -0500433 if dt['status'] == DOWN or dt['status'] == SKIP:
Alexe65ff4e2019-10-11 14:45:09 -0500434 continue
435 if not dt[_key_r]:
436 # no stats collected, put negatives
437 dt.pop(_key_r)
Alex1839bbf2019-08-22 17:17:21 -0500438 continue
Alex836fac82019-08-22 13:36:16 -0500439 lines = dt[_key_r].splitlines()
440 for line in lines:
441 li = line.split()
442 _status = li[1]
443 _name = li[3]
444 if _status == '-':
445 dt[_key][_name] = False
446 elif _status == '+':
447 dt[_key][_name] = True
448 else:
449 dt[_key][_name] = None
450 dt.pop(_key_r)
451
Alex1839bbf2019-08-22 17:17:21 -0500452 def _vcp_status(_dict):
453 _key = "virsh"
454 _key_r = "virsh_raw"
Alex1f90e7b2021-09-03 15:31:28 -0500455 self.master.get_cmd_for_nodes(
Alex1839bbf2019-08-22 17:17:21 -0500456 "virsh list --all | sed -n -e '/[0-9]/s/ \\+/ /gp'",
457 _key_r,
458 target_dict=_dict,
459 nodes="kvm*"
460 )
461 _kvm = filter(lambda x: x.find("kvm") >= 0, _dict.keys())
462 for node in _kvm:
463 dt = _dict[node]
464 dt[_key] = {}
Alexe9908f72020-05-19 16:04:53 -0500465 if dt['status'] == DOWN or dt['status'] == SKIP:
Alexe65ff4e2019-10-11 14:45:09 -0500466 continue
467 if not dt[_key_r]:
468 # no stats collected, put negatives
469 dt.pop(_key_r)
Alex1839bbf2019-08-22 17:17:21 -0500470 continue
471 lines = dt[_key_r].splitlines()
472 for line in lines:
473 li = line.split()
474 _id = li[0]
475 _name = li[1]
476 _status = li[2]
477 dt[_key][_name] = {
478 'id': _id,
479 'status': _status
480 }
481 dt.pop(_key_r)
482
483 # query per-cpu and count totals
484 # total (0), dropped(1), squeezed (2), collision (7)
485 def _soft_net_stats(_dict):
486 _key = "net_stats"
487 _key_r = "net_stats_raw"
Alex1f90e7b2021-09-03 15:31:28 -0500488 _f_cmd = self.master.get_cmd_for_nodes
Alex1839bbf2019-08-22 17:17:21 -0500489 _cmd = "cat /proc/net/softnet_stat; echo \\#; " \
490 "sleep {}; cat /proc/net/softnet_stat".format(
491 _softnet_interval
492 )
493 _f_cmd(_cmd, _key_r, target_dict=_dict)
Alex3bc95f62020-03-05 17:00:04 -0600494 for node, dt in _dict.items():
Alex1839bbf2019-08-22 17:17:21 -0500495 _cpuindex = 1
496 _add_mode = True
Alex1839bbf2019-08-22 17:17:21 -0500497 # totals for start mark
498 _ts = [0, 0, 0, 0]
499 # skip if node is down
Alexe9908f72020-05-19 16:04:53 -0500500 if dt['status'] == DOWN or dt['status'] == SKIP:
Alexc96fdd32019-10-15 12:48:59 -0500501 dt[_key] = {
502 "total": [-1, -1, -1, -1]
503 }
Alex1839bbf2019-08-22 17:17:21 -0500504 continue
Alexf3dbe862019-10-07 15:17:04 -0500505 if not dt[_key_r]:
506 # no stats collected, put negatives
507 dt.pop(_key_r)
508 dt[_key] = {
509 "total": [-1, -1, -1, -1]
510 }
511 continue
512 # final totals
513 dt[_key] = {
514 "total": [0, 0, 0, 0]
515 }
Alex1839bbf2019-08-22 17:17:21 -0500516 lines = dt[_key_r].splitlines()
517 for line in lines:
518 if line.startswith("#"):
519 _add_mode = False
520 _cpuindex = 1
521 continue
522 li = line.split()
523 _c = [
524 int(li[0], 16),
525 int(li[1], 16),
526 int(li[2], 16),
527 int(li[7], 16)
528 ]
529 _id = "cpu{:02}".format(_cpuindex)
530 if _id not in dt[_key]:
531 dt[_key][_id] = []
532 _dc = dt[_key][_id]
533 if _add_mode:
534 # saving values and adding totals
535 dt[_key][_id] = _c
536 # save start totals
537 _ts = [_ts[i]+_c[i] for i in range(0, len(_c))]
538 else:
539 # this is second measurement
540 # subtract all values
541 for i in range(len(_c)):
542 dt[_key][_id][i] = _c[i] - _dc[i]
543 dt[_key]["total"][i] += _c[i]
544 _cpuindex += 1
545 # finally, subtract initial totals
Alex3bc95f62020-03-05 17:00:04 -0600546 for k, v in dt[_key].items():
Alex1839bbf2019-08-22 17:17:21 -0500547 if k != "total":
548 dt[_key][k] = [v[i] / 5. for i in range(len(v))]
549 else:
550 dt[_key][k] = [(v[i]-_ts[i])/5. for i in range(len(v))]
551 dt.pop(_key_r)
552
553 # prepare yellow and red marker values
Alex836fac82019-08-22 13:36:16 -0500554 data["const"] = {
Alex1839bbf2019-08-22 17:17:21 -0500555 "net_interval": _softnet_interval,
Alex836fac82019-08-22 13:36:16 -0500556 "ram_warn": _ram_warn,
557 "ram_critical": _ram_critical,
558 "disk_warn": _disk_warn,
Alex1839bbf2019-08-22 17:17:21 -0500559 "disk_critical": _disk_critical,
560 "services": read_file_as_lines(
561 os.path.join(
562 pkg_dir,
563 'etc',
564 'services.list'
565 )
566 )
Alex836fac82019-08-22 13:36:16 -0500567 }
568
569 # get kernel version
Alex1f90e7b2021-09-03 15:31:28 -0500570 self.master.get_cmd_for_nodes(
Alex836fac82019-08-22 13:36:16 -0500571 "uname -r",
572 "kernel",
573 target_dict=data["nodes"]
574 )
Alex1839bbf2019-08-22 17:17:21 -0500575 # process lscpu data
576 _lscpu(data["nodes"])
Alex836fac82019-08-22 13:36:16 -0500577
578 # free ram
579 # sample: 16425392 14883144 220196
Alex1839bbf2019-08-22 17:17:21 -0500580 _free(data["nodes"])
Alex836fac82019-08-22 13:36:16 -0500581
582 # disk space
583 # sample: /dev/vda1 78G 33G 45G 43%
Alexe65ff4e2019-10-11 14:45:09 -0500584 _key = "disk"
585 _key_r = "disk_raw"
Alex1f90e7b2021-09-03 15:31:28 -0500586 self.master.get_cmd_for_nodes(
Alex836fac82019-08-22 13:36:16 -0500587 "df -h | sed -n '/^\\/dev/s/ \\+/ /gp' | cut -d\" \" -f 1-5",
588 "disk_raw",
589 target_dict=data["nodes"]
590 )
Alex3bc95f62020-03-05 17:00:04 -0600591 for dt in data["nodes"].values():
Alexc96fdd32019-10-15 12:48:59 -0500592 dt["disk"] = {}
593 dt["disk_max_dev"] = None
Alexe65ff4e2019-10-11 14:45:09 -0500594 if dt['status'] == DOWN:
Alexc96fdd32019-10-15 12:48:59 -0500595 dt["disk"]["unknown"] = {}
596 dt["disk_max_dev"] = "unknown"
Alexe65ff4e2019-10-11 14:45:09 -0500597 continue
Alexe9908f72020-05-19 16:04:53 -0500598 if dt['status'] == SKIP:
599 dt["disk"]["skipped"] = {}
600 dt["disk_max_dev"] = "skipped"
601 continue
Alexe65ff4e2019-10-11 14:45:09 -0500602 if not dt[_key_r]:
603 # no stats collected, put negatives
604 dt.pop(_key_r)
605 dt[_key] = {}
606 continue
Alex836fac82019-08-22 13:36:16 -0500607 # show first device row by default
Alexe65ff4e2019-10-11 14:45:09 -0500608 _d = dt["disk"]
609 _r = dt["disk_raw"]
Alex836fac82019-08-22 13:36:16 -0500610 _r = _r.splitlines()
611 _max = -1
612 for idx in range(0, len(_r)):
613 _t = _r[idx].split()
614 _d[_t[0]] = {}
615 _d[_t[0]]['v'] = _t[1:]
616 _chk = int(_t[-1].split('%')[0])
617 if _chk > _max:
Alexe65ff4e2019-10-11 14:45:09 -0500618 dt["disk_max_dev"] = _t[0]
Alex836fac82019-08-22 13:36:16 -0500619 _max = _chk
620 if _chk > _disk_critical:
621 _d[_t[0]]['f'] = "fail"
622 elif _chk > _disk_warn:
623 _d[_t[0]]['f'] = "warn"
624 else:
625 _d[_t[0]]['f'] = ""
626
627 # prepare networks data for report
Alex3bc95f62020-03-05 17:00:04 -0600628 for net, net_v in data['map'].items():
629 for node, ifs in net_v.items():
Alex836fac82019-08-22 13:36:16 -0500630 for d in ifs:
Alex1f90e7b2021-09-03 15:31:28 -0500631 _e = "fail"
632 d['interface_error'] = _e if 'interface_error' in d else ""
633 d['mtu_error'] = _e if 'mtu_error' in d else ""
634 d['status_error'] = _e if 'status_error' in d else ""
Alex836fac82019-08-22 13:36:16 -0500635 d['subnet_gateway_error'] = \
Alex1f90e7b2021-09-03 15:31:28 -0500636 _e if 'subnet_gateway_error' in d else ""
Alex836fac82019-08-22 13:36:16 -0500637
638 _services(data["nodes"])
Alex1839bbf2019-08-22 17:17:21 -0500639 # vcp status
640 # query virsh and prepare for report
641 _vcp_status(data["nodes"])
642
643 # soft net stats
644 _soft_net_stats(data["nodes"])
Alex836fac82019-08-22 13:36:16 -0500645
Alex Savatieiev9b2f6512019-02-20 18:05:00 -0600646
savex4448e132018-04-25 15:51:14 +0200647class ReportToFile(object):
648 def __init__(self, report, target):
649 self.report = report
650 self.target = target
651
652 def __call__(self, payload):
653 payload = self.report(payload)
654
655 if isinstance(self.target, six.string_types):
656 self._wrapped_dump(payload)
657 else:
658 self._dump(payload, self.target)
659
660 def _wrapped_dump(self, payload):
661 with open(self.target, 'wt') as target:
662 self._dump(payload, target)
663
664 @staticmethod
665 def _dump(payload, target):
666 target.write(payload)