blob: c37ecee6681fa6a6a2596c4087662c7753f502a6 [file] [log] [blame]
savex4448e132018-04-25 15:51:14 +02001import json
savex4448e132018-04-25 15:51:14 +02002
Alex3ebc5632019-04-18 16:47:18 -05003from cfg_checker.common import const, logger_cli
Alex41485522019-04-12 17:26:18 -05004from cfg_checker.common.exception import ConfigException
Alex41485522019-04-12 17:26:18 -05005from cfg_checker.helpers.console_utils import Progress
Alexd0391d42019-05-21 18:48:55 -05006from cfg_checker.modules.packages.repos import RepoManager
Alexe0c5b9e2019-04-23 18:51:23 -05007from cfg_checker.nodes import salt_master
Alex41485522019-04-12 17:26:18 -05008from cfg_checker.reports import reporter
9
Alex3ebc5632019-04-18 16:47:18 -050010from versions import DebianVersion, PkgVersions, VersionCmpResult
savex4448e132018-04-25 15:51:14 +020011
12
Alexe0c5b9e2019-04-23 18:51:23 -050013class CloudPackageChecker(object):
Alexd0391d42019-05-21 18:48:55 -050014 def __init__(self):
15 # Init salt master info
16 if not salt_master.nodes:
17 salt_master.nodes = salt_master.get_nodes()
18
19 # check that this env tag is present in Manager
Alex571bf152019-05-31 16:28:30 -050020 self.rm = RepoManager()
Alexd0391d42019-05-21 18:48:55 -050021 _tags = self.rm.get_available_tags(tag=salt_master.mcp_release)
22 if not _tags:
23 logger_cli.warning(
24 "\n# hWARNING: '{0}' is not listed in repo index. "
25 "Consider running:\n\t{1}\nto add info on this tag's "
26 "release package versions".format(
27 salt_master.mcp_release,
28 "mcp-checker packages versions --tag {0}"
29 )
30 )
31
Alex41485522019-04-12 17:26:18 -050032 @staticmethod
33 def presort_packages(all_packages, full=None):
34 logger_cli.info("-> Presorting packages")
35 # labels
36 _data = {}
37 _data = {
38 "cs": {
39 "ok": const.VERSION_OK,
40 "up": const.VERSION_UP,
41 "down": const.VERSION_DOWN,
42 "err": const.VERSION_ERR
43 },
44 "ca": {
45 "na": const.ACT_NA,
46 "up": const.ACT_UPGRADE,
47 "need_up": const.ACT_NEED_UP,
48 "need_down": const.ACT_NEED_DOWN,
49 "repo": const.ACT_REPO
50 }
51 }
52 _data['status_err'] = const.VERSION_ERR
53 _data['status_down'] = const.VERSION_DOWN
54
55 # Presort packages
56 _data['critical'] = {}
57 _data['system'] = {}
58 _data['other'] = {}
59 _data['unlisted'] = {}
60
61 _l = len(all_packages)
62 _progress = Progress(_l)
63 _progress_index = 0
64 # counters
65 _ec = _es = _eo = _eu = 0
66 _dc = _ds = _do = _du = 0
67 while _progress_index < _l:
68 # progress bar
69 _progress_index += 1
70 _progress.write_progress(_progress_index)
71 # sort packages
72 _pn, _val = all_packages.popitem()
Alexd0391d42019-05-21 18:48:55 -050073 _c = _val['desc']['section']
74
Alexe0c5b9e2019-04-23 18:51:23 -050075 if not full:
Alex41485522019-04-12 17:26:18 -050076 # Check if this packet has errors
77 # if all is ok -> just skip it
78 _max_status = max(_val['results'].keys())
79 if _max_status <= const.VERSION_OK:
80 _max_action = max(_val['results'][_max_status].keys())
81 if _max_action == const.ACT_NA:
Alexd0391d42019-05-21 18:48:55 -050082 # this package does not have any comments
Alex41485522019-04-12 17:26:18 -050083 # ...just skip it from report
84 continue
85
Alexd0391d42019-05-21 18:48:55 -050086 if len(_c) > 0 and _val['is_mirantis'] is None:
Alex41485522019-04-12 17:26:18 -050087 # not listed package in version lib
88 _data['unlisted'].update({
89 _pn: _val
90 })
91 _eu += _val['results'].keys().count(const.VERSION_ERR)
92 _du += _val['results'].keys().count(const.VERSION_DOWN)
93 # mirantis/critical
Alexd0391d42019-05-21 18:48:55 -050094 # elif len(_c) > 0 and _c != 'System':
95 elif _val['is_mirantis']:
Alex41485522019-04-12 17:26:18 -050096 # not blank and not system
97 _data['critical'].update({
98 _pn: _val
99 })
100 _ec += _val['results'].keys().count(const.VERSION_ERR)
101 _dc += _val['results'].keys().count(const.VERSION_DOWN)
102 # system
103 elif _c == 'System':
104 _data['system'].update({
105 _pn: _val
106 })
107 _es += _val['results'].keys().count(const.VERSION_ERR)
108 _ds += _val['results'].keys().count(const.VERSION_DOWN)
109 # rest
110 else:
111 _data['other'].update({
112 _pn: _val
113 })
114 _eo += _val['results'].keys().count(const.VERSION_ERR)
115 _do += _val['results'].keys().count(const.VERSION_DOWN)
116
Alexd9fd85e2019-05-16 16:58:24 -0500117 _progress.end()
Alex41485522019-04-12 17:26:18 -0500118
119 _data['errors'] = {
120 'mirantis': _ec,
121 'system': _es,
122 'other': _eo,
123 'unlisted': _eu
124 }
125 _data['downgrades'] = {
126 'mirantis': _dc,
127 'system': _ds,
128 'other': _do,
129 'unlisted': _du
130 }
131
132 return _data
133
savex4448e132018-04-25 15:51:14 +0200134 def collect_installed_packages(self):
135 """
136 Collect installed packages on each node
137 sets 'installed' dict property in the class
138
139 :return: none
140 """
Alex Savatieiev42b89fa2019-03-07 18:45:26 -0600141 logger_cli.info("# Collecting installed packages")
Alexe0c5b9e2019-04-23 18:51:23 -0500142 salt_master.prepare_script_on_active_nodes("pkg_versions.py")
143 _result = salt_master.execute_script_on_active_nodes("pkg_versions.py")
savex4448e132018-04-25 15:51:14 +0200144
Alexe0c5b9e2019-04-23 18:51:23 -0500145 for key in salt_master.nodes.keys():
savex4448e132018-04-25 15:51:14 +0200146 # due to much data to be passed from salt, it is happening in order
147 if key in _result:
148 _text = _result[key]
Alex Savatieievfa5910a2019-03-18 18:12:24 -0500149 try:
150 _dict = json.loads(_text[_text.find('{'):])
Alex3ebc5632019-04-18 16:47:18 -0500151 except ValueError:
Alex Savatieievfa5910a2019-03-18 18:12:24 -0500152 logger_cli.info("... no JSON for '{}'".format(
153 key
154 ))
Alex3ebc5632019-04-18 16:47:18 -0500155 logger_cli.debug(
156 "ERROR:\n{}\n".format(_text[:_text.find('{')])
157 )
Alex Savatieievfa5910a2019-03-18 18:12:24 -0500158 _dict = {}
Alex3ebc5632019-04-18 16:47:18 -0500159
Alexe0c5b9e2019-04-23 18:51:23 -0500160 salt_master.nodes[key]['packages'] = _dict
savex4448e132018-04-25 15:51:14 +0200161 else:
Alexe0c5b9e2019-04-23 18:51:23 -0500162 salt_master.nodes[key]['packages'] = {}
Alex Savatieiev42b89fa2019-03-07 18:45:26 -0600163 logger_cli.debug("... {} has {} packages installed".format(
savex4448e132018-04-25 15:51:14 +0200164 key,
Alexe0c5b9e2019-04-23 18:51:23 -0500165 len(salt_master.nodes[key]['packages'].keys())
savex4448e132018-04-25 15:51:14 +0200166 ))
Alex Savatieiev799bee32019-02-20 17:19:26 -0600167 logger_cli.info("-> Done")
savex4448e132018-04-25 15:51:14 +0200168
169 def collect_packages(self):
170 """
171 Check package versions in repos vs installed
172
173 :return: no return values, all date put to dict in place
174 """
Alex41485522019-04-12 17:26:18 -0500175 # Preload OpenStack release versions
176 _desc = PkgVersions()
Alex3ebc5632019-04-18 16:47:18 -0500177
178 logger_cli.info(
179 "# Cross-comparing: Installed vs Candidates vs Release"
180 )
Alexd0391d42019-05-21 18:48:55 -0500181 # shortcuts for this cloud values
182 _os = salt_master.openstack_release
183 _mcp = salt_master.mcp_release
184 # Progress class
Alexe0c5b9e2019-04-23 18:51:23 -0500185 _progress = Progress(len(salt_master.nodes.keys()))
Alex41485522019-04-12 17:26:18 -0500186 _progress_index = 0
187 _total_processed = 0
Alex Savatieiev3db12a72019-03-22 16:32:31 -0500188 # Collect packages from all of the nodes in flat dict
Alex41485522019-04-12 17:26:18 -0500189 _all_packages = {}
Alexe0c5b9e2019-04-23 18:51:23 -0500190 for node_name, node_value in salt_master.nodes.iteritems():
Alex41485522019-04-12 17:26:18 -0500191 _uniq_len = len(_all_packages.keys())
192 _progress_index += 1
Alexd0391d42019-05-21 18:48:55 -0500193 # progress updates shown before next node only
194 # it is costly operation to do it for each of the 150k packages
Alex41485522019-04-12 17:26:18 -0500195 _progress.write_progress(
196 _progress_index,
197 note="/ {} uniq out of {} packages found".format(
198 _uniq_len,
199 _total_processed
200 )
201 )
Alex Savatieiev3db12a72019-03-22 16:32:31 -0500202 for _name, _value in node_value['packages'].iteritems():
Alex41485522019-04-12 17:26:18 -0500203 _total_processed += 1
Alexcf91b182019-05-31 11:57:07 -0500204 # Parse versions from nodes
Alex41485522019-04-12 17:26:18 -0500205 _ver_ins = DebianVersion(_value['installed'])
206 _ver_can = DebianVersion(_value['candidate'])
207
Alexcf91b182019-05-31 11:57:07 -0500208 # Process package description and release version
209 # at a first sight
Alex41485522019-04-12 17:26:18 -0500210 if _name not in _all_packages:
Alexcf91b182019-05-31 11:57:07 -0500211 # get node attributes
Alexd0391d42019-05-21 18:48:55 -0500212 _linux = salt_master.nodes[node_name]['linux_codename']
213 _arch = salt_master.nodes[node_name]['linux_arch']
Alexcf91b182019-05-31 11:57:07 -0500214 # get versions for this tag and repo headers
215 # excluding 'nightly' repos
Alexd0391d42019-05-21 18:48:55 -0500216 _r = self.rm.get_filtered_versions(
217 _name,
218 tag=_mcp,
219 include=[_os, _linux, _arch],
220 exclude=["nightly"]
221 )
Alexcf91b182019-05-31 11:57:07 -0500222 # repack versions in flat format
Alexd0391d42019-05-21 18:48:55 -0500223 _vs = {}
224 _sections = {}
225 _apps = {}
Alexd0391d42019-05-21 18:48:55 -0500226 for s, apps in _r.iteritems():
227 for a, versions in apps.iteritems():
228 for v, repos in versions.iteritems():
229 for repo in repos:
Alexcf91b182019-05-31 11:57:07 -0500230 if v not in _vs:
231 _vs[v] = []
232 _vs[v].append(repo)
233 if v not in _sections:
234 _sections[v] = []
235 _sections[v].append(s)
236 if v not in _apps:
237 _apps[v] = []
238 _apps[v].append(a)
239 # search for the newest version among filtered
Alexd0391d42019-05-21 18:48:55 -0500240 _r_desc = []
241 _vs_keys = _vs.keys()
242 if _vs_keys:
243 _newest = _newest = DebianVersion(_vs_keys.pop())
244 else:
245 _newest = DebianVersion('')
Alexd0391d42019-05-21 18:48:55 -0500246 for v in _vs_keys:
247 _this = DebianVersion(v)
248 if _this > _newest:
249 _newest = _this
Alexd0391d42019-05-21 18:48:55 -0500250 _release = _newest
Alexcf91b182019-05-31 11:57:07 -0500251 # Get best description for the package
Alexd0391d42019-05-21 18:48:55 -0500252 if _release.version != 'n/a':
253 _r_desc = _vs[_release.version]
254 # preload special description
Alex41485522019-04-12 17:26:18 -0500255 if _desc[_name]:
Alex41485522019-04-12 17:26:18 -0500256 _pkg_desc = _desc[_name]
257 else:
Alex41485522019-04-12 17:26:18 -0500258 _pkg_desc = _desc.dummy_desc
Alexcf91b182019-05-31 11:57:07 -0500259 # Save repos list and desc for this version
Alexd0391d42019-05-21 18:48:55 -0500260 # Check if we can provide better from the package
261 if _release.version != 'n/a':
262 if not _pkg_desc['section']:
263 _pkg_desc['section'] = \
264 "/".join(_sections[_release.version])
265 if not _pkg_desc['app']:
266 _pkg_desc['app'] = \
267 "/".join(_apps[_release.version])
Alex3ebc5632019-04-18 16:47:18 -0500268
Alexcf91b182019-05-31 11:57:07 -0500269 # Populate package info, once for package
Alexd0391d42019-05-21 18:48:55 -0500270 _m = _r_desc[0]["maintainer"] if _r_desc else 'n/a'
Alex41485522019-04-12 17:26:18 -0500271 _all_packages[_name] = {
272 "desc": _pkg_desc,
Alexd0391d42019-05-21 18:48:55 -0500273 "repos": _r_desc,
274 "maintainer": _m,
275 "is_mirantis": self.rm.is_mirantis(
276 _name,
Alexcf91b182019-05-31 11:57:07 -0500277 tag=_mcp
Alexd0391d42019-05-21 18:48:55 -0500278 ),
Alex41485522019-04-12 17:26:18 -0500279 "results": {},
280 "r": _release,
Alex Savatieiev3db12a72019-03-22 16:32:31 -0500281 }
Alexcf91b182019-05-31 11:57:07 -0500282 # Cross-compare versions
Alex41485522019-04-12 17:26:18 -0500283 _cmp = VersionCmpResult(
284 _ver_ins,
285 _ver_can,
286 _all_packages[_name]['r']
287 )
Alexcf91b182019-05-31 11:57:07 -0500288 # Update results structure
Alex41485522019-04-12 17:26:18 -0500289 # shortcut to results
290 _res = _all_packages[_name]['results']
291 # update status
292 if _cmp.status not in _res:
293 _res[_cmp.status] = {}
294 # update action
295 if _cmp.action not in _res[_cmp.status]:
296 _res[_cmp.status][_cmp.action] = {}
297 # update node
298 if node_name not in _res[_cmp.status][_cmp.action]:
299 _res[_cmp.status][_cmp.action][node_name] = {}
300 # put result
301 _res[_cmp.status][_cmp.action][node_name] = {
302 'i': _ver_ins,
303 'c': _ver_can,
304 'res': _cmp,
305 'raw': _value['raw']
306 }
savex4448e132018-04-25 15:51:14 +0200307
Alex41485522019-04-12 17:26:18 -0500308 self._packages = _all_packages
Alexd9fd85e2019-05-16 16:58:24 -0500309 _progress.end()
savex4448e132018-04-25 15:51:14 +0200310
Alex41485522019-04-12 17:26:18 -0500311 def create_report(self, filename, rtype, full=None):
savex4448e132018-04-25 15:51:14 +0200312 """
313 Create static html showing packages diff per node
314
315 :return: buff with html
316 """
Alex Savatieiev42b89fa2019-03-07 18:45:26 -0600317 logger_cli.info("# Generating report to '{}'".format(filename))
Alex41485522019-04-12 17:26:18 -0500318 if rtype == 'html':
319 _type = reporter.HTMLPackageCandidates()
320 elif rtype == 'csv':
321 _type = reporter.CSVAllPackages()
322 else:
323 raise ConfigException("Report type not set")
Alex Savatieievd48994d2018-12-13 12:13:00 +0100324 _report = reporter.ReportToFile(
Alex41485522019-04-12 17:26:18 -0500325 _type,
savex4448e132018-04-25 15:51:14 +0200326 filename
327 )
Alex41485522019-04-12 17:26:18 -0500328 payload = {
Alexe0c5b9e2019-04-23 18:51:23 -0500329 "nodes": salt_master.nodes,
330 "mcp_release": salt_master.mcp_release,
331 "openstack_release": salt_master.openstack_release
Alex41485522019-04-12 17:26:18 -0500332 }
333 payload.update(self.presort_packages(self._packages, full))
334 _report(payload)
Alex Savatieiev799bee32019-02-20 17:19:26 -0600335 logger_cli.info("-> Done")