summaryrefslogtreecommitdiffstats
path: root/src/software/test/rpmcache.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/software/test/rpmcache.py')
-rw-r--r--src/software/test/rpmcache.py169
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.