blob: 0bcb1a658686f3000a691541494696d35d524c44 [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
Alexe0c5b9e2019-04-23 18:51:23 -05006from cfg_checker.nodes import salt_master
Alex41485522019-04-12 17:26:18 -05007from cfg_checker.reports import reporter
8
Alex3ebc5632019-04-18 16:47:18 -05009from versions import DebianVersion, PkgVersions, VersionCmpResult
savex4448e132018-04-25 15:51:14 +020010
11
Alexe0c5b9e2019-04-23 18:51:23 -050012class CloudPackageChecker(object):
Alex41485522019-04-12 17:26:18 -050013 @staticmethod
14 def presort_packages(all_packages, full=None):
15 logger_cli.info("-> Presorting packages")
16 # labels
17 _data = {}
18 _data = {
19 "cs": {
20 "ok": const.VERSION_OK,
21 "up": const.VERSION_UP,
22 "down": const.VERSION_DOWN,
23 "err": const.VERSION_ERR
24 },
25 "ca": {
26 "na": const.ACT_NA,
27 "up": const.ACT_UPGRADE,
28 "need_up": const.ACT_NEED_UP,
29 "need_down": const.ACT_NEED_DOWN,
30 "repo": const.ACT_REPO
31 }
32 }
33 _data['status_err'] = const.VERSION_ERR
34 _data['status_down'] = const.VERSION_DOWN
35
36 # Presort packages
37 _data['critical'] = {}
38 _data['system'] = {}
39 _data['other'] = {}
40 _data['unlisted'] = {}
41
42 _l = len(all_packages)
43 _progress = Progress(_l)
44 _progress_index = 0
45 # counters
46 _ec = _es = _eo = _eu = 0
47 _dc = _ds = _do = _du = 0
48 while _progress_index < _l:
49 # progress bar
50 _progress_index += 1
51 _progress.write_progress(_progress_index)
52 # sort packages
53 _pn, _val = all_packages.popitem()
54 _c = _val['desc']['component']
Alexe0c5b9e2019-04-23 18:51:23 -050055 if not full:
Alex41485522019-04-12 17:26:18 -050056 # Check if this packet has errors
57 # if all is ok -> just skip it
58 _max_status = max(_val['results'].keys())
59 if _max_status <= const.VERSION_OK:
60 _max_action = max(_val['results'][_max_status].keys())
61 if _max_action == const.ACT_NA:
Alexe0c5b9e2019-04-23 18:51:23 -050062 # this package do not has any comments
Alex41485522019-04-12 17:26:18 -050063 # ...just skip it from report
64 continue
65
66 if len(_c) > 0 and _c == 'unlisted':
67 # not listed package in version lib
68 _data['unlisted'].update({
69 _pn: _val
70 })
71 _eu += _val['results'].keys().count(const.VERSION_ERR)
72 _du += _val['results'].keys().count(const.VERSION_DOWN)
73 # mirantis/critical
74 elif len(_c) > 0 and _c != 'System':
75 # not blank and not system
76 _data['critical'].update({
77 _pn: _val
78 })
79 _ec += _val['results'].keys().count(const.VERSION_ERR)
80 _dc += _val['results'].keys().count(const.VERSION_DOWN)
81 # system
82 elif _c == 'System':
83 _data['system'].update({
84 _pn: _val
85 })
86 _es += _val['results'].keys().count(const.VERSION_ERR)
87 _ds += _val['results'].keys().count(const.VERSION_DOWN)
88 # rest
89 else:
90 _data['other'].update({
91 _pn: _val
92 })
93 _eo += _val['results'].keys().count(const.VERSION_ERR)
94 _do += _val['results'].keys().count(const.VERSION_DOWN)
95
Alex41485522019-04-12 17:26:18 -050096 _progress.newline()
97
98 _data['errors'] = {
99 'mirantis': _ec,
100 'system': _es,
101 'other': _eo,
102 'unlisted': _eu
103 }
104 _data['downgrades'] = {
105 'mirantis': _dc,
106 'system': _ds,
107 'other': _do,
108 'unlisted': _du
109 }
110
111 return _data
112
savex4448e132018-04-25 15:51:14 +0200113 def collect_installed_packages(self):
114 """
115 Collect installed packages on each node
116 sets 'installed' dict property in the class
117
118 :return: none
119 """
Alex Savatieiev42b89fa2019-03-07 18:45:26 -0600120 logger_cli.info("# Collecting installed packages")
Alexe0c5b9e2019-04-23 18:51:23 -0500121 if not salt_master.nodes:
122 salt_master.nodes = salt_master.get_nodes()
123 salt_master.prepare_script_on_active_nodes("pkg_versions.py")
124 _result = salt_master.execute_script_on_active_nodes("pkg_versions.py")
savex4448e132018-04-25 15:51:14 +0200125
Alexe0c5b9e2019-04-23 18:51:23 -0500126 for key in salt_master.nodes.keys():
savex4448e132018-04-25 15:51:14 +0200127 # due to much data to be passed from salt, it is happening in order
128 if key in _result:
129 _text = _result[key]
Alex Savatieievfa5910a2019-03-18 18:12:24 -0500130 try:
131 _dict = json.loads(_text[_text.find('{'):])
Alex3ebc5632019-04-18 16:47:18 -0500132 except ValueError:
Alex Savatieievfa5910a2019-03-18 18:12:24 -0500133 logger_cli.info("... no JSON for '{}'".format(
134 key
135 ))
Alex3ebc5632019-04-18 16:47:18 -0500136 logger_cli.debug(
137 "ERROR:\n{}\n".format(_text[:_text.find('{')])
138 )
Alex Savatieievfa5910a2019-03-18 18:12:24 -0500139 _dict = {}
Alex3ebc5632019-04-18 16:47:18 -0500140
Alexe0c5b9e2019-04-23 18:51:23 -0500141 salt_master.nodes[key]['packages'] = _dict
savex4448e132018-04-25 15:51:14 +0200142 else:
Alexe0c5b9e2019-04-23 18:51:23 -0500143 salt_master.nodes[key]['packages'] = {}
Alex Savatieiev42b89fa2019-03-07 18:45:26 -0600144 logger_cli.debug("... {} has {} packages installed".format(
savex4448e132018-04-25 15:51:14 +0200145 key,
Alexe0c5b9e2019-04-23 18:51:23 -0500146 len(salt_master.nodes[key]['packages'].keys())
savex4448e132018-04-25 15:51:14 +0200147 ))
Alex Savatieiev799bee32019-02-20 17:19:26 -0600148 logger_cli.info("-> Done")
savex4448e132018-04-25 15:51:14 +0200149
150 def collect_packages(self):
151 """
152 Check package versions in repos vs installed
153
154 :return: no return values, all date put to dict in place
155 """
Alex41485522019-04-12 17:26:18 -0500156 # Preload OpenStack release versions
157 _desc = PkgVersions()
Alex3ebc5632019-04-18 16:47:18 -0500158
159 logger_cli.info(
160 "# Cross-comparing: Installed vs Candidates vs Release"
161 )
Alexe0c5b9e2019-04-23 18:51:23 -0500162 _progress = Progress(len(salt_master.nodes.keys()))
Alex41485522019-04-12 17:26:18 -0500163 _progress_index = 0
164 _total_processed = 0
Alex Savatieiev3db12a72019-03-22 16:32:31 -0500165 # Collect packages from all of the nodes in flat dict
Alex41485522019-04-12 17:26:18 -0500166 _all_packages = {}
Alexe0c5b9e2019-04-23 18:51:23 -0500167 for node_name, node_value in salt_master.nodes.iteritems():
Alex41485522019-04-12 17:26:18 -0500168 _uniq_len = len(_all_packages.keys())
169 _progress_index += 1
170 # progress will jump from node to node
171 # it is very costly operation to execute it for each pkg
172 _progress.write_progress(
173 _progress_index,
174 note="/ {} uniq out of {} packages found".format(
175 _uniq_len,
176 _total_processed
177 )
178 )
Alex Savatieiev3db12a72019-03-22 16:32:31 -0500179 for _name, _value in node_value['packages'].iteritems():
Alex41485522019-04-12 17:26:18 -0500180 _total_processed += 1
181 # Parse versions
182 _ver_ins = DebianVersion(_value['installed'])
183 _ver_can = DebianVersion(_value['candidate'])
184
185 # All packages list with version and node list
186 if _name not in _all_packages:
187 # shortcuts for this cloud values
Alexe0c5b9e2019-04-23 18:51:23 -0500188 _os = salt_master.openstack_release
189 _mcp = salt_master.mcp_release
Alex41485522019-04-12 17:26:18 -0500190 _pkg_desc = {}
191 if _desc[_name]:
192 # shortcut to version library
193 _vers = _desc[_name]['versions']
194 _pkg_desc = _desc[_name]
195 else:
196 # no description - no library :)
197 _vers = {}
198 _pkg_desc = _desc.dummy_desc
Alex3ebc5632019-04-18 16:47:18 -0500199
Alex41485522019-04-12 17:26:18 -0500200 # get specific set for this OS release if present
201 if _os in _vers:
Alex3ebc5632019-04-18 16:47:18 -0500202 _v = _vers[_os]
Alex41485522019-04-12 17:26:18 -0500203 elif 'any' in _vers:
204 _v = _vers['any']
205 else:
206 _v = {}
207 # Finally, get specific version
208 _release = DebianVersion(_v[_mcp] if _mcp in _v else '')
209 # Populate package info
210 _all_packages[_name] = {
211 "desc": _pkg_desc,
212 "results": {},
213 "r": _release,
Alex Savatieiev3db12a72019-03-22 16:32:31 -0500214 }
Alex3ebc5632019-04-18 16:47:18 -0500215
Alex41485522019-04-12 17:26:18 -0500216 _cmp = VersionCmpResult(
217 _ver_ins,
218 _ver_can,
219 _all_packages[_name]['r']
220 )
Alex3ebc5632019-04-18 16:47:18 -0500221
Alex41485522019-04-12 17:26:18 -0500222 # shortcut to results
223 _res = _all_packages[_name]['results']
224 # update status
225 if _cmp.status not in _res:
226 _res[_cmp.status] = {}
227 # update action
228 if _cmp.action not in _res[_cmp.status]:
229 _res[_cmp.status][_cmp.action] = {}
230 # update node
231 if node_name not in _res[_cmp.status][_cmp.action]:
232 _res[_cmp.status][_cmp.action][node_name] = {}
233 # put result
234 _res[_cmp.status][_cmp.action][node_name] = {
235 'i': _ver_ins,
236 'c': _ver_can,
237 'res': _cmp,
238 'raw': _value['raw']
239 }
savex4448e132018-04-25 15:51:14 +0200240
Alex41485522019-04-12 17:26:18 -0500241 self._packages = _all_packages
242 _progress.newline()
savex4448e132018-04-25 15:51:14 +0200243
Alex41485522019-04-12 17:26:18 -0500244 def create_report(self, filename, rtype, full=None):
savex4448e132018-04-25 15:51:14 +0200245 """
246 Create static html showing packages diff per node
247
248 :return: buff with html
249 """
Alex Savatieiev42b89fa2019-03-07 18:45:26 -0600250 logger_cli.info("# Generating report to '{}'".format(filename))
Alex41485522019-04-12 17:26:18 -0500251 if rtype == 'html':
252 _type = reporter.HTMLPackageCandidates()
253 elif rtype == 'csv':
254 _type = reporter.CSVAllPackages()
255 else:
256 raise ConfigException("Report type not set")
Alex Savatieievd48994d2018-12-13 12:13:00 +0100257 _report = reporter.ReportToFile(
Alex41485522019-04-12 17:26:18 -0500258 _type,
savex4448e132018-04-25 15:51:14 +0200259 filename
260 )
Alex41485522019-04-12 17:26:18 -0500261 payload = {
Alexe0c5b9e2019-04-23 18:51:23 -0500262 "nodes": salt_master.nodes,
263 "mcp_release": salt_master.mcp_release,
264 "openstack_release": salt_master.openstack_release
Alex41485522019-04-12 17:26:18 -0500265 }
266 payload.update(self.presort_packages(self._packages, full))
267 _report(payload)
Alex Savatieiev799bee32019-02-20 17:19:26 -0600268 logger_cli.info("-> Done")