blob: bf192f932199bd0799ea27e4e78cbdb352db352d [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 rm = RepoManager()
15
16 def __init__(self):
17 # Init salt master info
18 if not salt_master.nodes:
19 salt_master.nodes = salt_master.get_nodes()
20
21 # check that this env tag is present in Manager
22 _tags = self.rm.get_available_tags(tag=salt_master.mcp_release)
23 if not _tags:
24 logger_cli.warning(
25 "\n# hWARNING: '{0}' is not listed in repo index. "
26 "Consider running:\n\t{1}\nto add info on this tag's "
27 "release package versions".format(
28 salt_master.mcp_release,
29 "mcp-checker packages versions --tag {0}"
30 )
31 )
32
Alex41485522019-04-12 17:26:18 -050033 @staticmethod
34 def presort_packages(all_packages, full=None):
35 logger_cli.info("-> Presorting packages")
36 # labels
37 _data = {}
38 _data = {
39 "cs": {
40 "ok": const.VERSION_OK,
41 "up": const.VERSION_UP,
42 "down": const.VERSION_DOWN,
43 "err": const.VERSION_ERR
44 },
45 "ca": {
46 "na": const.ACT_NA,
47 "up": const.ACT_UPGRADE,
48 "need_up": const.ACT_NEED_UP,
49 "need_down": const.ACT_NEED_DOWN,
50 "repo": const.ACT_REPO
51 }
52 }
53 _data['status_err'] = const.VERSION_ERR
54 _data['status_down'] = const.VERSION_DOWN
55
56 # Presort packages
57 _data['critical'] = {}
58 _data['system'] = {}
59 _data['other'] = {}
60 _data['unlisted'] = {}
61
62 _l = len(all_packages)
63 _progress = Progress(_l)
64 _progress_index = 0
65 # counters
66 _ec = _es = _eo = _eu = 0
67 _dc = _ds = _do = _du = 0
68 while _progress_index < _l:
69 # progress bar
70 _progress_index += 1
71 _progress.write_progress(_progress_index)
72 # sort packages
73 _pn, _val = all_packages.popitem()
Alexd0391d42019-05-21 18:48:55 -050074 _c = _val['desc']['section']
75
Alexe0c5b9e2019-04-23 18:51:23 -050076 if not full:
Alex41485522019-04-12 17:26:18 -050077 # Check if this packet has errors
78 # if all is ok -> just skip it
79 _max_status = max(_val['results'].keys())
80 if _max_status <= const.VERSION_OK:
81 _max_action = max(_val['results'][_max_status].keys())
82 if _max_action == const.ACT_NA:
Alexd0391d42019-05-21 18:48:55 -050083 # this package does not have any comments
Alex41485522019-04-12 17:26:18 -050084 # ...just skip it from report
85 continue
86
Alexd0391d42019-05-21 18:48:55 -050087 if len(_c) > 0 and _val['is_mirantis'] is None:
Alex41485522019-04-12 17:26:18 -050088 # not listed package in version lib
89 _data['unlisted'].update({
90 _pn: _val
91 })
92 _eu += _val['results'].keys().count(const.VERSION_ERR)
93 _du += _val['results'].keys().count(const.VERSION_DOWN)
94 # mirantis/critical
Alexd0391d42019-05-21 18:48:55 -050095 # elif len(_c) > 0 and _c != 'System':
96 elif _val['is_mirantis']:
Alex41485522019-04-12 17:26:18 -050097 # not blank and not system
98 _data['critical'].update({
99 _pn: _val
100 })
101 _ec += _val['results'].keys().count(const.VERSION_ERR)
102 _dc += _val['results'].keys().count(const.VERSION_DOWN)
103 # system
104 elif _c == 'System':
105 _data['system'].update({
106 _pn: _val
107 })
108 _es += _val['results'].keys().count(const.VERSION_ERR)
109 _ds += _val['results'].keys().count(const.VERSION_DOWN)
110 # rest
111 else:
112 _data['other'].update({
113 _pn: _val
114 })
115 _eo += _val['results'].keys().count(const.VERSION_ERR)
116 _do += _val['results'].keys().count(const.VERSION_DOWN)
117
Alexd9fd85e2019-05-16 16:58:24 -0500118 _progress.end()
Alex41485522019-04-12 17:26:18 -0500119
120 _data['errors'] = {
121 'mirantis': _ec,
122 'system': _es,
123 'other': _eo,
124 'unlisted': _eu
125 }
126 _data['downgrades'] = {
127 'mirantis': _dc,
128 'system': _ds,
129 'other': _do,
130 'unlisted': _du
131 }
132
133 return _data
134
savex4448e132018-04-25 15:51:14 +0200135 def collect_installed_packages(self):
136 """
137 Collect installed packages on each node
138 sets 'installed' dict property in the class
139
140 :return: none
141 """
Alex Savatieiev42b89fa2019-03-07 18:45:26 -0600142 logger_cli.info("# Collecting installed packages")
Alexe0c5b9e2019-04-23 18:51:23 -0500143 salt_master.prepare_script_on_active_nodes("pkg_versions.py")
144 _result = salt_master.execute_script_on_active_nodes("pkg_versions.py")
savex4448e132018-04-25 15:51:14 +0200145
Alexe0c5b9e2019-04-23 18:51:23 -0500146 for key in salt_master.nodes.keys():
savex4448e132018-04-25 15:51:14 +0200147 # due to much data to be passed from salt, it is happening in order
148 if key in _result:
149 _text = _result[key]
Alex Savatieievfa5910a2019-03-18 18:12:24 -0500150 try:
151 _dict = json.loads(_text[_text.find('{'):])
Alex3ebc5632019-04-18 16:47:18 -0500152 except ValueError:
Alex Savatieievfa5910a2019-03-18 18:12:24 -0500153 logger_cli.info("... no JSON for '{}'".format(
154 key
155 ))
Alex3ebc5632019-04-18 16:47:18 -0500156 logger_cli.debug(
157 "ERROR:\n{}\n".format(_text[:_text.find('{')])
158 )
Alex Savatieievfa5910a2019-03-18 18:12:24 -0500159 _dict = {}
Alex3ebc5632019-04-18 16:47:18 -0500160
Alexe0c5b9e2019-04-23 18:51:23 -0500161 salt_master.nodes[key]['packages'] = _dict
savex4448e132018-04-25 15:51:14 +0200162 else:
Alexe0c5b9e2019-04-23 18:51:23 -0500163 salt_master.nodes[key]['packages'] = {}
Alex Savatieiev42b89fa2019-03-07 18:45:26 -0600164 logger_cli.debug("... {} has {} packages installed".format(
savex4448e132018-04-25 15:51:14 +0200165 key,
Alexe0c5b9e2019-04-23 18:51:23 -0500166 len(salt_master.nodes[key]['packages'].keys())
savex4448e132018-04-25 15:51:14 +0200167 ))
Alex Savatieiev799bee32019-02-20 17:19:26 -0600168 logger_cli.info("-> Done")
savex4448e132018-04-25 15:51:14 +0200169
170 def collect_packages(self):
171 """
172 Check package versions in repos vs installed
173
174 :return: no return values, all date put to dict in place
175 """
Alex41485522019-04-12 17:26:18 -0500176 # Preload OpenStack release versions
177 _desc = PkgVersions()
Alex3ebc5632019-04-18 16:47:18 -0500178
179 logger_cli.info(
180 "# Cross-comparing: Installed vs Candidates vs Release"
181 )
Alexd0391d42019-05-21 18:48:55 -0500182 # shortcuts for this cloud values
183 _os = salt_master.openstack_release
184 _mcp = salt_master.mcp_release
185 # Progress class
Alexe0c5b9e2019-04-23 18:51:23 -0500186 _progress = Progress(len(salt_master.nodes.keys()))
Alex41485522019-04-12 17:26:18 -0500187 _progress_index = 0
188 _total_processed = 0
Alex Savatieiev3db12a72019-03-22 16:32:31 -0500189 # Collect packages from all of the nodes in flat dict
Alex41485522019-04-12 17:26:18 -0500190 _all_packages = {}
Alexd0391d42019-05-21 18:48:55 -0500191 _all_tags = set([])
192 # get env tag's year and major version
193 _tag_major = _mcp[:_mcp.find('.', _mcp.find('.')+1)]
Alexe0c5b9e2019-04-23 18:51:23 -0500194 for node_name, node_value in salt_master.nodes.iteritems():
Alex41485522019-04-12 17:26:18 -0500195 _uniq_len = len(_all_packages.keys())
196 _progress_index += 1
Alexd0391d42019-05-21 18:48:55 -0500197 # progress updates shown before next node only
198 # it is costly operation to do it for each of the 150k packages
Alex41485522019-04-12 17:26:18 -0500199 _progress.write_progress(
200 _progress_index,
201 note="/ {} uniq out of {} packages found".format(
202 _uniq_len,
203 _total_processed
204 )
205 )
Alex Savatieiev3db12a72019-03-22 16:32:31 -0500206 for _name, _value in node_value['packages'].iteritems():
Alex41485522019-04-12 17:26:18 -0500207 _total_processed += 1
208 # Parse versions
209 _ver_ins = DebianVersion(_value['installed'])
210 _ver_can = DebianVersion(_value['candidate'])
211
212 # All packages list with version and node list
213 if _name not in _all_packages:
Alexd0391d42019-05-21 18:48:55 -0500214 # get repo versions list,
215 _linux = salt_master.nodes[node_name]['linux_codename']
216 _arch = salt_master.nodes[node_name]['linux_arch']
217 if _name == u'qemu-block-extra':
218 a = 1
219 # omit tag as target versions might be of different tag
220 _r = self.rm.get_filtered_versions(
221 _name,
222 tag=_mcp,
223 include=[_os, _linux, _arch],
224 exclude=["nightly"]
225 )
226 # save versions for matching major tags
227 _vs = {}
228 _sections = {}
229 _apps = {}
230 # get all versions for this year
231 for s, apps in _r.iteritems():
232 for a, versions in apps.iteritems():
233 for v, repos in versions.iteritems():
234 for repo in repos:
235 t = repo['tag']
236 _major = t[:t.find('.', t.find('.')+1)]
237 if _tag_major == _major:
238 if v not in _vs:
239 _vs[v] = []
240 _vs[v].append(repo)
241 if v not in _sections:
242 _sections[v] = []
243 _sections[v].append(s)
244 if v not in _apps:
245 _apps[v] = []
246 _apps[v].append(a)
247
248 # check if we have candidate version among found
249 _r_desc = []
250 _vs_keys = _vs.keys()
251 if _vs_keys:
252 _newest = _newest = DebianVersion(_vs_keys.pop())
253 else:
254 _newest = DebianVersion('')
255 # if _ver_ins.version in _vs_keys:
256 # # exact match, save it
257 # _release = _ver_ins
258 # else:
259 # detect newest version among saved
260
261 for v in _vs_keys:
262 _this = DebianVersion(v)
263 if _this > _newest:
264 _newest = _this
265 # newest version for the YEAR.MAJOR will be the release
266 _release = _newest
267 # save repos list for this version
268 if _release.version != 'n/a':
269 _r_desc = _vs[_release.version]
270 # preload special description
Alex41485522019-04-12 17:26:18 -0500271 if _desc[_name]:
Alex41485522019-04-12 17:26:18 -0500272 _pkg_desc = _desc[_name]
273 else:
Alex41485522019-04-12 17:26:18 -0500274 _pkg_desc = _desc.dummy_desc
Alexd0391d42019-05-21 18:48:55 -0500275 # Check if we can provide better from the package
276 if _release.version != 'n/a':
277 if not _pkg_desc['section']:
278 _pkg_desc['section'] = \
279 "/".join(_sections[_release.version])
280 if not _pkg_desc['app']:
281 _pkg_desc['app'] = \
282 "/".join(_apps[_release.version])
Alex3ebc5632019-04-18 16:47:18 -0500283
Alex41485522019-04-12 17:26:18 -0500284 # get specific set for this OS release if present
Alexd0391d42019-05-21 18:48:55 -0500285 # if not, try search in all repos for this tag
286 # _r_desc = _r[_newest.version]
287 # if _r:
288 # _vs = _r.keys()
289 # if len(_vs) > 1:
290 # # search best match
291 # _release = None
292 # for _v in _vs:
293 # _deb = DebianVersion(_v)
294 # if _ver_can == _deb:
295 # _release = _deb
296 # _r_desc = _r[_v]
297 # break
298 # if not _release:
299 # _progress.clearline()
300 # logger_cli.error(
301 # "# ERROR: No release version found "
302 # "for '{}'".format(_name)
303 # )
304 # _release = DebianVersion('')
305 # else:
306 # _release = DebianVersion(_vs[0])
307 # _r_desc = _r[_vs[0]]
308 # else:
309 # # not found... 99% that it will not happen
310 # _release = DebianVersion('')
311
312 # Old versions match routine
313 # ########
314 # if _os in _vers:
315 # _v = _vers[_os]
316 # elif 'any' in _vers:
317 # _v = _vers['any']
318 # else:
319 # _v = {}
320
Alex41485522019-04-12 17:26:18 -0500321 # Finally, get specific version
Alexd0391d42019-05-21 18:48:55 -0500322 # _release = DebianVersion(_v[_mcp] if _mcp in _v else '')
323 for repo in _r_desc:
324 _all_tags.add(repo['tag'])
325
Alex41485522019-04-12 17:26:18 -0500326 # Populate package info
Alexd0391d42019-05-21 18:48:55 -0500327 _m = _r_desc[0]["maintainer"] if _r_desc else 'n/a'
Alex41485522019-04-12 17:26:18 -0500328 _all_packages[_name] = {
329 "desc": _pkg_desc,
Alexd0391d42019-05-21 18:48:55 -0500330 "repos": _r_desc,
331 "maintainer": _m,
332 "is_mirantis": self.rm.is_mirantis(
333 _name,
334 tag=_tag_major
335 ),
Alex41485522019-04-12 17:26:18 -0500336 "results": {},
337 "r": _release,
Alex Savatieiev3db12a72019-03-22 16:32:31 -0500338 }
Alex3ebc5632019-04-18 16:47:18 -0500339
Alex41485522019-04-12 17:26:18 -0500340 _cmp = VersionCmpResult(
341 _ver_ins,
342 _ver_can,
343 _all_packages[_name]['r']
344 )
Alex3ebc5632019-04-18 16:47:18 -0500345
Alex41485522019-04-12 17:26:18 -0500346 # shortcut to results
347 _res = _all_packages[_name]['results']
348 # update status
349 if _cmp.status not in _res:
350 _res[_cmp.status] = {}
351 # update action
352 if _cmp.action not in _res[_cmp.status]:
353 _res[_cmp.status][_cmp.action] = {}
354 # update node
355 if node_name not in _res[_cmp.status][_cmp.action]:
356 _res[_cmp.status][_cmp.action][node_name] = {}
357 # put result
358 _res[_cmp.status][_cmp.action][node_name] = {
359 'i': _ver_ins,
360 'c': _ver_can,
361 'res': _cmp,
362 'raw': _value['raw']
363 }
savex4448e132018-04-25 15:51:14 +0200364
Alex41485522019-04-12 17:26:18 -0500365 self._packages = _all_packages
Alexd9fd85e2019-05-16 16:58:24 -0500366 _progress.end()
savex4448e132018-04-25 15:51:14 +0200367
Alex41485522019-04-12 17:26:18 -0500368 def create_report(self, filename, rtype, full=None):
savex4448e132018-04-25 15:51:14 +0200369 """
370 Create static html showing packages diff per node
371
372 :return: buff with html
373 """
Alex Savatieiev42b89fa2019-03-07 18:45:26 -0600374 logger_cli.info("# Generating report to '{}'".format(filename))
Alex41485522019-04-12 17:26:18 -0500375 if rtype == 'html':
376 _type = reporter.HTMLPackageCandidates()
377 elif rtype == 'csv':
378 _type = reporter.CSVAllPackages()
379 else:
380 raise ConfigException("Report type not set")
Alex Savatieievd48994d2018-12-13 12:13:00 +0100381 _report = reporter.ReportToFile(
Alex41485522019-04-12 17:26:18 -0500382 _type,
savex4448e132018-04-25 15:51:14 +0200383 filename
384 )
Alex41485522019-04-12 17:26:18 -0500385 payload = {
Alexe0c5b9e2019-04-23 18:51:23 -0500386 "nodes": salt_master.nodes,
387 "mcp_release": salt_master.mcp_release,
388 "openstack_release": salt_master.openstack_release
Alex41485522019-04-12 17:26:18 -0500389 }
390 payload.update(self.presort_packages(self._packages, full))
391 _report(payload)
Alex Savatieiev799bee32019-02-20 17:19:26 -0600392 logger_cli.info("-> Done")