| Alex | 4148552 | 2019-04-12 17:26:18 -0500 | [diff] [blame^] | 1 | import csv | 
|  | 2 | import os | 
|  | 3 |  | 
|  | 4 | from cfg_checker.common import config, logger, logger_cli, pkg_dir, const | 
|  | 5 |  | 
|  | 6 |  | 
|  | 7 | class PkgVersions(object): | 
|  | 8 | _labels = [] | 
|  | 9 | _list = {} | 
|  | 10 |  | 
|  | 11 | dummy_desc = { | 
|  | 12 | "component": "unlisted", | 
|  | 13 | "app": "-", | 
|  | 14 | "repo": "-", | 
|  | 15 | "versions": {} | 
|  | 16 | } | 
|  | 17 |  | 
|  | 18 | def __init__(self): | 
|  | 19 | # preload csv file | 
|  | 20 | logger_cli.info("# Preloading MCP release versions") | 
|  | 21 | with open(os.path.join(pkg_dir, 'etc', config.pkg_versions_map)) as f: | 
|  | 22 | _reader = csv.reader(f, delimiter=',') | 
|  | 23 | # load packages | 
|  | 24 | for row in _reader: | 
|  | 25 | # load release version labels | 
|  | 26 | if _reader.line_num == 1: | 
|  | 27 | self._labels = [v for v in row[5:]] | 
|  | 28 | continue | 
|  | 29 | # package_name,component,application_or_service,repo,openstack_release,2018.4.0,2018.11.0,2019.2.0,2019.2.1,2019.2.2 | 
|  | 30 | # reassign for code readability | 
|  | 31 | _pkg = row[0] | 
|  | 32 | _component = row[1] | 
|  | 33 | _app = row[2] | 
|  | 34 | _repo = row[3] | 
|  | 35 | # if release cell empty - use keyword 'any' | 
|  | 36 | _os_release = row[4] if len(row[4]) > 0 else 'any' | 
|  | 37 |  | 
|  | 38 | # prepare versions dict | 
|  | 39 | _l = self._labels | 
|  | 40 | _versions = {_l[i]:row[5+i] for i in range(0, len(row[5:]))} | 
|  | 41 |  | 
|  | 42 | if _pkg in self._list: | 
|  | 43 | if _os_release in self._list[_pkg]["versions"]: | 
|  | 44 | # all pkg/os_releases should be uniq. If found, latest one used | 
|  | 45 | logger_cli.info( | 
|  | 46 | "-> WARNING: Duplicate package info found " | 
|  | 47 | "'{}' (line {})".format( | 
|  | 48 | _pkg, | 
|  | 49 | _reader.line_num | 
|  | 50 | ) | 
|  | 51 | ) | 
|  | 52 | else: | 
|  | 53 | # update pkg data in list | 
|  | 54 | self._list.update({ | 
|  | 55 | _pkg: { | 
|  | 56 | "component": _component, | 
|  | 57 | "app": _app, | 
|  | 58 | "repo": _repo, | 
|  | 59 | "versions": {} | 
|  | 60 | } | 
|  | 61 | }) | 
|  | 62 |  | 
|  | 63 | # and finally, update the versions for this release | 
|  | 64 | self._list[_pkg]["versions"].update({ | 
|  | 65 | _os_release: _versions | 
|  | 66 | }) | 
|  | 67 |  | 
|  | 68 | def __getitem__(self, pkg_name): | 
|  | 69 | if pkg_name in self._list: | 
|  | 70 | return self._list[pkg_name] | 
|  | 71 | else: | 
|  | 72 | #return self._dummy_desc | 
|  | 73 | return None | 
|  | 74 |  | 
|  | 75 |  | 
|  | 76 | class DebianVersion(object): | 
|  | 77 | epoch = None | 
|  | 78 | epoch_status = const.VERSION_NA | 
|  | 79 | upstream = None | 
|  | 80 | upstream_rev = None | 
|  | 81 | upstream_status = const.VERSION_NA | 
|  | 82 | debian = None | 
|  | 83 | debian_rev = None | 
|  | 84 | debian_status = const.VERSION_NA | 
|  | 85 |  | 
|  | 86 | status = "" | 
|  | 87 | version = "" | 
|  | 88 |  | 
|  | 89 | @staticmethod | 
|  | 90 | def split_revision(version_fragment): | 
|  | 91 | # The symbols are -, +, ~ | 
|  | 92 | _symbols = ['-', '+', '~'] | 
|  | 93 | # nums, coz it is faster then regex | 
|  | 94 | _chars = [46, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57] | 
|  | 95 | _ord_map = [ord(ch) not in _chars for ch in version_fragment] | 
|  | 96 | # if there is nothing to extract, return at once | 
|  | 97 | if not any([_s in version_fragment for _s in _symbols]) \ | 
|  | 98 | and not any(_ord_map): | 
|  | 99 | # no revisions | 
|  | 100 | return version_fragment, "" | 
|  | 101 | else: | 
|  | 102 | _main = _rev = "" | 
|  | 103 | # get indices | 
|  | 104 | _indices = [] | 
|  | 105 | for _s in _symbols: | 
|  | 106 | if _s in version_fragment: | 
|  | 107 | _indices.append(version_fragment.index(_s)) | 
|  | 108 | for _s in version_fragment: | 
|  | 109 | if ord(_s) not in _chars: | 
|  | 110 | _indices.append(version_fragment.index(_s)) | 
|  | 111 | # sort indices | 
|  | 112 | _indices.sort() | 
|  | 113 | # extract starting from the lowest one | 
|  | 114 | _main = version_fragment[:_indices[0]] | 
|  | 115 | _rev = version_fragment[_indices[0]:] | 
|  | 116 | return _main, _rev | 
|  | 117 |  | 
|  | 118 | def __init__(self, version_string): | 
|  | 119 | # save | 
|  | 120 | if len(version_string) < 1: | 
|  | 121 | self.epoch = None | 
|  | 122 | self.upstream = None | 
|  | 123 | self.debian = None | 
|  | 124 | self.version = 'n/a' | 
|  | 125 | return | 
|  | 126 | else: | 
|  | 127 | # do parse the main versions | 
|  | 128 | _v = version_string | 
|  | 129 | # colon presence, means epoch present | 
|  | 130 | _e = _v.split(':', 1)[0] if ':' in _v else '' | 
|  | 131 | # if epoch was there, upstream should be cut | 
|  | 132 | _m = _v if ':' not in _v else _v.split(':', 1)[1] | 
|  | 133 | # dash presence, means debian present | 
|  | 134 | _d = _m.rsplit('-', 1)[1] if '-' in _m else '' | 
|  | 135 | # if debian was there, upstream version should be cut | 
|  | 136 | _m = _m if '-' not in _m else _m.rsplit('-', 1)[0] | 
|  | 137 |  | 
|  | 138 | self.epoch = _e | 
|  | 139 | self.upstream, self.upstream_rev = self.split_revision(_m) | 
|  | 140 | self.debian, self.debian_rev = self.split_revision(_d) | 
|  | 141 | self.version = version_string | 
|  | 142 |  | 
|  | 143 | # Following functions is a freestyle python mimic of apt's upstream, enjoy | 
|  | 144 | # https://github.com/chaos/apt/blob/master/apt/apt-pkg/deb/debversion.cc#L42 | 
|  | 145 | # mimic produced in order not to pull any packages or call external code | 
|  | 146 | @staticmethod | 
|  | 147 | def _cmp_fragment(lhf, rhf): | 
|  | 148 | # search for difference | 
|  | 149 | # indices | 
|  | 150 | _li = _ri = 0 | 
|  | 151 | # pre-calc len | 
|  | 152 | _lL = len(lhf) | 
|  | 153 | _rL = len(rhf) | 
|  | 154 | # bool for compare found | 
|  | 155 | _diff = False | 
|  | 156 | while _li < _lL and _ri < _rL: | 
|  | 157 | # iterate lists | 
|  | 158 | _num = lhf[_li] - rhf[_ri] | 
|  | 159 | if _num: | 
|  | 160 | return _num | 
|  | 161 | _li += 1 | 
|  | 162 | _ri += 1 | 
|  | 163 |  | 
|  | 164 | # diff found? lens equal? | 
|  | 165 | if not _diff and _lL != _rL: | 
|  | 166 | # lens not equal? Longer - later | 
|  | 167 | return _lL - _rL | 
|  | 168 | else: | 
|  | 169 | # equal | 
|  | 170 | return 0 | 
|  | 171 |  | 
|  | 172 | def _cmp_num(self, lf, rf): | 
|  | 173 | # split fragments into lists | 
|  | 174 | _lhf = lf.split('.') if '.' in lf else list(lf) | 
|  | 175 | _rhf = rf.split('.') if '.' in rf else list(rf) | 
|  | 176 | # cast them to ints, delete empty strs | 
|  | 177 | _lhf = [int(n) for n in _lhf if len(n)] | 
|  | 178 | _rhf = [int(n) for n in _rhf if len(n)] | 
|  | 179 |  | 
|  | 180 | return self._cmp_fragment(_lhf, _rhf) | 
|  | 181 |  | 
|  | 182 | def _cmp_lex(self, lf, rf): | 
|  | 183 | # cast each item into its ORD value | 
|  | 184 | _lhf = [ord(n) for n in lf] | 
|  | 185 | _rhf = [ord(n) for n in rf] | 
|  | 186 |  | 
|  | 187 | return self._cmp_fragment(_lhf, _rhf) | 
|  | 188 | # end of cmps | 
|  | 189 |  | 
|  | 190 | # main part compared using splitted numbers | 
|  | 191 | # if equal, revision is compared using lexical comparizon | 
|  | 192 | def __lt__(self, v): | 
|  | 193 | if self._cmp_num(self.epoch, v.epoch) < 0: | 
|  | 194 | return True | 
|  | 195 | elif self._cmp_num(self.upstream, v.upstream) < 0: | 
|  | 196 | return True | 
|  | 197 | elif self._cmp_lex(self.upstream_rev, v.upstream_rev) < 0: | 
|  | 198 | return True | 
|  | 199 | else: | 
|  | 200 | return False | 
|  | 201 |  | 
|  | 202 | def __eq__(self, v): | 
|  | 203 | # compare all portions | 
|  | 204 | _result = [] | 
|  | 205 | _result.append(self._cmp_num(self.epoch, v.epoch)) | 
|  | 206 | _result.append(self._cmp_num(self.upstream, v.upstream)) | 
|  | 207 | _result.append(self._cmp_lex(self.upstream_rev, v.upstream_rev)) | 
|  | 208 | # if there is any non-zero, its not equal | 
|  | 209 | return not any(_result) | 
|  | 210 |  | 
|  | 211 | def __gt__(self, v): | 
|  | 212 | if self._cmp_num(self.epoch, v.epoch) > 0: | 
|  | 213 | return True | 
|  | 214 | elif self._cmp_num(self.upstream, v.upstream) > 0: | 
|  | 215 | return True | 
|  | 216 | elif self._cmp_lex(self.upstream_rev, v.upstream_rev) > 0: | 
|  | 217 | return True | 
|  | 218 | else: | 
|  | 219 | return False | 
|  | 220 |  | 
|  | 221 | def update_parts(self, target, status): | 
|  | 222 | # updating parts of version statuses | 
|  | 223 | if self._cmp_num(self.epoch, target.epoch) != 0: | 
|  | 224 | self.epoch_status = status | 
|  | 225 | else: | 
|  | 226 | self.epoch_status = const.VERSION_OK | 
|  | 227 |  | 
|  | 228 | if self._cmp_num(self.upstream, target.upstream) != 0 \ | 
|  | 229 | or self._cmp_lex(self.upstream_rev, target.upstream_rev) != 0: | 
|  | 230 | self.upstream_status = status | 
|  | 231 | else: | 
|  | 232 | self.upstream_status = const.VERSION_OK | 
|  | 233 |  | 
|  | 234 | if self._cmp_lex(self.debian, target.debian) != 0 \ | 
|  | 235 | or self._cmp_lex(self.debian_rev, target.debian_rev) != 0: | 
|  | 236 | self.debian_status = status | 
|  | 237 | else: | 
|  | 238 | self.debian_status = const.VERSION_OK | 
|  | 239 |  | 
|  | 240 |  | 
|  | 241 | class VersionCmpResult(object): | 
|  | 242 | status = "" | 
|  | 243 | action = "" | 
|  | 244 |  | 
|  | 245 | source = None | 
|  | 246 | target = None | 
|  | 247 |  | 
|  | 248 |  | 
|  | 249 | def __init__(self, i, c, r): | 
|  | 250 | # compare three versions and write a result | 
|  | 251 | self.source = i | 
|  | 252 | self.status = const.VERSION_NA | 
|  | 253 | self.action = const.ACT_NA | 
|  | 254 |  | 
|  | 255 | # Check if there is a release version present | 
|  | 256 | if r and len(r.version) > 0 and r.version != 'n/a': | 
|  | 257 | # I < C, installed version is older | 
|  | 258 | if i < c: | 
|  | 259 | self.target = c | 
|  | 260 | if i == r: | 
|  | 261 | # installed version is equal vs release version | 
|  | 262 | self.status = const.VERSION_OK | 
|  | 263 | self.action = const.ACT_UPGRADE | 
|  | 264 | elif i > r: | 
|  | 265 | # installed version is newer vs release version | 
|  | 266 | self.status = const.VERSION_UP | 
|  | 267 | self.action = const.ACT_UPGRADE | 
|  | 268 | elif i < r and r < c: | 
|  | 269 | # installed version is older vs release version | 
|  | 270 | self.status = const.VERSION_ERR | 
|  | 271 | self.action = const.ACT_NEED_UP | 
|  | 272 | self.target = r | 
|  | 273 | elif i < r and c == r: | 
|  | 274 | # installed version is older vs release version | 
|  | 275 | self.status = const.VERSION_ERR | 
|  | 276 | self.action = const.ACT_NEED_UP | 
|  | 277 | self.target = c | 
|  | 278 | elif c < r: | 
|  | 279 | # installed and repo versions older vs release version | 
|  | 280 | self.status = const.VERSION_ERR | 
|  | 281 | self.action = const.ACT_REPO | 
|  | 282 | # I > C | 
|  | 283 | # installed version is newer | 
|  | 284 | elif i > c: | 
|  | 285 | self.target = c | 
|  | 286 | if c == r: | 
|  | 287 | # some unknown version installed | 
|  | 288 | self.status = const.VERSION_ERR | 
|  | 289 | self.action = const.ACT_NEED_DOWN | 
|  | 290 | elif c > r: | 
|  | 291 | # installed and repo versions newer than release | 
|  | 292 | self.status = const.VERSION_UP | 
|  | 293 | self.action = const.ACT_NEED_DOWN | 
|  | 294 | elif c < r and r < i: | 
|  | 295 | # repo is older vs release and both older vs installed | 
|  | 296 | self.status = const.VERSION_UP | 
|  | 297 | self.action = const.ACT_REPO | 
|  | 298 | elif c < r and r == i: | 
|  | 299 | # repo is older vs release, but release version installed | 
|  | 300 | self.status = const.VERSION_OK | 
|  | 301 | self.action = const.ACT_REPO | 
|  | 302 | elif i < r: | 
|  | 303 | # both repo and installed older vs release, new target | 
|  | 304 | self.status = const.VERSION_DOWN | 
|  | 305 | self.action = const.ACT_REPO | 
|  | 306 | self.target = r | 
|  | 307 | # I = C | 
|  | 308 | # installed and linked repo is inline, | 
|  | 309 | elif i == c: | 
|  | 310 | self.target = c | 
|  | 311 | if i < r: | 
|  | 312 | # both are old, new target | 
|  | 313 | self.status = const.VERSION_ERR | 
|  | 314 | self.action = const.ACT_REPO | 
|  | 315 | self.target = r | 
|  | 316 | elif i > r: | 
|  | 317 | # both are newer, same target | 
|  | 318 | self.status = const.VERSION_UP | 
|  | 319 | self.action = const.ACT_NA | 
|  | 320 | elif i == r: | 
|  | 321 | # all is ok | 
|  | 322 | self.status = const.VERSION_OK | 
|  | 323 | self.action = const.ACT_NA | 
|  | 324 | else: | 
|  | 325 | # no release version present | 
|  | 326 | self.target = c | 
|  | 327 | if i < c: | 
|  | 328 | self.status = const.VERSION_OK | 
|  | 329 | self.action = const.ACT_UPGRADE | 
|  | 330 | elif i > c: | 
|  | 331 | self.status = const.VERSION_UP | 
|  | 332 | self.action = const.ACT_NEED_DOWN | 
|  | 333 | elif i == c: | 
|  | 334 | self.status = const.VERSION_OK | 
|  | 335 | self.action = const.ACT_NA | 
|  | 336 |  | 
|  | 337 | # and we need to update per-part status | 
|  | 338 | self.source.update_parts(self.target, self.status) | 
|  | 339 |  | 
|  | 340 | @staticmethod | 
|  | 341 | def deb_lower(_s, _t): | 
|  | 342 | if _t.debian and _t.debian > _s.debian: | 
|  | 343 | return True | 
|  | 344 | else: | 
|  | 345 | return false |