diff options
Diffstat (limited to 'src/software/test/rpmcache.py')
-rw-r--r-- | src/software/test/rpmcache.py | 169 |
1 files changed, 119 insertions, 50 deletions
diff --git a/src/software/test/rpmcache.py b/src/software/test/rpmcache.py index af64fe3..f68f128 100644 --- a/src/software/test/rpmcache.py +++ b/src/software/test/rpmcache.py @@ -27,7 +27,44 @@ import os import pickle import re from collections import defaultdict -from subprocess import call, check_output +from subprocess import call, check_output, CalledProcessError + +DB_BACKUP_FILE = 'lmi_software_test_cache' + +RE_AVAIL_PKG = re.compile( + r'^(?P<name>[^\s]+)\.(?P<arch>[a-zA-Z0-9_]+)' + r'\s+(?P<epoch>([0-9]+:)?)(?P<version>[a-zA-Z0-9._+-]+)' + r'-(?P<release>[a-zA-Z0-9_.]+)\s+' + r'(?P<repository>[a-zA-Z0-9_-]+)\s*$', re.MULTILINE) +# this won't match the last entry, unless "package\n" is not appended +# at the end of the string +RE_PKG_DEPS = re.compile( + r'^package:\s*(?P<name>[^\s]+)\.(?P<arch>[a-zA-Z0-9_]+)' + r'\s+(?P<epoch>([0-9]+:)?)(?P<version>[a-zA-Z0-9._+-]+)' + r'-(?P<release>[a-zA-Z0-9_.]+)\s+(?P<dep_list>.*?)' + r'(?=^package|\Z)', re.MULTILINE | re.DOTALL) +RE_DEPS_PROVIDERS = re.compile( + r'^\s*dependency:\s*(?P<dependency>.+?)\s*' + r'(?P<providers>(^\s+provider:.+?$)+)', re.MULTILINE | re.IGNORECASE) +RE_PROVIDER = re.compile( + r'^\s+provider:\s*(?P<name>[^\s]+)\.(?P<arch>[a-zA-Z0-9_]+)' + r'\s+(?P<epoch>([0-9]+:)?)(?P<version>[a-zA-Z0-9._+-]+)' + r'-(?P<release>[a-zA-Z0-9_.]+)\s*$', re.IGNORECASE | re.MULTILINE) +RE_PKG_INFO = re.compile( + r'^Name\s*:\s*(?P<name>[^\s]+).*?' + r'^(Epoch\s*:\s*(?P<epoch>[0-9]+)\s+)?' + r'^Version\s*:\s*(?P<version>[a-zA-Z0-9._+-]+)\s+' + r'^Release\s*:\s*(?P<release>[^\s]+)\s+.*?' + r'^Size\s*:\s*(?P<size>\d+(\.\d+)?)( *(?P<units>[kMG]))?', + re.MULTILINE | re.DOTALL | re.IGNORECASE) +RE_REPO = re.compile( + r'(?:^\*?)(?P<name>[^\s/]+\b)(?!\s+id)', re.MULTILINE | re.IGNORECASE) + +# maximum number of packages, that will be selected for testing +MAX_PKG_DB_SIZE = 5 +# step used to iterate over package names used to check for thery dependencies +# it's a number of packages, that will be passed to yum command at once +PKG_DEPS_ITER_STEP = 50 class InvalidTestCache(Exception): """Exception saying, that rpm test cache is not valiid.""" @@ -80,6 +117,9 @@ class Package(object): #pylint: disable=R0902 self._up_rel = up_rel self._up_repo = up_repo + def __str__(self): + return self.get_nevra() + @property def name(self): return self._name #pylint: disable=C0111,C0321 @property @@ -120,50 +160,30 @@ class Package(object): #pylint: disable=R0902 return make_nevra(*[getattr(self, '_'+a) for a in attrs], with_epoch=with_epoch) -DB_BACKUP_FILE = 'lmi_software_test_cache' - -RE_AVAIL_PKG = re.compile( - r'^(?P<name>[^\s]+)\.(?P<arch>[a-zA-Z0-9_]+)' - r'\s+(?P<epoch>([0-9]+:)?)(?P<version>[a-zA-Z0-9._+-]+)' - r'-(?P<release>[a-zA-Z0-9_.]+)\s+' - r'(?P<repository>[a-zA-Z0-9_-]+)\s*$', re.MULTILINE) -# this won't match the last entry, unless "package\n" is not appended -# at the end of the string -RE_PKG_DEPS = re.compile( - r'^package:\s*(?P<name>[^\s]+)\.(?P<arch>[a-zA-Z0-9_]+)' - r'\s+(?P<epoch>([0-9]+:)?)(?P<version>[a-zA-Z0-9._+-]+)' - r'-(?P<release>[a-zA-Z0-9_.]+)\s+(?P<dep_list>.*?)' - r'(?=^package|\Z)', re.MULTILINE | re.DOTALL) -RE_DEPS_PROVIDERS = re.compile( - r'^\s+provider:\s*(?P<name>[^\s]+)\.(?P<arch>[a-zA-Z0-9_]+)' - r'\s+(?P<epoch>([0-9]+:)?)(?P<version>[a-zA-Z0-9._+-]+)' - r'-(?P<release>[a-zA-Z0-9_.]+)\s*$', re.IGNORECASE | re.MULTILINE) -RE_PKG_INFO = re.compile( - r'^Name\s*:\s*(?P<name>[^\s]+).*?' - r'^(Epoch\s*:\s*(?P<epoch>[0-9]+)\s+)?' - r'^Version\s*:\s*(?P<version>[a-zA-Z0-9._+-]+)\s+' - r'^Release\s*:\s*(?P<release>[^\s]+)\s+.*?' - r'^Size\s*:\s*(?P<size>\d+(\.\d+)?)( *(?P<units>[kMG]))?', - re.MULTILINE | re.DOTALL | re.IGNORECASE) -RE_REPO = re.compile( - r'(?:^\*?)(?P<name>[^\s/]+\b)(?!\s+id)', re.MULTILINE | re.IGNORECASE) - -# maximum number of packages, that will be selected for testing -MAX_PKG_DB_SIZE = 3 -# step used to iterate over package names used to check for thery dependencies -# it's a number of packages, that will be passed to yum command at once -PKG_DEPS_ITER_STEP = 50 - def _match_nevr(match): """ @param match is a regexp match object with parsed rpm package @return tuple (name, epoch, version, release) """ + epoch = match.group('epoch') return ( match.group('name') - , match.group('epoch') + , epoch[:-1] if epoch else "0" , match.group('version') , match.group('release')) +def _match2pkg(match): + """ + @param match is a regexp match object with attributes: + name, epoch, version, release, arch + @return instance of Package + """ + return Package( + match.group('name'), + match.group('epoch')[:-1] if match.group('epoch') else '0', + match.group('version'), match.group('release'), + match.group('arch'), match.groupdict().get('repository', None), + None, None, None, None) + def _filter_duplicates(installed, avail_str): """ Parse output of "yum list available" command and retuns only those @@ -174,11 +194,6 @@ def _filter_duplicates(installed, avail_str): Each sublist of result contain at least 2 elements, that are instances of Package. """ - m2pkg = lambda m: Package(m.group('name'), - m.group('epoch')[:-1] if m.group('epoch') else '0', - m.group('version'), m.group('release'), - m.group('arch'), m.group('repository'), - None, None, None, None) dups_list = [] cur_package_matches = [] prev_match = None @@ -194,15 +209,38 @@ def _filter_duplicates(installed, avail_str): if prev_match and prev_match.group('name') != match.group('name'): if ( len(cur_package_matches) > 1 and not cur_package_matches[0].group('name') in installed): - pkgs = [ m2pkg(m) for m in cur_package_matches ] + pkgs = [ _match2pkg(m) for m in cur_package_matches ] dups_list.append(pkgs) cur_package_matches = [] cur_package_matches.append(match) prev_match = match if len(cur_package_matches) > 1: - dups_list.append([ m2pkg(m) for m in cur_package_matches ]) + dups_list.append([ _match2pkg(m) for m in cur_package_matches ]) return dups_list +def _check_single_pkg_deps( + installed, + dependencies_str): + """ + Each package has zero or more dependencies. Each dependency + has at least one provider. One of these provider must be installed + for each such dependency. + @param dependencies of single package + @return True if all dependencies have at least one provider installed + """ + for match_deps in RE_DEPS_PROVIDERS.finditer(dependencies_str): + providers = [] + for match_dep in RE_PROVIDER.finditer(match_deps.group('providers')): + if match_dep.group('name') not in installed: + continue + providers.append(_match2pkg(match_dep)) + for provider in providers: + if is_installed(provider, False): + break + else: # no provider is installed + return False + return True + def _check_pkg_dependencies( installed, dup_list, @@ -218,15 +256,28 @@ def _check_pkg_dependencies( dups_part = dup_list[i:i+PKG_DEPS_ITER_STEP] cmd = cmd[:2] for dups in dups_part: - cmd.append(dups[0].name) + cmd.extend([d.get_nevra(newer=False) for d in dups]) deplist_str = check_output(cmd) - for pkgs, match_pkg in zip(dups_part, - RE_PKG_DEPS.finditer(deplist_str)): - for match_dep in RE_DEPS_PROVIDERS.finditer( - match_pkg.group('dep_list')): - if match_dep.group('name') not in installed: + matches = RE_PKG_DEPS.finditer(deplist_str) + prev_match = None + for pkgs in dups_part: + satisfied = True + remains = len(pkgs) + for match_pkg in matches: + if ( prev_match + and _match_nevr(prev_match) == _match_nevr(match_pkg)): + # there sometimes appear duplicates in yum deplist + # output, so let's skip them + continue + if satisfied and not _check_single_pkg_deps( + installed, match_pkg.group('dep_list')): + satisfied = False + prev_match = match_pkg + remains -= 1 + if remains <= 0: break - else: + if satisfied: + # all packages from pkgs have satisfied dependencies dups_no_deps.append(pkgs) if len(dups_no_deps) >= number_of_packages: break @@ -317,6 +368,24 @@ def _make_rpm_path(pkg, cache_dir='', newer=True, without_epoch=False): with_epoch='NEVER' if without_epoch else 'NOT_ZERO') return os.path.join(cache_dir, nevra) + '.rpm' +def is_installed(pkg, newer=True): + """ + Check, whether package is installed. + """ + if not isinstance(pkg, Package): + return call(["rpm", "--quiet", "-q", pkg]) == 0 + else: + try: + cmd = [ "rpm", "-q", "--qf", "%{EPOCH}:%{NVRA}", pkg.name ] + out = check_output(cmd) + epoch, nvra = out.split(':') #pylint: disable=E1103 + if not epoch or epoch == "(none)": + epoch = "0" + return ( epoch == pkg.epoch + and nvra == pkg.get_nevra(newer=newer, with_epoch="NEVER")) + except CalledProcessError: + return False + def rpm_exists(pkg, cache_dir='', newer=True): """ @return True, when rpm package is in cache. |