savex | 4448e13 | 2018-04-25 15:51:14 +0200 | [diff] [blame] | 1 | import json |
| 2 | import os |
Alex Savatieiev | c905571 | 2019-03-01 14:43:56 -0600 | [diff] [blame] | 3 | #import sys |
savex | 4448e13 | 2018-04-25 15:51:14 +0200 | [diff] [blame] | 4 | |
| 5 | from copy import deepcopy |
| 6 | |
Alex | 4148552 | 2019-04-12 17:26:18 -0500 | [diff] [blame^] | 7 | from cfg_checker.common.exception import ConfigException |
Alex Savatieiev | 5118de0 | 2019-02-20 15:50:42 -0600 | [diff] [blame] | 8 | from cfg_checker.common import utils, const |
| 9 | from cfg_checker.common import config, logger, logger_cli, pkg_dir |
| 10 | from cfg_checker.common import salt_utils |
Alex | 4148552 | 2019-04-12 17:26:18 -0500 | [diff] [blame^] | 11 | from cfg_checker.helpers.console_utils import Progress |
Alex Savatieiev | 9b2f651 | 2019-02-20 18:05:00 -0600 | [diff] [blame] | 12 | from cfg_checker.nodes import SaltNodes, node_tmpl |
Alex | 4148552 | 2019-04-12 17:26:18 -0500 | [diff] [blame^] | 13 | from cfg_checker.reports import reporter |
| 14 | |
| 15 | from versions import PkgVersions, DebianVersion, VersionCmpResult |
savex | 4448e13 | 2018-04-25 15:51:14 +0200 | [diff] [blame] | 16 | |
| 17 | |
Alex Savatieiev | 9b2f651 | 2019-02-20 18:05:00 -0600 | [diff] [blame] | 18 | class CloudPackageChecker(SaltNodes): |
Alex | 4148552 | 2019-04-12 17:26:18 -0500 | [diff] [blame^] | 19 | @staticmethod |
| 20 | def presort_packages(all_packages, full=None): |
| 21 | logger_cli.info("-> Presorting packages") |
| 22 | # labels |
| 23 | _data = {} |
| 24 | _data = { |
| 25 | "cs": { |
| 26 | "ok": const.VERSION_OK, |
| 27 | "up": const.VERSION_UP, |
| 28 | "down": const.VERSION_DOWN, |
| 29 | "err": const.VERSION_ERR |
| 30 | }, |
| 31 | "ca": { |
| 32 | "na": const.ACT_NA, |
| 33 | "up": const.ACT_UPGRADE, |
| 34 | "need_up": const.ACT_NEED_UP, |
| 35 | "need_down": const.ACT_NEED_DOWN, |
| 36 | "repo": const.ACT_REPO |
| 37 | } |
| 38 | } |
| 39 | _data['status_err'] = const.VERSION_ERR |
| 40 | _data['status_down'] = const.VERSION_DOWN |
| 41 | |
| 42 | # Presort packages |
| 43 | _data['critical'] = {} |
| 44 | _data['system'] = {} |
| 45 | _data['other'] = {} |
| 46 | _data['unlisted'] = {} |
| 47 | |
| 48 | _l = len(all_packages) |
| 49 | _progress = Progress(_l) |
| 50 | _progress_index = 0 |
| 51 | # counters |
| 52 | _ec = _es = _eo = _eu = 0 |
| 53 | _dc = _ds = _do = _du = 0 |
| 54 | while _progress_index < _l: |
| 55 | # progress bar |
| 56 | _progress_index += 1 |
| 57 | _progress.write_progress(_progress_index) |
| 58 | # sort packages |
| 59 | _pn, _val = all_packages.popitem() |
| 60 | _c = _val['desc']['component'] |
| 61 | if full: |
| 62 | # Check if this packet has errors |
| 63 | # if all is ok -> just skip it |
| 64 | _max_status = max(_val['results'].keys()) |
| 65 | if _max_status <= const.VERSION_OK: |
| 66 | _max_action = max(_val['results'][_max_status].keys()) |
| 67 | if _max_action == const.ACT_NA: |
| 68 | # this package do not ha any comments |
| 69 | # ...just skip it from report |
| 70 | continue |
| 71 | |
| 72 | if len(_c) > 0 and _c == 'unlisted': |
| 73 | # not listed package in version lib |
| 74 | _data['unlisted'].update({ |
| 75 | _pn: _val |
| 76 | }) |
| 77 | _eu += _val['results'].keys().count(const.VERSION_ERR) |
| 78 | _du += _val['results'].keys().count(const.VERSION_DOWN) |
| 79 | # mirantis/critical |
| 80 | elif len(_c) > 0 and _c != 'System': |
| 81 | # not blank and not system |
| 82 | _data['critical'].update({ |
| 83 | _pn: _val |
| 84 | }) |
| 85 | _ec += _val['results'].keys().count(const.VERSION_ERR) |
| 86 | _dc += _val['results'].keys().count(const.VERSION_DOWN) |
| 87 | # system |
| 88 | elif _c == 'System': |
| 89 | _data['system'].update({ |
| 90 | _pn: _val |
| 91 | }) |
| 92 | _es += _val['results'].keys().count(const.VERSION_ERR) |
| 93 | _ds += _val['results'].keys().count(const.VERSION_DOWN) |
| 94 | # rest |
| 95 | else: |
| 96 | _data['other'].update({ |
| 97 | _pn: _val |
| 98 | }) |
| 99 | _eo += _val['results'].keys().count(const.VERSION_ERR) |
| 100 | _do += _val['results'].keys().count(const.VERSION_DOWN) |
| 101 | |
| 102 | |
| 103 | _progress.newline() |
| 104 | |
| 105 | _data['errors'] = { |
| 106 | 'mirantis': _ec, |
| 107 | 'system': _es, |
| 108 | 'other': _eo, |
| 109 | 'unlisted': _eu |
| 110 | } |
| 111 | _data['downgrades'] = { |
| 112 | 'mirantis': _dc, |
| 113 | 'system': _ds, |
| 114 | 'other': _do, |
| 115 | 'unlisted': _du |
| 116 | } |
| 117 | |
| 118 | return _data |
| 119 | |
savex | 4448e13 | 2018-04-25 15:51:14 +0200 | [diff] [blame] | 120 | def collect_installed_packages(self): |
| 121 | """ |
| 122 | Collect installed packages on each node |
| 123 | sets 'installed' dict property in the class |
| 124 | |
| 125 | :return: none |
| 126 | """ |
Alex Savatieiev | 42b89fa | 2019-03-07 18:45:26 -0600 | [diff] [blame] | 127 | logger_cli.info("# Collecting installed packages") |
Alex Savatieiev | 01f0d7f | 2019-03-07 17:53:29 -0600 | [diff] [blame] | 128 | _result = self.execute_script_on_active_nodes("pkg_versions.py") |
savex | 4448e13 | 2018-04-25 15:51:14 +0200 | [diff] [blame] | 129 | |
savex | 4448e13 | 2018-04-25 15:51:14 +0200 | [diff] [blame] | 130 | for key in self.nodes.keys(): |
| 131 | # due to much data to be passed from salt, it is happening in order |
| 132 | if key in _result: |
| 133 | _text = _result[key] |
Alex Savatieiev | fa5910a | 2019-03-18 18:12:24 -0500 | [diff] [blame] | 134 | try: |
| 135 | _dict = json.loads(_text[_text.find('{'):]) |
| 136 | except ValueError as e: |
| 137 | logger_cli.info("... no JSON for '{}'".format( |
| 138 | key |
| 139 | )) |
Alex Savatieiev | 3db12a7 | 2019-03-22 16:32:31 -0500 | [diff] [blame] | 140 | logger_cli.debug("ERROR:\n{}\n".format(_text[:_text.find('{')])) |
Alex Savatieiev | fa5910a | 2019-03-18 18:12:24 -0500 | [diff] [blame] | 141 | _dict = {} |
| 142 | |
savex | 4448e13 | 2018-04-25 15:51:14 +0200 | [diff] [blame] | 143 | self.nodes[key]['packages'] = _dict |
| 144 | else: |
| 145 | self.nodes[key]['packages'] = {} |
Alex Savatieiev | 42b89fa | 2019-03-07 18:45:26 -0600 | [diff] [blame] | 146 | logger_cli.debug("... {} has {} packages installed".format( |
savex | 4448e13 | 2018-04-25 15:51:14 +0200 | [diff] [blame] | 147 | key, |
| 148 | len(self.nodes[key]['packages'].keys()) |
| 149 | )) |
Alex Savatieiev | 799bee3 | 2019-02-20 17:19:26 -0600 | [diff] [blame] | 150 | logger_cli.info("-> Done") |
savex | 4448e13 | 2018-04-25 15:51:14 +0200 | [diff] [blame] | 151 | |
| 152 | def collect_packages(self): |
| 153 | """ |
| 154 | Check package versions in repos vs installed |
| 155 | |
| 156 | :return: no return values, all date put to dict in place |
| 157 | """ |
Alex | 4148552 | 2019-04-12 17:26:18 -0500 | [diff] [blame^] | 158 | # Preload OpenStack release versions |
| 159 | _desc = PkgVersions() |
| 160 | |
| 161 | logger_cli.info("# Cross-comparing: Installed vs Candidates vs Release") |
| 162 | _progress = Progress(len(self.nodes.keys())) |
| 163 | _progress_index = 0 |
| 164 | _total_processed = 0 |
Alex Savatieiev | 3db12a7 | 2019-03-22 16:32:31 -0500 | [diff] [blame] | 165 | # Collect packages from all of the nodes in flat dict |
Alex | 4148552 | 2019-04-12 17:26:18 -0500 | [diff] [blame^] | 166 | _all_packages = {} |
savex | 4448e13 | 2018-04-25 15:51:14 +0200 | [diff] [blame] | 167 | for node_name, node_value in self.nodes.iteritems(): |
Alex | 4148552 | 2019-04-12 17:26:18 -0500 | [diff] [blame^] | 168 | _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 Savatieiev | 3db12a7 | 2019-03-22 16:32:31 -0500 | [diff] [blame] | 179 | for _name, _value in node_value['packages'].iteritems(): |
Alex | 4148552 | 2019-04-12 17:26:18 -0500 | [diff] [blame^] | 180 | _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 |
| 188 | _os = self.openstack_release |
| 189 | _mcp = self.mcp_release |
| 190 | _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 |
| 199 | |
| 200 | # get specific set for this OS release if present |
| 201 | if _os in _vers: |
| 202 | _v = _vers[_os] |
| 203 | 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 Savatieiev | 3db12a7 | 2019-03-22 16:32:31 -0500 | [diff] [blame] | 214 | } |
Alex | 4148552 | 2019-04-12 17:26:18 -0500 | [diff] [blame^] | 215 | |
| 216 | _cmp = VersionCmpResult( |
| 217 | _ver_ins, |
| 218 | _ver_can, |
| 219 | _all_packages[_name]['r'] |
| 220 | ) |
| 221 | |
| 222 | # 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 | } |
savex | 4448e13 | 2018-04-25 15:51:14 +0200 | [diff] [blame] | 240 | |
Alex | 4148552 | 2019-04-12 17:26:18 -0500 | [diff] [blame^] | 241 | self._packages = _all_packages |
| 242 | _progress.newline() |
| 243 | |
savex | 4448e13 | 2018-04-25 15:51:14 +0200 | [diff] [blame] | 244 | |
Alex | 4148552 | 2019-04-12 17:26:18 -0500 | [diff] [blame^] | 245 | def create_report(self, filename, rtype, full=None): |
savex | 4448e13 | 2018-04-25 15:51:14 +0200 | [diff] [blame] | 246 | """ |
| 247 | Create static html showing packages diff per node |
| 248 | |
| 249 | :return: buff with html |
| 250 | """ |
Alex Savatieiev | 42b89fa | 2019-03-07 18:45:26 -0600 | [diff] [blame] | 251 | logger_cli.info("# Generating report to '{}'".format(filename)) |
Alex | 4148552 | 2019-04-12 17:26:18 -0500 | [diff] [blame^] | 252 | if rtype == 'html': |
| 253 | _type = reporter.HTMLPackageCandidates() |
| 254 | elif rtype == 'csv': |
| 255 | _type = reporter.CSVAllPackages() |
| 256 | else: |
| 257 | raise ConfigException("Report type not set") |
Alex Savatieiev | d48994d | 2018-12-13 12:13:00 +0100 | [diff] [blame] | 258 | _report = reporter.ReportToFile( |
Alex | 4148552 | 2019-04-12 17:26:18 -0500 | [diff] [blame^] | 259 | _type, |
savex | 4448e13 | 2018-04-25 15:51:14 +0200 | [diff] [blame] | 260 | filename |
| 261 | ) |
Alex | 4148552 | 2019-04-12 17:26:18 -0500 | [diff] [blame^] | 262 | payload = { |
Alex Savatieiev | 36b938d | 2019-01-21 11:01:18 +0100 | [diff] [blame] | 263 | "nodes": self.nodes, |
Alex | 4148552 | 2019-04-12 17:26:18 -0500 | [diff] [blame^] | 264 | "mcp_release": self.mcp_release, |
| 265 | "openstack_release": self.openstack_release |
| 266 | } |
| 267 | payload.update(self.presort_packages(self._packages, full)) |
| 268 | _report(payload) |
Alex Savatieiev | 799bee3 | 2019-02-20 17:19:26 -0600 | [diff] [blame] | 269 | logger_cli.info("-> Done") |