blob: e0a746ecdd6d898dd52afc4fbbc36b62ca8763fc [file] [log] [blame]
savex4448e132018-04-25 15:51:14 +02001import abc
2import os
Alex1839bbf2019-08-22 17:17:21 -05003import re
Alex41485522019-04-12 17:26:18 -05004import time
savex4448e132018-04-25 15:51:14 +02005
Alex Savatieiev5118de02019-02-20 15:50:42 -06006from cfg_checker.common import const
Alex3ebc5632019-04-18 16:47:18 -05007from cfg_checker.common import logger_cli
Alex1839bbf2019-08-22 17:17:21 -05008from cfg_checker.common.file_utils import read_file_as_lines
Alex836fac82019-08-22 13:36:16 -05009from cfg_checker.nodes import salt_master
Alex3ebc5632019-04-18 16:47:18 -050010
11import jinja2
12
13import six
savex4448e132018-04-25 15:51:14 +020014
15pkg_dir = os.path.dirname(__file__)
Alex Savatieiev6d010be2019-03-11 10:36:59 -050016pkg_dir = os.path.join(pkg_dir, os.pardir, os.pardir)
savex4448e132018-04-25 15:51:14 +020017pkg_dir = os.path.normpath(pkg_dir)
18
Alex836fac82019-08-22 13:36:16 -050019# % threshhold values
20_disk_warn = 80
21_disk_critical = 90
22_ram_warn = 5
23_ram_critical = 3
Alex1839bbf2019-08-22 17:17:21 -050024_softnet_interval = 5
25
26UP = const.NODE_UP
27DOWN = const.NODE_DOWN
Alexe9908f72020-05-19 16:04:53 -050028SKIP = const.NODE_SKIP
Alex836fac82019-08-22 13:36:16 -050029
savex4448e132018-04-25 15:51:14 +020030
savex4448e132018-04-25 15:51:14 +020031def line_breaks(text):
32 # replace python linebreaks with html breaks
33 return text.replace("\n", "<br />")
34
35
Alex41485522019-04-12 17:26:18 -050036def get_sorted_keys(td):
37 # detect if we can sort by desc
38 # Yes, this is slow, but bullet-proof from empty desc
39 _desc = all([bool(td[k]['desc']) for k in td.keys()])
40 # Get sorted list
41 if not _desc:
42 return sorted(td.keys())
43 else:
44 return sorted(
45 td.keys(),
46 key=lambda k: (
Alexd0391d42019-05-21 18:48:55 -050047 td[k]['desc']['section'],
Alex41485522019-04-12 17:26:18 -050048 td[k]['desc']['app'],
49 k
50 )
51 )
52
53
54def get_max(_list):
55 return sorted(_list)[-1]
56
57
Alex836fac82019-08-22 13:36:16 -050058def make_pkg_action_label(act):
Alex41485522019-04-12 17:26:18 -050059 _act_labels = {
60 const.ACT_UPGRADE: "Upgrade possible",
61 const.ACT_NEED_UP: "Needs upgrade",
62 const.ACT_NEED_DOWN: "Needs downgrade",
Alex9e4bfaf2019-06-11 15:21:59 -050063 const.ACT_REPO: "Repo update",
Alex41485522019-04-12 17:26:18 -050064 const.ACT_NA: ""
65 }
66 return _act_labels[act]
67
68
Alex836fac82019-08-22 13:36:16 -050069def make_pkg_action_class(act):
Alex41485522019-04-12 17:26:18 -050070 _act_classes = {
71 const.ACT_UPGRADE: "possible",
72 const.ACT_NEED_UP: "needs_up",
73 const.ACT_NEED_DOWN: "needs_down",
74 const.ACT_REPO: "needs_repo",
75 const.ACT_NA: ""
76 }
77 return _act_classes[act]
78
79
Alex836fac82019-08-22 13:36:16 -050080def make_pkg_status_label(sts):
Alex41485522019-04-12 17:26:18 -050081 _status_labels = {
82 const.VERSION_OK: "OK",
83 const.VERSION_UP: "Upgraded",
84 const.VERSION_DOWN: "Downgraded",
Alex26b8a8c2019-10-09 17:09:07 -050085 const.VERSION_WARN: "WARNING",
Alex41485522019-04-12 17:26:18 -050086 const.VERSION_ERR: "ERROR",
87 const.VERSION_NA: "N/A"
88 }
89 return _status_labels[sts]
90
91
Alex836fac82019-08-22 13:36:16 -050092def make_pkg_status_class(sts):
93 return const.all_pkg_statuses[sts]
94
95
96def make_node_status(sts):
97 return const.node_status[sts]
Alex41485522019-04-12 17:26:18 -050098
99
Alexd0391d42019-05-21 18:48:55 -0500100def make_repo_info(repos):
101 _text = ""
102 for r in repos:
103 # tag
104 _text += r['tag'] + ": "
105 # repo header
106 _text += " ".join([
107 r['subset'],
108 r['release'],
109 r['ubuntu-release'],
110 r['type'],
111 r['arch']
112 ]) + ", "
113 # maintainer w/o email
Alex3bc95f62020-03-05 17:00:04 -0600114 _text += ascii(r['maintainer'][:r['maintainer'].find('<')-1])
Alexd0391d42019-05-21 18:48:55 -0500115 # newline
116 _text += "<br />"
117 return _text
118
119
savex4448e132018-04-25 15:51:14 +0200120@six.add_metaclass(abc.ABCMeta)
121class _Base(object):
122 def __init__(self):
123 self.jinja2_env = self.init_jinja2_env()
124
125 @abc.abstractmethod
126 def __call__(self, payload):
127 pass
128
129 @staticmethod
130 def init_jinja2_env():
131 return jinja2.Environment(
132 loader=jinja2.FileSystemLoader(os.path.join(pkg_dir, 'templates')),
133 trim_blocks=True,
134 lstrip_blocks=True)
135
136
137class _TMPLBase(_Base):
138 @abc.abstractproperty
139 def tmpl(self):
140 pass
141
142 @staticmethod
143 def _count_totals(data):
144 data['counters']['total_nodes'] = len(data['nodes'])
145
Alex Savatieiev36b938d2019-01-21 11:01:18 +0100146 def __call__(self, payload):
savex4448e132018-04-25 15:51:14 +0200147 # init data structures
148 data = self.common_data()
Alex41485522019-04-12 17:26:18 -0500149 # payload should have pre-sorted structure according to report called
150 # nodes, openstack_release, mcp_release, etc...
151 data.update(payload)
savex4448e132018-04-25 15:51:14 +0200152
153 # add template specific data
154 self._extend_data(data)
155
156 # do counts global
157 self._count_totals(data)
158
159 # specific filters
savex4448e132018-04-25 15:51:14 +0200160 self.jinja2_env.filters['linebreaks'] = line_breaks
Alex41485522019-04-12 17:26:18 -0500161 self.jinja2_env.filters['get_max'] = get_max
162
163 self.jinja2_env.filters['get_sorted_keys'] = get_sorted_keys
Alex836fac82019-08-22 13:36:16 -0500164 self.jinja2_env.filters['pkg_status_label'] = make_pkg_status_label
165 self.jinja2_env.filters['pkg_status_class'] = make_pkg_status_class
166 self.jinja2_env.filters['pkg_action_label'] = make_pkg_action_label
167 self.jinja2_env.filters['pkg_action_class'] = make_pkg_action_class
168 self.jinja2_env.filters['node_status_class'] = make_node_status
Alexc6566d82019-09-23 16:07:17 -0500169 self.jinja2_env.filters['pkg_repo_info'] = make_repo_info
savex4448e132018-04-25 15:51:14 +0200170
171 # render!
Alex41485522019-04-12 17:26:18 -0500172 logger_cli.info("-> Using template: {}".format(self.tmpl))
savex4448e132018-04-25 15:51:14 +0200173 tmpl = self.jinja2_env.get_template(self.tmpl)
Alex41485522019-04-12 17:26:18 -0500174 logger_cli.info("-> Rendering")
savex4448e132018-04-25 15:51:14 +0200175 return tmpl.render(data)
176
177 def common_data(self):
178 return {
179 'counters': {},
Alex41485522019-04-12 17:26:18 -0500180 'salt_info': {},
181 'gen_date': time.strftime("%m/%d/%Y %H:%M:%S")
savex4448e132018-04-25 15:51:14 +0200182 }
183
184 def _extend_data(self, data):
185 pass
186
187
Alex41485522019-04-12 17:26:18 -0500188# HTML Package versions report
189class CSVAllPackages(_TMPLBase):
190 tmpl = "pkg_versions_csv.j2"
191
192
193# HTML Package versions report
savexce010ba2018-04-27 09:49:23 +0200194class HTMLPackageCandidates(_TMPLBase):
Alex41485522019-04-12 17:26:18 -0500195 tmpl = "pkg_versions_html.j2"
savex4448e132018-04-25 15:51:14 +0200196
197
Alex Savatieievd48994d2018-12-13 12:13:00 +0100198# Package versions report
199class HTMLModelCompare(_TMPLBase):
200 tmpl = "model_tree_cmp_tmpl.j2"
201
202 def _extend_data(self, data):
Alex Savatieiev36b938d2019-01-21 11:01:18 +0100203 # move names into separate place
Alexb8af13a2019-04-16 18:38:12 -0500204 data["names"] = data["diffs"].pop("diff_names")
205 data["tabs"] = data.pop("diffs")
Alex3ebc5632019-04-18 16:47:18 -0500206
Alex Savatieievd48994d2018-12-13 12:13:00 +0100207 # counters - mdl_diff
Alex Savatieiev4f149d02019-02-28 17:15:29 -0600208 for _tab in data["tabs"].keys():
209 data['counters'][_tab] = len(data["tabs"][_tab]["diffs"].keys())
Alex Savatieievd48994d2018-12-13 12:13:00 +0100210
211
Alex Savatieiev9b2f6512019-02-20 18:05:00 -0600212class HTMLNetworkReport(_TMPLBase):
213 tmpl = "network_check_tmpl.j2"
214
Alex836fac82019-08-22 13:36:16 -0500215 def _extend_data(self, data):
216 def get_bytes(value):
Alex3bc95f62020-03-05 17:00:04 -0600217 _char = value[-1]
218 _ord = ord(_char)
219 if _ord > 47 and _ord < 58:
220 # bytes comes with no Char
Alex836fac82019-08-22 13:36:16 -0500221 return int(value)
Alex3bc95f62020-03-05 17:00:04 -0600222 else:
223 _sizes = ["*", "K", "M", "G", "T"]
224 _flo = float(value[:-1])
225 _pwr = 1
226 if _char in _sizes:
227 _pwr = _sizes.index(_char)
228 return int(_flo**_pwr)
Alex836fac82019-08-22 13:36:16 -0500229
Alex2e213b22019-12-05 10:40:29 -0600230 def _dmidecode(_dict, type=0):
Alex3bc95f62020-03-05 17:00:04 -0600231 # _key = "dmi"
Alex2e213b22019-12-05 10:40:29 -0600232 _key_r = "dmi_r"
233 _f_cmd = salt_master.get_cmd_for_nodes
234 _cmd = "dmidecode -t {}".format(type)
235 _f_cmd(_cmd, _key_r, target_dict=_dict)
236 # TODO: parse BIOS output or given type
237 pass
238
239 def _lsblk(_dict):
Alex3bc95f62020-03-05 17:00:04 -0600240 # _key = "lsblk"
Alex2e213b22019-12-05 10:40:29 -0600241 _key_r = "lsblk_raw"
242 _f_cmd = salt_master.get_cmd_for_nodes
243 _columns = [
244 "NAME",
245 "HCTL",
246 "TYPE",
247 "SIZE",
248 "VENDOR",
249 "MODEL",
250 "SERIAL",
251 "REV",
252 "TRAN"
253 ]
254 _cmd = "lsblk -S --output {}".format(",".join(_columns))
255 _f_cmd(_cmd, _key_r, target_dict=_dict)
256 # TODO: parse lsblk output
257 pass
258
Alex1839bbf2019-08-22 17:17:21 -0500259 def _lscpu(_dict):
260 _key = "lscpu"
261 _key_r = "lscpu_raw"
262 # get all of the values
Alex836fac82019-08-22 13:36:16 -0500263 _f_cmd = salt_master.get_cmd_for_nodes
Alex1839bbf2019-08-22 17:17:21 -0500264 _cmd = "lscpu | sed -n '/\\:/s/ \\+/ /gp'"
265 _f_cmd(_cmd, _key_r, target_dict=_dict)
266 # parse them and put into dict
Alex3bc95f62020-03-05 17:00:04 -0600267 for node, dt in _dict.items():
Alex1839bbf2019-08-22 17:17:21 -0500268 dt[_key] = {}
Alexe9908f72020-05-19 16:04:53 -0500269 if dt['status'] == DOWN or dt['status'] == SKIP:
Alexe65ff4e2019-10-11 14:45:09 -0500270 continue
271 if not dt[_key_r]:
272 # no stats collected, put negatives
273 dt.pop(_key_r)
Alex1839bbf2019-08-22 17:17:21 -0500274 continue
275 lines = dt[_key_r].splitlines()
276 for line in lines:
277 li = line.split(':')
278 _var_name = li[0].lower()
279 _var_name = re.sub(' ', '_', _var_name)
280 _var_name = re.sub('|'.join(['\\(', '\\)']), '', _var_name)
281 _var_value = li[1].strip()
282 dt[_key][_var_name] = _var_value
283 dt.pop(_key_r)
284 # detect virtual nodes
285 if "hypervisor_vendor" in dt[_key]:
286 dt['node_type'] = "virtual"
287 else:
288 dt['node_type'] = "physical"
Alex836fac82019-08-22 13:36:16 -0500289
Alex1839bbf2019-08-22 17:17:21 -0500290 def _free(_dict):
291 _key = "ram"
292 _key_r = "ram_raw"
Alex836fac82019-08-22 13:36:16 -0500293 _f_cmd = salt_master.get_cmd_for_nodes
Alex1839bbf2019-08-22 17:17:21 -0500294 _cmd = "free -h | sed -n '/Mem/s/ \\+/ /gp'"
295 _f_cmd(_cmd, _key_r, target_dict=_dict)
296 # parse them and put into dict
Alex3bc95f62020-03-05 17:00:04 -0600297 for node, dt in _dict.items():
Alex1839bbf2019-08-22 17:17:21 -0500298 dt[_key] = {}
Alexe9908f72020-05-19 16:04:53 -0500299 if dt['status'] == DOWN or dt['status'] == SKIP:
Alexe65ff4e2019-10-11 14:45:09 -0500300 continue
301 if not dt[_key_r]:
302 # no stats collected, put negatives
303 dt.pop(_key_r)
Alex1839bbf2019-08-22 17:17:21 -0500304 continue
305 li = dt[_key_r].split()
306 dt[_key]['total'] = li[1]
307 dt[_key]['used'] = li[2]
308 dt[_key]['free'] = li[3]
309 dt[_key]['shared'] = li[4]
310 dt[_key]['cache'] = li[5]
311 dt[_key]['available'] = li[6]
312
313 _total = get_bytes(li[1])
314 _avail = get_bytes(li[6])
315 _m = _avail * 100.0 / _total
316 if _m < _ram_critical:
317 dt[_key]["status"] = "fail"
318 elif _m < _ram_warn:
319 dt[_key]["status"] = "warn"
320 else:
321 dt[_key]["status"] = ""
Alex836fac82019-08-22 13:36:16 -0500322
323 def _services(_dict):
324 _key = "services"
325 _key_r = "services_raw"
326 _f_cmd = salt_master.get_cmd_for_nodes
327 _cmd = "service --status-all"
328 _f_cmd(_cmd, _key_r, target_dict=_dict)
Alex3bc95f62020-03-05 17:00:04 -0600329 for node, dt in _dict.items():
Alex836fac82019-08-22 13:36:16 -0500330 dt[_key] = {}
Alexe9908f72020-05-19 16:04:53 -0500331 if dt['status'] == DOWN or dt['status'] == SKIP:
Alexe65ff4e2019-10-11 14:45:09 -0500332 continue
333 if not dt[_key_r]:
334 # no stats collected, put negatives
335 dt.pop(_key_r)
Alex1839bbf2019-08-22 17:17:21 -0500336 continue
Alex836fac82019-08-22 13:36:16 -0500337 lines = dt[_key_r].splitlines()
338 for line in lines:
339 li = line.split()
340 _status = li[1]
341 _name = li[3]
342 if _status == '-':
343 dt[_key][_name] = False
344 elif _status == '+':
345 dt[_key][_name] = True
346 else:
347 dt[_key][_name] = None
348 dt.pop(_key_r)
349
Alex1839bbf2019-08-22 17:17:21 -0500350 def _vcp_status(_dict):
351 _key = "virsh"
352 _key_r = "virsh_raw"
353 salt_master.get_cmd_for_nodes(
354 "virsh list --all | sed -n -e '/[0-9]/s/ \\+/ /gp'",
355 _key_r,
356 target_dict=_dict,
357 nodes="kvm*"
358 )
359 _kvm = filter(lambda x: x.find("kvm") >= 0, _dict.keys())
360 for node in _kvm:
361 dt = _dict[node]
362 dt[_key] = {}
Alexe9908f72020-05-19 16:04:53 -0500363 if dt['status'] == DOWN or dt['status'] == SKIP:
Alexe65ff4e2019-10-11 14:45:09 -0500364 continue
365 if not dt[_key_r]:
366 # no stats collected, put negatives
367 dt.pop(_key_r)
Alex1839bbf2019-08-22 17:17:21 -0500368 continue
369 lines = dt[_key_r].splitlines()
370 for line in lines:
371 li = line.split()
372 _id = li[0]
373 _name = li[1]
374 _status = li[2]
375 dt[_key][_name] = {
376 'id': _id,
377 'status': _status
378 }
379 dt.pop(_key_r)
380
381 # query per-cpu and count totals
382 # total (0), dropped(1), squeezed (2), collision (7)
383 def _soft_net_stats(_dict):
384 _key = "net_stats"
385 _key_r = "net_stats_raw"
386 _f_cmd = salt_master.get_cmd_for_nodes
387 _cmd = "cat /proc/net/softnet_stat; echo \\#; " \
388 "sleep {}; cat /proc/net/softnet_stat".format(
389 _softnet_interval
390 )
391 _f_cmd(_cmd, _key_r, target_dict=_dict)
Alex3bc95f62020-03-05 17:00:04 -0600392 for node, dt in _dict.items():
Alex1839bbf2019-08-22 17:17:21 -0500393 _cpuindex = 1
394 _add_mode = True
Alex1839bbf2019-08-22 17:17:21 -0500395 # totals for start mark
396 _ts = [0, 0, 0, 0]
397 # skip if node is down
Alexe9908f72020-05-19 16:04:53 -0500398 if dt['status'] == DOWN or dt['status'] == SKIP:
Alexc96fdd32019-10-15 12:48:59 -0500399 dt[_key] = {
400 "total": [-1, -1, -1, -1]
401 }
Alex1839bbf2019-08-22 17:17:21 -0500402 continue
Alexf3dbe862019-10-07 15:17:04 -0500403 if not dt[_key_r]:
404 # no stats collected, put negatives
405 dt.pop(_key_r)
406 dt[_key] = {
407 "total": [-1, -1, -1, -1]
408 }
409 continue
410 # final totals
411 dt[_key] = {
412 "total": [0, 0, 0, 0]
413 }
Alex1839bbf2019-08-22 17:17:21 -0500414 lines = dt[_key_r].splitlines()
415 for line in lines:
416 if line.startswith("#"):
417 _add_mode = False
418 _cpuindex = 1
419 continue
420 li = line.split()
421 _c = [
422 int(li[0], 16),
423 int(li[1], 16),
424 int(li[2], 16),
425 int(li[7], 16)
426 ]
427 _id = "cpu{:02}".format(_cpuindex)
428 if _id not in dt[_key]:
429 dt[_key][_id] = []
430 _dc = dt[_key][_id]
431 if _add_mode:
432 # saving values and adding totals
433 dt[_key][_id] = _c
434 # save start totals
435 _ts = [_ts[i]+_c[i] for i in range(0, len(_c))]
436 else:
437 # this is second measurement
438 # subtract all values
439 for i in range(len(_c)):
440 dt[_key][_id][i] = _c[i] - _dc[i]
441 dt[_key]["total"][i] += _c[i]
442 _cpuindex += 1
443 # finally, subtract initial totals
Alex3bc95f62020-03-05 17:00:04 -0600444 for k, v in dt[_key].items():
Alex1839bbf2019-08-22 17:17:21 -0500445 if k != "total":
446 dt[_key][k] = [v[i] / 5. for i in range(len(v))]
447 else:
448 dt[_key][k] = [(v[i]-_ts[i])/5. for i in range(len(v))]
449 dt.pop(_key_r)
450
451 # prepare yellow and red marker values
Alex836fac82019-08-22 13:36:16 -0500452 data["const"] = {
Alex1839bbf2019-08-22 17:17:21 -0500453 "net_interval": _softnet_interval,
Alex836fac82019-08-22 13:36:16 -0500454 "ram_warn": _ram_warn,
455 "ram_critical": _ram_critical,
456 "disk_warn": _disk_warn,
Alex1839bbf2019-08-22 17:17:21 -0500457 "disk_critical": _disk_critical,
458 "services": read_file_as_lines(
459 os.path.join(
460 pkg_dir,
461 'etc',
462 'services.list'
463 )
464 )
Alex836fac82019-08-22 13:36:16 -0500465 }
466
467 # get kernel version
468 salt_master.get_cmd_for_nodes(
469 "uname -r",
470 "kernel",
471 target_dict=data["nodes"]
472 )
Alex1839bbf2019-08-22 17:17:21 -0500473 # process lscpu data
474 _lscpu(data["nodes"])
Alex836fac82019-08-22 13:36:16 -0500475
476 # free ram
477 # sample: 16425392 14883144 220196
Alex1839bbf2019-08-22 17:17:21 -0500478 _free(data["nodes"])
Alex836fac82019-08-22 13:36:16 -0500479
480 # disk space
481 # sample: /dev/vda1 78G 33G 45G 43%
Alexe65ff4e2019-10-11 14:45:09 -0500482 _key = "disk"
483 _key_r = "disk_raw"
Alex836fac82019-08-22 13:36:16 -0500484 salt_master.get_cmd_for_nodes(
485 "df -h | sed -n '/^\\/dev/s/ \\+/ /gp' | cut -d\" \" -f 1-5",
486 "disk_raw",
487 target_dict=data["nodes"]
488 )
Alex3bc95f62020-03-05 17:00:04 -0600489 for dt in data["nodes"].values():
Alexc96fdd32019-10-15 12:48:59 -0500490 dt["disk"] = {}
491 dt["disk_max_dev"] = None
Alexe65ff4e2019-10-11 14:45:09 -0500492 if dt['status'] == DOWN:
Alexc96fdd32019-10-15 12:48:59 -0500493 dt["disk"]["unknown"] = {}
494 dt["disk_max_dev"] = "unknown"
Alexe65ff4e2019-10-11 14:45:09 -0500495 continue
Alexe9908f72020-05-19 16:04:53 -0500496 if dt['status'] == SKIP:
497 dt["disk"]["skipped"] = {}
498 dt["disk_max_dev"] = "skipped"
499 continue
Alexe65ff4e2019-10-11 14:45:09 -0500500 if not dt[_key_r]:
501 # no stats collected, put negatives
502 dt.pop(_key_r)
503 dt[_key] = {}
504 continue
Alex836fac82019-08-22 13:36:16 -0500505 # show first device row by default
Alexe65ff4e2019-10-11 14:45:09 -0500506 _d = dt["disk"]
507 _r = dt["disk_raw"]
Alex836fac82019-08-22 13:36:16 -0500508 _r = _r.splitlines()
509 _max = -1
510 for idx in range(0, len(_r)):
511 _t = _r[idx].split()
512 _d[_t[0]] = {}
513 _d[_t[0]]['v'] = _t[1:]
514 _chk = int(_t[-1].split('%')[0])
515 if _chk > _max:
Alexe65ff4e2019-10-11 14:45:09 -0500516 dt["disk_max_dev"] = _t[0]
Alex836fac82019-08-22 13:36:16 -0500517 _max = _chk
518 if _chk > _disk_critical:
519 _d[_t[0]]['f'] = "fail"
520 elif _chk > _disk_warn:
521 _d[_t[0]]['f'] = "warn"
522 else:
523 _d[_t[0]]['f'] = ""
524
525 # prepare networks data for report
Alex3bc95f62020-03-05 17:00:04 -0600526 for net, net_v in data['map'].items():
527 for node, ifs in net_v.items():
Alex836fac82019-08-22 13:36:16 -0500528 for d in ifs:
529 _err = "fail"
530 d['interface_error'] = _err if d['interface_error'] else ""
531 d['mtu_error'] = _err if d['mtu_error'] else ""
532 d['status_error'] = _err if d['status_error'] else ""
533 d['subnet_gateway_error'] = \
534 _err if d['subnet_gateway_error'] else ""
535
536 _services(data["nodes"])
Alex1839bbf2019-08-22 17:17:21 -0500537 # vcp status
538 # query virsh and prepare for report
539 _vcp_status(data["nodes"])
540
541 # soft net stats
542 _soft_net_stats(data["nodes"])
Alex836fac82019-08-22 13:36:16 -0500543
Alex Savatieiev9b2f6512019-02-20 18:05:00 -0600544
savex4448e132018-04-25 15:51:14 +0200545class ReportToFile(object):
546 def __init__(self, report, target):
547 self.report = report
548 self.target = target
549
550 def __call__(self, payload):
551 payload = self.report(payload)
552
553 if isinstance(self.target, six.string_types):
554 self._wrapped_dump(payload)
555 else:
556 self._dump(payload, self.target)
557
558 def _wrapped_dump(self, payload):
559 with open(self.target, 'wt') as target:
560 self._dump(payload, target)
561
562 @staticmethod
563 def _dump(payload, target):
564 target.write(payload)