savex | 4448e13 | 2018-04-25 15:51:14 +0200 | [diff] [blame] | 1 | import json |
savex | 4448e13 | 2018-04-25 15:51:14 +0200 | [diff] [blame] | 2 | |
Alex | 3ebc563 | 2019-04-18 16:47:18 -0500 | [diff] [blame] | 3 | from cfg_checker.common import const, logger_cli |
Alex | 4148552 | 2019-04-12 17:26:18 -0500 | [diff] [blame] | 4 | from cfg_checker.common.exception import ConfigException |
Alex | 4148552 | 2019-04-12 17:26:18 -0500 | [diff] [blame] | 5 | from cfg_checker.helpers.console_utils import Progress |
Alex | e0c5b9e | 2019-04-23 18:51:23 -0500 | [diff] [blame^] | 6 | from cfg_checker.nodes import salt_master |
Alex | 4148552 | 2019-04-12 17:26:18 -0500 | [diff] [blame] | 7 | from cfg_checker.reports import reporter |
| 8 | |
Alex | 3ebc563 | 2019-04-18 16:47:18 -0500 | [diff] [blame] | 9 | from versions import DebianVersion, PkgVersions, VersionCmpResult |
savex | 4448e13 | 2018-04-25 15:51:14 +0200 | [diff] [blame] | 10 | |
| 11 | |
Alex | e0c5b9e | 2019-04-23 18:51:23 -0500 | [diff] [blame^] | 12 | class CloudPackageChecker(object): |
Alex | 4148552 | 2019-04-12 17:26:18 -0500 | [diff] [blame] | 13 | @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'] |
Alex | e0c5b9e | 2019-04-23 18:51:23 -0500 | [diff] [blame^] | 55 | if not full: |
Alex | 4148552 | 2019-04-12 17:26:18 -0500 | [diff] [blame] | 56 | # 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: |
Alex | e0c5b9e | 2019-04-23 18:51:23 -0500 | [diff] [blame^] | 62 | # this package do not has any comments |
Alex | 4148552 | 2019-04-12 17:26:18 -0500 | [diff] [blame] | 63 | # ...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 | |
Alex | 4148552 | 2019-04-12 17:26:18 -0500 | [diff] [blame] | 96 | _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 | |
savex | 4448e13 | 2018-04-25 15:51:14 +0200 | [diff] [blame] | 113 | 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 Savatieiev | 42b89fa | 2019-03-07 18:45:26 -0600 | [diff] [blame] | 120 | logger_cli.info("# Collecting installed packages") |
Alex | e0c5b9e | 2019-04-23 18:51:23 -0500 | [diff] [blame^] | 121 | 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") |
savex | 4448e13 | 2018-04-25 15:51:14 +0200 | [diff] [blame] | 125 | |
Alex | e0c5b9e | 2019-04-23 18:51:23 -0500 | [diff] [blame^] | 126 | for key in salt_master.nodes.keys(): |
savex | 4448e13 | 2018-04-25 15:51:14 +0200 | [diff] [blame] | 127 | # due to much data to be passed from salt, it is happening in order |
| 128 | if key in _result: |
| 129 | _text = _result[key] |
Alex Savatieiev | fa5910a | 2019-03-18 18:12:24 -0500 | [diff] [blame] | 130 | try: |
| 131 | _dict = json.loads(_text[_text.find('{'):]) |
Alex | 3ebc563 | 2019-04-18 16:47:18 -0500 | [diff] [blame] | 132 | except ValueError: |
Alex Savatieiev | fa5910a | 2019-03-18 18:12:24 -0500 | [diff] [blame] | 133 | logger_cli.info("... no JSON for '{}'".format( |
| 134 | key |
| 135 | )) |
Alex | 3ebc563 | 2019-04-18 16:47:18 -0500 | [diff] [blame] | 136 | logger_cli.debug( |
| 137 | "ERROR:\n{}\n".format(_text[:_text.find('{')]) |
| 138 | ) |
Alex Savatieiev | fa5910a | 2019-03-18 18:12:24 -0500 | [diff] [blame] | 139 | _dict = {} |
Alex | 3ebc563 | 2019-04-18 16:47:18 -0500 | [diff] [blame] | 140 | |
Alex | e0c5b9e | 2019-04-23 18:51:23 -0500 | [diff] [blame^] | 141 | salt_master.nodes[key]['packages'] = _dict |
savex | 4448e13 | 2018-04-25 15:51:14 +0200 | [diff] [blame] | 142 | else: |
Alex | e0c5b9e | 2019-04-23 18:51:23 -0500 | [diff] [blame^] | 143 | salt_master.nodes[key]['packages'] = {} |
Alex Savatieiev | 42b89fa | 2019-03-07 18:45:26 -0600 | [diff] [blame] | 144 | logger_cli.debug("... {} has {} packages installed".format( |
savex | 4448e13 | 2018-04-25 15:51:14 +0200 | [diff] [blame] | 145 | key, |
Alex | e0c5b9e | 2019-04-23 18:51:23 -0500 | [diff] [blame^] | 146 | len(salt_master.nodes[key]['packages'].keys()) |
savex | 4448e13 | 2018-04-25 15:51:14 +0200 | [diff] [blame] | 147 | )) |
Alex Savatieiev | 799bee3 | 2019-02-20 17:19:26 -0600 | [diff] [blame] | 148 | logger_cli.info("-> Done") |
savex | 4448e13 | 2018-04-25 15:51:14 +0200 | [diff] [blame] | 149 | |
| 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 | """ |
Alex | 4148552 | 2019-04-12 17:26:18 -0500 | [diff] [blame] | 156 | # Preload OpenStack release versions |
| 157 | _desc = PkgVersions() |
Alex | 3ebc563 | 2019-04-18 16:47:18 -0500 | [diff] [blame] | 158 | |
| 159 | logger_cli.info( |
| 160 | "# Cross-comparing: Installed vs Candidates vs Release" |
| 161 | ) |
Alex | e0c5b9e | 2019-04-23 18:51:23 -0500 | [diff] [blame^] | 162 | _progress = Progress(len(salt_master.nodes.keys())) |
Alex | 4148552 | 2019-04-12 17:26:18 -0500 | [diff] [blame] | 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 = {} |
Alex | e0c5b9e | 2019-04-23 18:51:23 -0500 | [diff] [blame^] | 167 | for node_name, node_value in salt_master.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 |
Alex | e0c5b9e | 2019-04-23 18:51:23 -0500 | [diff] [blame^] | 188 | _os = salt_master.openstack_release |
| 189 | _mcp = salt_master.mcp_release |
Alex | 4148552 | 2019-04-12 17:26:18 -0500 | [diff] [blame] | 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 |
Alex | 3ebc563 | 2019-04-18 16:47:18 -0500 | [diff] [blame] | 199 | |
Alex | 4148552 | 2019-04-12 17:26:18 -0500 | [diff] [blame] | 200 | # get specific set for this OS release if present |
| 201 | if _os in _vers: |
Alex | 3ebc563 | 2019-04-18 16:47:18 -0500 | [diff] [blame] | 202 | _v = _vers[_os] |
Alex | 4148552 | 2019-04-12 17:26:18 -0500 | [diff] [blame] | 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 | 3ebc563 | 2019-04-18 16:47:18 -0500 | [diff] [blame] | 215 | |
Alex | 4148552 | 2019-04-12 17:26:18 -0500 | [diff] [blame] | 216 | _cmp = VersionCmpResult( |
| 217 | _ver_ins, |
| 218 | _ver_can, |
| 219 | _all_packages[_name]['r'] |
| 220 | ) |
Alex | 3ebc563 | 2019-04-18 16:47:18 -0500 | [diff] [blame] | 221 | |
Alex | 4148552 | 2019-04-12 17:26:18 -0500 | [diff] [blame] | 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() |
savex | 4448e13 | 2018-04-25 15:51:14 +0200 | [diff] [blame] | 243 | |
Alex | 4148552 | 2019-04-12 17:26:18 -0500 | [diff] [blame] | 244 | def create_report(self, filename, rtype, full=None): |
savex | 4448e13 | 2018-04-25 15:51:14 +0200 | [diff] [blame] | 245 | """ |
| 246 | Create static html showing packages diff per node |
| 247 | |
| 248 | :return: buff with html |
| 249 | """ |
Alex Savatieiev | 42b89fa | 2019-03-07 18:45:26 -0600 | [diff] [blame] | 250 | logger_cli.info("# Generating report to '{}'".format(filename)) |
Alex | 4148552 | 2019-04-12 17:26:18 -0500 | [diff] [blame] | 251 | 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 Savatieiev | d48994d | 2018-12-13 12:13:00 +0100 | [diff] [blame] | 257 | _report = reporter.ReportToFile( |
Alex | 4148552 | 2019-04-12 17:26:18 -0500 | [diff] [blame] | 258 | _type, |
savex | 4448e13 | 2018-04-25 15:51:14 +0200 | [diff] [blame] | 259 | filename |
| 260 | ) |
Alex | 4148552 | 2019-04-12 17:26:18 -0500 | [diff] [blame] | 261 | payload = { |
Alex | e0c5b9e | 2019-04-23 18:51:23 -0500 | [diff] [blame^] | 262 | "nodes": salt_master.nodes, |
| 263 | "mcp_release": salt_master.mcp_release, |
| 264 | "openstack_release": salt_master.openstack_release |
Alex | 4148552 | 2019-04-12 17:26:18 -0500 | [diff] [blame] | 265 | } |
| 266 | payload.update(self.presort_packages(self._packages, full)) |
| 267 | _report(payload) |
Alex Savatieiev | 799bee3 | 2019-02-20 17:19:26 -0600 | [diff] [blame] | 268 | logger_cli.info("-> Done") |