blob: 631c99222cea85d91938a7898f5382f6c3c7031f [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):
Alexe9547d82019-06-03 15:22:50 -050014 def __init__(self, force_tag=None, exclude_keywords=[]):
Alexd0391d42019-05-21 18:48:55 -050015 # 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
Alexe9547d82019-06-03 15:22:50 -050032 self.force_tag = force_tag
Alex9e4bfaf2019-06-11 15:21:59 -050033 self.exclude_keywords = exclude_keywords
Alexe9547d82019-06-03 15:22:50 -050034
Alex41485522019-04-12 17:26:18 -050035 @staticmethod
36 def presort_packages(all_packages, full=None):
37 logger_cli.info("-> Presorting packages")
38 # labels
39 _data = {}
40 _data = {
41 "cs": {
42 "ok": const.VERSION_OK,
43 "up": const.VERSION_UP,
44 "down": const.VERSION_DOWN,
45 "err": const.VERSION_ERR
46 },
47 "ca": {
48 "na": const.ACT_NA,
49 "up": const.ACT_UPGRADE,
50 "need_up": const.ACT_NEED_UP,
51 "need_down": const.ACT_NEED_DOWN,
52 "repo": const.ACT_REPO
53 }
54 }
55 _data['status_err'] = const.VERSION_ERR
56 _data['status_down'] = const.VERSION_DOWN
57
58 # Presort packages
59 _data['critical'] = {}
60 _data['system'] = {}
61 _data['other'] = {}
62 _data['unlisted'] = {}
63
64 _l = len(all_packages)
65 _progress = Progress(_l)
66 _progress_index = 0
67 # counters
68 _ec = _es = _eo = _eu = 0
69 _dc = _ds = _do = _du = 0
70 while _progress_index < _l:
71 # progress bar
72 _progress_index += 1
73 _progress.write_progress(_progress_index)
74 # sort packages
75 _pn, _val = all_packages.popitem()
Alexd0391d42019-05-21 18:48:55 -050076 _c = _val['desc']['section']
77
Alexe0c5b9e2019-04-23 18:51:23 -050078 if not full:
Alex41485522019-04-12 17:26:18 -050079 # Check if this packet has errors
80 # if all is ok -> just skip it
81 _max_status = max(_val['results'].keys())
82 if _max_status <= const.VERSION_OK:
83 _max_action = max(_val['results'][_max_status].keys())
84 if _max_action == const.ACT_NA:
Alexd0391d42019-05-21 18:48:55 -050085 # this package does not have any comments
Alex41485522019-04-12 17:26:18 -050086 # ...just skip it from report
87 continue
88
Alexd0391d42019-05-21 18:48:55 -050089 if len(_c) > 0 and _val['is_mirantis'] is None:
Alex41485522019-04-12 17:26:18 -050090 # not listed package in version lib
91 _data['unlisted'].update({
92 _pn: _val
93 })
94 _eu += _val['results'].keys().count(const.VERSION_ERR)
95 _du += _val['results'].keys().count(const.VERSION_DOWN)
96 # mirantis/critical
Alexd0391d42019-05-21 18:48:55 -050097 # elif len(_c) > 0 and _c != 'System':
98 elif _val['is_mirantis']:
Alex41485522019-04-12 17:26:18 -050099 # not blank and not system
100 _data['critical'].update({
101 _pn: _val
102 })
103 _ec += _val['results'].keys().count(const.VERSION_ERR)
104 _dc += _val['results'].keys().count(const.VERSION_DOWN)
105 # system
106 elif _c == 'System':
107 _data['system'].update({
108 _pn: _val
109 })
110 _es += _val['results'].keys().count(const.VERSION_ERR)
111 _ds += _val['results'].keys().count(const.VERSION_DOWN)
112 # rest
113 else:
114 _data['other'].update({
115 _pn: _val
116 })
117 _eo += _val['results'].keys().count(const.VERSION_ERR)
118 _do += _val['results'].keys().count(const.VERSION_DOWN)
119
Alexd9fd85e2019-05-16 16:58:24 -0500120 _progress.end()
Alex41485522019-04-12 17:26:18 -0500121
122 _data['errors'] = {
123 'mirantis': _ec,
124 'system': _es,
125 'other': _eo,
126 'unlisted': _eu
127 }
128 _data['downgrades'] = {
129 'mirantis': _dc,
130 'system': _ds,
131 'other': _do,
132 'unlisted': _du
133 }
134
135 return _data
136
savex4448e132018-04-25 15:51:14 +0200137 def collect_installed_packages(self):
138 """
139 Collect installed packages on each node
140 sets 'installed' dict property in the class
141
142 :return: none
143 """
Alex Savatieiev42b89fa2019-03-07 18:45:26 -0600144 logger_cli.info("# Collecting installed packages")
Alexe0c5b9e2019-04-23 18:51:23 -0500145 salt_master.prepare_script_on_active_nodes("pkg_versions.py")
146 _result = salt_master.execute_script_on_active_nodes("pkg_versions.py")
savex4448e132018-04-25 15:51:14 +0200147
Alexe0c5b9e2019-04-23 18:51:23 -0500148 for key in salt_master.nodes.keys():
savex4448e132018-04-25 15:51:14 +0200149 # due to much data to be passed from salt, it is happening in order
Alexe9547d82019-06-03 15:22:50 -0500150 if key in _result and _result[key]:
savex4448e132018-04-25 15:51:14 +0200151 _text = _result[key]
Alex Savatieievfa5910a2019-03-18 18:12:24 -0500152 try:
153 _dict = json.loads(_text[_text.find('{'):])
Alex3ebc5632019-04-18 16:47:18 -0500154 except ValueError:
Alex Savatieievfa5910a2019-03-18 18:12:24 -0500155 logger_cli.info("... no JSON for '{}'".format(
156 key
157 ))
Alex3ebc5632019-04-18 16:47:18 -0500158 logger_cli.debug(
159 "ERROR:\n{}\n".format(_text[:_text.find('{')])
160 )
Alex Savatieievfa5910a2019-03-18 18:12:24 -0500161 _dict = {}
Alex3ebc5632019-04-18 16:47:18 -0500162
Alexe0c5b9e2019-04-23 18:51:23 -0500163 salt_master.nodes[key]['packages'] = _dict
savex4448e132018-04-25 15:51:14 +0200164 else:
Alexe0c5b9e2019-04-23 18:51:23 -0500165 salt_master.nodes[key]['packages'] = {}
Alex Savatieiev42b89fa2019-03-07 18:45:26 -0600166 logger_cli.debug("... {} has {} packages installed".format(
savex4448e132018-04-25 15:51:14 +0200167 key,
Alexe0c5b9e2019-04-23 18:51:23 -0500168 len(salt_master.nodes[key]['packages'].keys())
savex4448e132018-04-25 15:51:14 +0200169 ))
Alex Savatieiev799bee32019-02-20 17:19:26 -0600170 logger_cli.info("-> Done")
savex4448e132018-04-25 15:51:14 +0200171
172 def collect_packages(self):
173 """
174 Check package versions in repos vs installed
175
176 :return: no return values, all date put to dict in place
177 """
Alex41485522019-04-12 17:26:18 -0500178 # Preload OpenStack release versions
179 _desc = PkgVersions()
Alex3ebc5632019-04-18 16:47:18 -0500180 logger_cli.info(
181 "# Cross-comparing: Installed vs Candidates vs Release"
182 )
Alexd0391d42019-05-21 18:48:55 -0500183 # shortcuts for this cloud values
184 _os = salt_master.openstack_release
185 _mcp = salt_master.mcp_release
Alexe9547d82019-06-03 15:22:50 -0500186 _t = [self.force_tag] if self.force_tag else []
187 _t.append(_mcp)
188
189 logger_cli.info("# Tag search list: {}".format(", ".join(_t)))
190 logger_cli.info("# Openstack version: {}".format(_os))
191 logger_cli.info(
192 "# Release versions repos keyword exclude list: {}".format(
Alex9e4bfaf2019-06-11 15:21:59 -0500193 ", ".join(self.exclude_keywords)
Alexe9547d82019-06-03 15:22:50 -0500194 )
195 )
196
Alexd0391d42019-05-21 18:48:55 -0500197 # Progress class
Alexe0c5b9e2019-04-23 18:51:23 -0500198 _progress = Progress(len(salt_master.nodes.keys()))
Alex41485522019-04-12 17:26:18 -0500199 _progress_index = 0
200 _total_processed = 0
Alex Savatieiev3db12a72019-03-22 16:32:31 -0500201 # Collect packages from all of the nodes in flat dict
Alex41485522019-04-12 17:26:18 -0500202 _all_packages = {}
Alexe0c5b9e2019-04-23 18:51:23 -0500203 for node_name, node_value in salt_master.nodes.iteritems():
Alex41485522019-04-12 17:26:18 -0500204 _uniq_len = len(_all_packages.keys())
205 _progress_index += 1
Alexd0391d42019-05-21 18:48:55 -0500206 # progress updates shown before next node only
207 # it is costly operation to do it for each of the 150k packages
Alex41485522019-04-12 17:26:18 -0500208 _progress.write_progress(
209 _progress_index,
210 note="/ {} uniq out of {} packages found".format(
211 _uniq_len,
212 _total_processed
213 )
214 )
Alex Savatieiev3db12a72019-03-22 16:32:31 -0500215 for _name, _value in node_value['packages'].iteritems():
Alex41485522019-04-12 17:26:18 -0500216 _total_processed += 1
Alexcf91b182019-05-31 11:57:07 -0500217 # Parse versions from nodes
Alex41485522019-04-12 17:26:18 -0500218 _ver_ins = DebianVersion(_value['installed'])
219 _ver_can = DebianVersion(_value['candidate'])
220
Alexcf91b182019-05-31 11:57:07 -0500221 # Process package description and release version
222 # at a first sight
Alex41485522019-04-12 17:26:18 -0500223 if _name not in _all_packages:
Alexcf91b182019-05-31 11:57:07 -0500224 # get node attributes
Alexd0391d42019-05-21 18:48:55 -0500225 _linux = salt_master.nodes[node_name]['linux_codename']
226 _arch = salt_master.nodes[node_name]['linux_arch']
Alexe9547d82019-06-03 15:22:50 -0500227 # get versions for tag, Openstack release and repo headers
228 # excluding 'nightly' repos by default
229 _r = {}
230 # if there is a forced tag = use it
231 if self.force_tag:
232 _r = self.rm.get_filtered_versions(
233 _name,
234 tag=self.force_tag,
235 include=[_os, _linux, _arch],
Alex9e4bfaf2019-06-11 15:21:59 -0500236 exclude=self.exclude_keywords
Alexe9547d82019-06-03 15:22:50 -0500237 )
238 # if nothing found, look everywhere
Alex9e4bfaf2019-06-11 15:21:59 -0500239 # but with no word 'openstack'
Alexe9547d82019-06-03 15:22:50 -0500240 if not _r:
241 _r = self.rm.get_filtered_versions(
242 _name,
243 tag=self.force_tag,
244 include=[_linux, _arch],
Alex9e4bfaf2019-06-11 15:21:59 -0500245 exclude=self.exclude_keywords + ['openstack']
Alexe9547d82019-06-03 15:22:50 -0500246 )
247 # if nothing is found at this point,
248 # repeat search using normal tags
249 if not _r:
250 _r = self.rm.get_filtered_versions(
251 _name,
252 tag=_mcp,
253 include=[_os, _linux, _arch],
Alex9e4bfaf2019-06-11 15:21:59 -0500254 exclude=self.exclude_keywords
Alexe9547d82019-06-03 15:22:50 -0500255 )
256 # Once again, if nothing found, look everywhere
257 if not _r:
258 _r = self.rm.get_filtered_versions(
259 _name,
260 tag=_mcp,
261 include=[_linux, _arch],
Alex9e4bfaf2019-06-11 15:21:59 -0500262 exclude=self.exclude_keywords + ['openstack']
Alexe9547d82019-06-03 15:22:50 -0500263 )
Alexcf91b182019-05-31 11:57:07 -0500264 # repack versions in flat format
Alexd0391d42019-05-21 18:48:55 -0500265 _vs = {}
266 _sections = {}
267 _apps = {}
Alexd0391d42019-05-21 18:48:55 -0500268 for s, apps in _r.iteritems():
269 for a, versions in apps.iteritems():
270 for v, repos in versions.iteritems():
271 for repo in repos:
Alexcf91b182019-05-31 11:57:07 -0500272 if v not in _vs:
273 _vs[v] = []
274 _vs[v].append(repo)
275 if v not in _sections:
276 _sections[v] = []
277 _sections[v].append(s)
278 if v not in _apps:
279 _apps[v] = []
280 _apps[v].append(a)
281 # search for the newest version among filtered
Alexd0391d42019-05-21 18:48:55 -0500282 _r_desc = []
283 _vs_keys = _vs.keys()
284 if _vs_keys:
285 _newest = _newest = DebianVersion(_vs_keys.pop())
286 else:
287 _newest = DebianVersion('')
Alexd0391d42019-05-21 18:48:55 -0500288 for v in _vs_keys:
289 _this = DebianVersion(v)
290 if _this > _newest:
291 _newest = _this
Alexd0391d42019-05-21 18:48:55 -0500292 _release = _newest
Alexcf91b182019-05-31 11:57:07 -0500293 # Get best description for the package
Alexd0391d42019-05-21 18:48:55 -0500294 if _release.version != 'n/a':
295 _r_desc = _vs[_release.version]
296 # preload special description
Alex41485522019-04-12 17:26:18 -0500297 if _desc[_name]:
Alex41485522019-04-12 17:26:18 -0500298 _pkg_desc = _desc[_name]
299 else:
Alex41485522019-04-12 17:26:18 -0500300 _pkg_desc = _desc.dummy_desc
Alexcf91b182019-05-31 11:57:07 -0500301 # Save repos list and desc for this version
Alexd0391d42019-05-21 18:48:55 -0500302 # Check if we can provide better from the package
303 if _release.version != 'n/a':
304 if not _pkg_desc['section']:
305 _pkg_desc['section'] = \
306 "/".join(_sections[_release.version])
307 if not _pkg_desc['app']:
308 _pkg_desc['app'] = \
309 "/".join(_apps[_release.version])
Alex3ebc5632019-04-18 16:47:18 -0500310
Alexcf91b182019-05-31 11:57:07 -0500311 # Populate package info, once for package
Alexd0391d42019-05-21 18:48:55 -0500312 _m = _r_desc[0]["maintainer"] if _r_desc else 'n/a'
Alex41485522019-04-12 17:26:18 -0500313 _all_packages[_name] = {
314 "desc": _pkg_desc,
Alexd0391d42019-05-21 18:48:55 -0500315 "repos": _r_desc,
316 "maintainer": _m,
317 "is_mirantis": self.rm.is_mirantis(
318 _name,
Alexcf91b182019-05-31 11:57:07 -0500319 tag=_mcp
Alexd0391d42019-05-21 18:48:55 -0500320 ),
Alex41485522019-04-12 17:26:18 -0500321 "results": {},
322 "r": _release,
Alex Savatieiev3db12a72019-03-22 16:32:31 -0500323 }
Alexcf91b182019-05-31 11:57:07 -0500324 # Cross-compare versions
Alex41485522019-04-12 17:26:18 -0500325 _cmp = VersionCmpResult(
326 _ver_ins,
327 _ver_can,
328 _all_packages[_name]['r']
329 )
Alexcf91b182019-05-31 11:57:07 -0500330 # Update results structure
Alex41485522019-04-12 17:26:18 -0500331 # shortcut to results
332 _res = _all_packages[_name]['results']
333 # update status
334 if _cmp.status not in _res:
335 _res[_cmp.status] = {}
336 # update action
337 if _cmp.action not in _res[_cmp.status]:
338 _res[_cmp.status][_cmp.action] = {}
339 # update node
340 if node_name not in _res[_cmp.status][_cmp.action]:
341 _res[_cmp.status][_cmp.action][node_name] = {}
342 # put result
343 _res[_cmp.status][_cmp.action][node_name] = {
344 'i': _ver_ins,
345 'c': _ver_can,
346 'res': _cmp,
347 'raw': _value['raw']
348 }
savex4448e132018-04-25 15:51:14 +0200349
Alex41485522019-04-12 17:26:18 -0500350 self._packages = _all_packages
Alexd9fd85e2019-05-16 16:58:24 -0500351 _progress.end()
savex4448e132018-04-25 15:51:14 +0200352
Alex41485522019-04-12 17:26:18 -0500353 def create_report(self, filename, rtype, full=None):
savex4448e132018-04-25 15:51:14 +0200354 """
355 Create static html showing packages diff per node
356
357 :return: buff with html
358 """
Alex Savatieiev42b89fa2019-03-07 18:45:26 -0600359 logger_cli.info("# Generating report to '{}'".format(filename))
Alex41485522019-04-12 17:26:18 -0500360 if rtype == 'html':
361 _type = reporter.HTMLPackageCandidates()
362 elif rtype == 'csv':
363 _type = reporter.CSVAllPackages()
364 else:
365 raise ConfigException("Report type not set")
Alex Savatieievd48994d2018-12-13 12:13:00 +0100366 _report = reporter.ReportToFile(
Alex41485522019-04-12 17:26:18 -0500367 _type,
savex4448e132018-04-25 15:51:14 +0200368 filename
369 )
Alex41485522019-04-12 17:26:18 -0500370 payload = {
Alexe0c5b9e2019-04-23 18:51:23 -0500371 "nodes": salt_master.nodes,
372 "mcp_release": salt_master.mcp_release,
373 "openstack_release": salt_master.openstack_release
Alex41485522019-04-12 17:26:18 -0500374 }
375 payload.update(self.presort_packages(self._packages, full))
376 _report(payload)
Alex Savatieiev799bee32019-02-20 17:19:26 -0600377 logger_cli.info("-> Done")