Index: client/tests/kvm/kvm_utils.py |
diff --git a/client/tests/kvm/kvm_utils.py b/client/tests/kvm/kvm_utils.py |
index de52b65c4b99e40a74cff019a255b467740814c1..38c4afe335ce7bcb720749416b9bc82777d95010 100644 |
--- a/client/tests/kvm/kvm_utils.py |
+++ b/client/tests/kvm/kvm_utils.py |
@@ -6,6 +6,7 @@ KVM test utility functions. |
import time, string, random, socket, os, signal, re, logging, commands, cPickle |
import fcntl, shelve, ConfigParser, rss_file_transfer, threading, sys, UserDict |
+import inspect |
from autotest_lib.client.bin import utils, os_dep |
from autotest_lib.client.common_lib import error, logging_config |
import kvm_subprocess |
@@ -675,7 +676,7 @@ def wait_for_login(client, host, port, username, password, prompt, linesep="\n", |
linesep, log_filename, internal_timeout) |
-def _remote_scp(session, password, transfer_timeout=600, login_timeout=10): |
+def _remote_scp(session, password_list, transfer_timeout=600, login_timeout=10): |
""" |
Transfer file(s) to a remote host (guest) using SCP. Wait for questions |
and provide answers. If login_timeout expires while waiting for output |
@@ -685,7 +686,7 @@ def _remote_scp(session, password, transfer_timeout=600, login_timeout=10): |
@brief: Transfer files using SCP, given a command line. |
@param session: An Expect or ShellSession instance to operate on |
- @param password: The password to send in reply to a password prompt. |
+ @param password_list: Password list to send in reply to the password prompt |
@param transfer_timeout: The time duration (in seconds) to wait for the |
transfer to complete. |
@param login_timeout: The maximal time duration (in seconds) to wait for |
@@ -701,6 +702,8 @@ def _remote_scp(session, password, transfer_timeout=600, login_timeout=10): |
timeout = login_timeout |
authentication_done = False |
+ scp_type = len(password_list) |
+ |
while True: |
try: |
match, text = session.read_until_last_line_matches( |
@@ -712,8 +715,18 @@ def _remote_scp(session, password, transfer_timeout=600, login_timeout=10): |
continue |
elif match == 1: # "password:" |
if password_prompt_count == 0: |
- logging.debug("Got password prompt; sending '%s'", password) |
- session.sendline(password) |
+ logging.debug("Got password prompt; sending '%s'" % |
+ password_list[password_prompt_count]) |
+ session.sendline(password_list[password_prompt_count]) |
+ password_prompt_count += 1 |
+ timeout = transfer_timeout |
+ if scp_type == 1: |
+ authentication_done = True |
+ continue |
+ elif password_prompt_count == 1 and scp_type == 2: |
+ logging.debug("Got password prompt; sending '%s'" % |
+ password_list[password_prompt_count]) |
+ session.sendline(password_list[password_prompt_count]) |
password_prompt_count += 1 |
timeout = transfer_timeout |
authentication_done = True |
@@ -736,7 +749,7 @@ def _remote_scp(session, password, transfer_timeout=600, login_timeout=10): |
raise SCPTransferFailedError(e.status, e.output) |
-def remote_scp(command, password, log_filename=None, transfer_timeout=600, |
+def remote_scp(command, password_list, log_filename=None, transfer_timeout=600, |
login_timeout=10): |
""" |
Transfer file(s) to a remote host (guest) using SCP. |
@@ -745,7 +758,7 @@ def remote_scp(command, password, log_filename=None, transfer_timeout=600, |
@param command: The command to execute |
(e.g. "scp -r foobar root@localhost:/tmp/"). |
- @param password: The password to send in reply to a password prompt. |
+ @param password_list: Password list to send in reply to a password prompt. |
@param log_filename: If specified, log all output to this file |
@param transfer_timeout: The time duration (in seconds) to wait for the |
transfer to complete. |
@@ -766,7 +779,7 @@ def remote_scp(command, password, log_filename=None, transfer_timeout=600, |
output_func=output_func, |
output_params=output_params) |
try: |
- _remote_scp(session, password, transfer_timeout, login_timeout) |
+ _remote_scp(session, password_list, transfer_timeout, login_timeout) |
finally: |
session.close() |
@@ -789,7 +802,10 @@ def scp_to_remote(host, port, username, password, local_path, remote_path, |
command = ("scp -v -o UserKnownHostsFile=/dev/null " |
"-o PreferredAuthentications=password -r -P %s %s %s@%s:%s" % |
(port, local_path, username, host, remote_path)) |
- remote_scp(command, password, log_filename, timeout) |
+ password_list = [] |
+ password_list.append(password) |
+ return remote_scp(command, password_list, log_filename, timeout) |
+ |
def scp_from_remote(host, port, username, password, remote_path, local_path, |
@@ -810,7 +826,34 @@ def scp_from_remote(host, port, username, password, remote_path, local_path, |
command = ("scp -v -o UserKnownHostsFile=/dev/null " |
"-o PreferredAuthentications=password -r -P %s %s@%s:%s %s" % |
(port, username, host, remote_path, local_path)) |
- remote_scp(command, password, log_filename, timeout) |
+ password_list = [] |
+ password_list.append(password) |
+ remote_scp(command, password_list, log_filename, timeout) |
+ |
+ |
+def scp_between_remotes(src, dst, port, s_passwd, d_passwd, s_name, d_name, |
+ s_path, d_path, log_filename=None, timeout=600): |
+ """ |
+ Copy files from a remote host (guest) to another remote host (guest). |
+ |
+ @param src/dst: Hostname or IP address of src and dst |
+ @param s_name/d_name: Username (if required) |
+ @param s_passwd/d_passwd: Password (if required) |
+ @param s_path/d_path: Path on the remote machine where we are copying |
+ from/to |
+ @param log_filename: If specified, log all output to this file |
+ @param timeout: The time duration (in seconds) to wait for the transfer |
+ to complete. |
+ |
+ @return: True on success and False on failure. |
+ """ |
+ command = ("scp -v -o UserKnownHostsFile=/dev/null -o " |
+ "PreferredAuthentications=password -r -P %s %s@%s:%s %s@%s:%s" % |
+ (port, s_name, src, s_path, d_name, dst, d_path)) |
+ password_list = [] |
+ password_list.append(s_passwd) |
+ password_list.append(d_passwd) |
+ return remote_scp(command, password_list, log_filename, timeout) |
def copy_files_to(address, client, username, password, port, local_path, |
@@ -1108,30 +1151,38 @@ def run_tests(parser, job): |
for test_name in status_dict.keys(): |
if not dep in test_name: |
continue |
- if not status_dict[test_name]: |
+ # So the only really non-fatal state is WARN, |
+ # All the others make it not safe to proceed with dependency |
+ # execution |
+ if status_dict[test_name] not in ['GOOD', 'WARN']: |
dependencies_satisfied = False |
break |
+ test_iterations = int(dict.get("iterations", 1)) |
+ test_tag = dict.get("shortname") |
+ |
if dependencies_satisfied: |
- test_iterations = int(dict.get("iterations", 1)) |
- test_tag = dict.get("shortname") |
# Setting up profilers during test execution. |
profilers = dict.get("profilers", "").split() |
for profiler in profilers: |
job.profilers.add(profiler) |
- |
# We need only one execution, profiled, hence we're passing |
# the profile_only parameter to job.run_test(). |
- current_status = job.run_test("kvm", params=dict, tag=test_tag, |
- iterations=test_iterations, |
- profile_only= bool(profilers) or None) |
- |
+ profile_only = bool(profilers) or None |
+ current_status = job.run_test_detail("kvm", params=dict, |
+ tag=test_tag, |
+ iterations=test_iterations, |
+ profile_only=profile_only) |
for profiler in profilers: |
job.profilers.delete(profiler) |
- |
- if not current_status: |
- failed = True |
else: |
- current_status = False |
+ # We will force the test to fail as TestNA during preprocessing |
+ dict['dependency_failed'] = 'yes' |
+ current_status = job.run_test_detail("kvm", params=dict, |
+ tag=test_tag, |
+ iterations=test_iterations) |
+ |
+ if not current_status: |
+ failed = True |
status_dict[dict.get("name")] = current_status |
return not failed |
@@ -1149,6 +1200,19 @@ def create_report(report_dir, results_dir): |
os.system('%s -r %s -f %s -R' % (reporter, results_dir, html_file)) |
+def display_attributes(instance): |
+ """ |
+ Inspects a given class instance attributes and displays them, convenient |
+ for debugging. |
+ """ |
+ logging.debug("Attributes set:") |
+ for member in inspect.getmembers(instance): |
+ name, value = member |
+ attribute = getattr(instance, name) |
+ if not (name.startswith("__") or callable(attribute) or not value): |
+ logging.debug(" %s: %s", name, value) |
+ |
+ |
def get_full_pci_id(pci_id): |
""" |
Get full PCI ID of pci_id. |
@@ -1535,142 +1599,597 @@ class PciAssignable(object): |
return |
-class KojiDownloader(object): |
+class KojiClient(object): |
""" |
- Stablish a connection with the build system, either koji or brew. |
+ Stablishes a connection with the build system, either koji or brew. |
- This class provides a convenience methods to retrieve packages hosted on |
- the build system. |
+ This class provides convenience methods to retrieve information on packages |
+ and the packages themselves hosted on the build system. Packages should be |
+ specified in the KojiPgkSpec syntax. |
""" |
- def __init__(self, cmd): |
+ |
+ CMD_LOOKUP_ORDER = ['/usr/bin/brew', '/usr/bin/koji' ] |
+ |
+ CONFIG_MAP = {'/usr/bin/brew': '/etc/brewkoji.conf', |
+ '/usr/bin/koji': '/etc/koji.conf'} |
+ |
+ |
+ def __init__(self, cmd=None): |
""" |
Verifies whether the system has koji or brew installed, then loads |
the configuration file that will be used to download the files. |
- @param cmd: Command name, either 'brew' or 'koji'. It is important |
- to figure out the appropriate configuration used by the |
- downloader. |
- @param dst_dir: Destination dir for the packages. |
+ @type cmd: string |
+ @param cmd: Optional command name, either 'brew' or 'koji'. If not |
+ set, get_default_command() is used and to look for |
+ one of them. |
+ @raise: ValueError |
""" |
if not KOJI_INSTALLED: |
raise ValueError('No koji/brew installed on the machine') |
- if os.path.isfile(cmd): |
- koji_cmd = cmd |
+ # Instance variables used by many methods |
+ self.command = None |
+ self.config = None |
+ self.config_options = {} |
+ self.session = None |
+ |
+ # Set koji command or get default |
+ if cmd is None: |
+ self.command = self.get_default_command() |
else: |
- koji_cmd = os_dep.command(cmd) |
+ self.command = cmd |
- logging.debug("Found %s as the buildsystem interface", koji_cmd) |
+ # Check koji command |
+ if not self.is_command_valid(): |
+ raise ValueError('Koji command "%s" is not valid' % self.command) |
- config_map = {'/usr/bin/koji': '/etc/koji.conf', |
- '/usr/bin/brew': '/etc/brewkoji.conf'} |
+ # Assuming command is valid, set configuration file and read it |
+ self.config = self.CONFIG_MAP[self.command] |
+ self.read_config() |
- try: |
- config_file = config_map[koji_cmd] |
- except IndexError: |
- raise ValueError('Could not find config file for %s' % koji_cmd) |
- |
- base_name = os.path.basename(koji_cmd) |
- if os.access(config_file, os.F_OK): |
- f = open(config_file) |
- config = ConfigParser.ConfigParser() |
- config.readfp(f) |
- f.close() |
- else: |
- raise IOError('Configuration file %s missing or with wrong ' |
- 'permissions' % config_file) |
- |
- if config.has_section(base_name): |
- self.koji_options = {} |
- session_options = {} |
- server = None |
- for name, value in config.items(base_name): |
- if name in ('user', 'password', 'debug_xmlrpc', 'debug'): |
- session_options[name] = value |
- self.koji_options[name] = value |
- self.session = koji.ClientSession(self.koji_options['server'], |
- session_options) |
- else: |
- raise ValueError('Koji config file %s does not have a %s ' |
- 'session' % (config_file, base_name)) |
+ # Setup koji session |
+ server_url = self.config_options['server'] |
+ session_options = self.get_session_options() |
+ self.session = koji.ClientSession(server_url, |
+ session_options) |
- def get(self, src_package, dst_dir, rfilter=None, tag=None, build=None, |
- arch=None): |
- """ |
- Download a list of packages from the build system. |
- |
- This will download all packages originated from source package [package] |
- with given [tag] or [build] for the architecture reported by the |
- machine. |
- |
- @param src_package: Source package name. |
- @param dst_dir: Destination directory for the downloaded packages. |
- @param rfilter: Regexp filter, only download the packages that match |
- that particular filter. |
- @param tag: Build system tag. |
- @param build: Build system ID. |
- @param arch: Package arch. Useful when you want to download noarch |
- packages. |
- |
- @return: List of paths with the downloaded rpm packages. |
- """ |
- if build and build.isdigit(): |
- build = int(build) |
- |
- if tag and build: |
- logging.info("Both tag and build parameters provided, ignoring tag " |
- "parameter...") |
- |
- if not tag and not build: |
- raise ValueError("Koji install selected but neither koji_tag " |
- "nor koji_build parameters provided. Please " |
- "provide an appropriate tag or build name.") |
- |
- if not build: |
- builds = self.session.listTagged(tag, latest=True, inherit=True, |
- package=src_package) |
- if not builds: |
- raise ValueError("Tag %s has no builds of %s" % (tag, |
- src_package)) |
- info = builds[0] |
- else: |
- info = self.session.getBuild(build) |
+ def read_config(self, check_is_valid=True): |
+ ''' |
+ Reads options from the Koji configuration file |
+ |
+ By default it checks if the koji configuration is valid |
+ |
+ @type check_valid: boolean |
+ @param check_valid: whether to include a check on the configuration |
+ @raises: ValueError |
+ @returns: None |
+ ''' |
+ if check_is_valid: |
+ if not self.is_config_valid(): |
+ raise ValueError('Koji config "%s" is not valid' % self.config) |
- if info is None: |
- raise ValueError('No such brew/koji build: %s' % build) |
+ config = ConfigParser.ConfigParser() |
+ config.read(self.config) |
+ basename = os.path.basename(self.command) |
+ for name, value in config.items(basename): |
+ self.config_options[name] = value |
+ |
+ |
+ def get_session_options(self): |
+ ''' |
+ Filter only options necessary for setting up a cobbler client session |
+ |
+ @returns: only the options used for session setup |
+ ''' |
+ session_options = {} |
+ for name, value in self.config_options.items(): |
+ if name in ('user', 'password', 'debug_xmlrpc', 'debug'): |
+ session_options[name] = value |
+ return session_options |
+ |
+ |
+ def is_command_valid(self): |
+ ''' |
+ Checks if the currently set koji command is valid |
+ |
+ @returns: True or False |
+ ''' |
+ koji_command_ok = True |
+ |
+ if not os.path.isfile(self.command): |
+ logging.error('Koji command "%s" is not a regular file', |
+ self.command) |
+ koji_command_ok = False |
+ |
+ if not os.access(self.command, os.X_OK): |
+ logging.warn('Koji command "%s" is not executable: this is ' |
+ 'not fatal but indicates an unexpected situation', |
+ self.command) |
+ |
+ if not self.command in self.CONFIG_MAP.keys(): |
+ logging.error('Koji command "%s" does not have a configuration ' |
+ 'file associated to it', self.command) |
+ koji_command_ok = False |
+ |
+ return koji_command_ok |
+ |
+ |
+ def is_config_valid(self): |
+ ''' |
+ Checks if the currently set koji configuration is valid |
+ |
+ @returns: True or False |
+ ''' |
+ koji_config_ok = True |
+ |
+ if not os.path.isfile(self.config): |
+ logging.error('Koji config "%s" is not a regular file', self.config) |
+ koji_config_ok = False |
+ |
+ if not os.access(self.config, os.R_OK): |
+ logging.error('Koji config "%s" is not readable', self.config) |
+ koji_config_ok = False |
+ |
+ config = ConfigParser.ConfigParser() |
+ config.read(self.config) |
+ basename = os.path.basename(self.command) |
+ if not config.has_section(basename): |
+ logging.error('Koji configuration file "%s" does not have a ' |
+ 'section "%s", named after the base name of the ' |
+ 'currently set koji command "%s"', self.config, |
+ basename, self.command) |
+ koji_config_ok = False |
+ |
+ return koji_config_ok |
+ |
+ |
+ def get_default_command(self): |
+ ''' |
+ Looks up for koji or brew "binaries" on the system |
+ |
+ Systems with plain koji usually don't have a brew cmd, while systems |
+ with koji, have *both* koji and brew utilities. So we look for brew |
+ first, and if found, we consider that the system is configured for |
+ brew. If not, we consider this is a system with plain koji. |
+ |
+ @returns: either koji or brew command line executable path, or None |
+ ''' |
+ koji_command = None |
+ for command in self.CMD_LOOKUP_ORDER: |
+ if os.path.isfile(command): |
+ koji_command = command |
+ break |
+ else: |
+ koji_command_basename = os.path.basename(koji_command) |
+ try: |
+ koji_command = os_dep.command(koji_command_basename) |
+ break |
+ except ValueError: |
+ pass |
+ return koji_command |
+ |
+ |
+ def get_pkg_info(self, pkg): |
+ ''' |
+ Returns information from Koji on the package |
+ |
+ @type pkg: KojiPkgSpec |
+ @param pkg: information about the package, as a KojiPkgSpec instance |
+ |
+ @returns: information from Koji about the specified package |
+ ''' |
+ info = {} |
+ if pkg.build is not None: |
+ info = self.session.getBuild(int(pkg.build)) |
+ elif pkg.tag is not None and pkg.package is not None: |
+ builds = self.session.listTagged(pkg.tag, |
+ latest=True, |
+ inherit=True, |
+ package=pkg.package) |
+ if builds: |
+ info = builds[0] |
+ return info |
+ |
+ |
+ def is_pkg_valid(self, pkg): |
+ ''' |
+ Checks if this package is altogether valid on Koji |
+ |
+ This verifies if the build or tag specified in the package |
+ specification actually exist on the Koji server |
+ |
+ @returns: True or False |
+ ''' |
+ valid = True |
+ if not self.is_pkg_spec_build_valid(pkg): |
+ valid = False |
+ if not self.is_pkg_spec_tag_valid(pkg): |
+ valid = False |
+ return valid |
+ |
+ |
+ def is_pkg_spec_build_valid(self, pkg): |
+ ''' |
+ Checks if build is valid on Koji |
+ |
+ @param pkg: a Pkg instance |
+ ''' |
+ if pkg.build is not None: |
+ info = self.session.getBuild(int(pkg.build)) |
+ if info: |
+ return True |
+ return False |
+ |
+ |
+ def is_pkg_spec_tag_valid(self, pkg): |
+ ''' |
+ Checks if tag is valid on Koji |
+ |
+ @type pkg: KojiPkgSpec |
+ @param pkg: a package specification |
+ ''' |
+ if pkg.tag is not None: |
+ tag = self.session.getTag(pkg.tag) |
+ if tag: |
+ return True |
+ return False |
+ |
+ |
+ def get_pkg_rpm_info(self, pkg, arch=None): |
+ ''' |
+ Returns a list of infomation on the RPM packages found on koji |
+ |
+ @type pkg: KojiPkgSpec |
+ @param pkg: a package specification |
+ @type arch: string |
+ @param arch: packages built for this architecture, but also including |
+ architecture independent (noarch) packages |
+ ''' |
if arch is None: |
arch = utils.get_arch() |
+ rpms = [] |
+ info = self.get_pkg_info(pkg) |
+ if info: |
+ rpms = self.session.listRPMs(buildID=info['id'], |
+ arches=[arch, 'noarch']) |
+ if pkg.subpackages: |
+ rpms = [d for d in rpms if d['name'] in pkg.subpackages] |
+ return rpms |
+ |
+ |
+ def get_pkg_rpm_names(self, pkg, arch=None): |
+ ''' |
+ Gets the names for the RPM packages specified in pkg |
+ |
+ @type pkg: KojiPkgSpec |
+ @param pkg: a package specification |
+ @type arch: string |
+ @param arch: packages built for this architecture, but also including |
+ architecture independent (noarch) packages |
+ ''' |
+ if arch is None: |
+ arch = utils.get_arch() |
+ rpms = self.get_pkg_rpm_info(pkg, arch) |
+ return [rpm['name'] for rpm in rpms] |
+ |
- rpms = self.session.listRPMs(buildID=info['id'], |
- arches=arch) |
- if not rpms: |
- raise ValueError("No %s packages available for %s" % |
- arch, koji.buildLabel(info)) |
+ def get_pkg_rpm_file_names(self, pkg, arch=None): |
+ ''' |
+ Gets the file names for the RPM packages specified in pkg |
- rpm_paths = [] |
+ @type pkg: KojiPkgSpec |
+ @param pkg: a package specification |
+ @type arch: string |
+ @param arch: packages built for this architecture, but also including |
+ architecture independent (noarch) packages |
+ ''' |
+ if arch is None: |
+ arch = utils.get_arch() |
+ rpm_names = [] |
+ rpms = self.get_pkg_rpm_info(pkg, arch) |
+ for rpm in rpms: |
+ arch_rpm_name = koji.pathinfo.rpm(rpm) |
+ rpm_name = os.path.basename(arch_rpm_name) |
+ rpm_names.append(rpm_name) |
+ return rpm_names |
+ |
+ |
+ def get_pkg_urls(self, pkg, arch=None): |
+ ''' |
+ Gets the urls for the packages specified in pkg |
+ |
+ @type pkg: KojiPkgSpec |
+ @param pkg: a package specification |
+ @type arch: string |
+ @param arch: packages built for this architecture, but also including |
+ architecture independent (noarch) packages |
+ ''' |
+ info = self.get_pkg_info(pkg) |
+ rpms = self.get_pkg_rpm_info(pkg, arch) |
+ rpm_urls = [] |
for rpm in rpms: |
rpm_name = koji.pathinfo.rpm(rpm) |
- url = ("%s/%s/%s/%s/%s" % (self.koji_options['pkgurl'], |
+ url = ("%s/%s/%s/%s/%s" % (self.config_options['pkgurl'], |
info['package_name'], |
info['version'], info['release'], |
rpm_name)) |
- if rfilter: |
- filter_regexp = re.compile(rfilter, re.IGNORECASE) |
- if filter_regexp.match(os.path.basename(rpm_name)): |
- download = True |
- else: |
- download = False |
+ rpm_urls.append(url) |
+ return rpm_urls |
+ |
+ |
+ def get_pkgs(self, pkg, dst_dir, arch=None): |
+ ''' |
+ Download the packages |
+ |
+ @type pkg: KojiPkgSpec |
+ @param pkg: a package specification |
+ @type dst_dir: string |
+ @param dst_dir: the destination directory, where the downloaded |
+ packages will be saved on |
+ @type arch: string |
+ @param arch: packages built for this architecture, but also including |
+ architecture independent (noarch) packages |
+ ''' |
+ rpm_urls = self.get_pkg_urls(pkg, arch) |
+ for url in rpm_urls: |
+ utils.get_file(url, |
+ os.path.join(dst_dir, os.path.basename(url))) |
+ |
+ |
+DEFAULT_KOJI_TAG = None |
+def set_default_koji_tag(tag): |
+ ''' |
+ Sets the default tag that will be used |
+ ''' |
+ global DEFAULT_KOJI_TAG |
+ DEFAULT_KOJI_TAG = tag |
+ |
+ |
+def get_default_koji_tag(): |
+ return DEFAULT_KOJI_TAG |
+ |
+ |
+class KojiPkgSpec: |
+ ''' |
+ A package specification syntax parser for Koji |
+ |
+ This holds information on either tag or build, and packages to be fetched |
+ from koji and possibly installed (features external do this class). |
+ |
+ New objects can be created either by providing information in the textual |
+ format or by using the actual parameters for tag, build, package and sub- |
+ packages. The textual format is useful for command line interfaces and |
+ configuration files, while using parameters is better for using this in |
+ a programatic fashion. |
+ |
+ The following sets of examples are interchangeable. Specifying all packages |
+ part of build number 1000: |
+ |
+ >>> from kvm_utils import KojiPkgSpec |
+ >>> pkg = KojiPkgSpec('1000') |
+ |
+ >>> pkg = KojiPkgSpec(build=1000) |
+ |
+ Specifying only a subset of packages of build number 1000: |
+ |
+ >>> pkg = KojiPkgSpec('1000:kernel,kernel-devel') |
+ |
+ >>> pkg = KojiPkgSpec(build=1000, |
+ subpackages=['kernel', 'kernel-devel']) |
+ |
+ Specifying the latest build for the 'kernel' package tagged with 'dist-f14': |
+ |
+ >>> pkg = KojiPkgSpec('dist-f14:kernel') |
+ |
+ >>> pkg = KojiPkgSpec(tag='dist-f14', package='kernel') |
+ |
+ Specifying the 'kernel' package using the default tag: |
+ |
+ >>> kvm_utils.set_default_koji_tag('dist-f14') |
+ >>> pkg = KojiPkgSpec('kernel') |
+ |
+ >>> pkg = KojiPkgSpec(package='kernel') |
+ |
+ Specifying the 'kernel' package using the default tag: |
+ |
+ >>> kvm_utils.set_default_koji_tag('dist-f14') |
+ >>> pkg = KojiPkgSpec('kernel') |
+ |
+ >>> pkg = KojiPkgSpec(package='kernel') |
+ |
+ If you do not specify a default tag, and give a package name without an |
+ explicit tag, your package specification is considered invalid: |
+ |
+ >>> print kvm_utils.get_default_koji_tag() |
+ None |
+ >>> print kvm_utils.KojiPkgSpec('kernel').is_valid() |
+ False |
+ |
+ >>> print kvm_utils.KojiPkgSpec(package='kernel').is_valid() |
+ False |
+ ''' |
+ |
+ SEP = ':' |
+ |
+ def __init__(self, text='', tag=None, build=None, |
+ package=None, subpackages=[]): |
+ ''' |
+ Instantiates a new KojiPkgSpec object |
+ |
+ @type text: string |
+ @param text: a textual representation of a package on Koji that |
+ will be parsed |
+ @type tag: string |
+ @param tag: a koji tag, example: Fedora-14-RELEASE |
+ (see U{http://fedoraproject.org/wiki/Koji#Tags_and_Targets}) |
+ @type build: number |
+ @param build: a koji build, example: 1001 |
+ (see U{http://fedoraproject.org/wiki/Koji#Koji_Architecture}) |
+ @type package: string |
+ @param package: a koji package, example: python |
+ (see U{http://fedoraproject.org/wiki/Koji#Koji_Architecture}) |
+ @type subpackages: list of strings |
+ @param subpackages: a list of package names, usually a subset of |
+ the RPM packages generated by a given build |
+ ''' |
+ |
+ # Set to None to indicate 'not set' (and be able to use 'is') |
+ self.tag = None |
+ self.build = None |
+ self.package = None |
+ self.subpackages = [] |
+ |
+ self.default_tag = None |
+ |
+ # Textual representation takes precedence (most common use case) |
+ if text: |
+ self.parse(text) |
+ else: |
+ self.tag = tag |
+ self.build = build |
+ self.package = package |
+ self.subpackages = subpackages |
+ |
+ # Set the default tag, if set, as a fallback |
+ if not self.build and not self.tag: |
+ default_tag = get_default_koji_tag() |
+ if default_tag is not None: |
+ self.tag = default_tag |
+ |
+ |
+ def parse(self, text): |
+ ''' |
+ Parses a textual representation of a package specification |
+ |
+ @type text: string |
+ @param text: textual representation of a package in koji |
+ ''' |
+ parts = text.count(self.SEP) + 1 |
+ if parts == 1: |
+ if text.isdigit(): |
+ self.build = text |
+ else: |
+ self.package = text |
+ elif parts == 2: |
+ part1, part2 = text.split(self.SEP) |
+ if part1.isdigit(): |
+ self.build = part1 |
+ self.subpackages = part2.split(',') |
+ else: |
+ self.tag = part1 |
+ self.package = part2 |
+ elif parts >= 3: |
+ # Instead of erroring on more arguments, we simply ignore them |
+ # This makes the parser suitable for future syntax additions, such |
+ # as specifying the package architecture |
+ part1, part2, part3 = text.split(self.SEP)[0:3] |
+ self.tag = part1 |
+ self.package = part2 |
+ self.subpackages = part3.split(',') |
+ |
+ |
+ def _is_invalid_neither_tag_or_build(self): |
+ ''' |
+ Checks if this package is invalid due to not having either a valid |
+ tag or build set, that is, both are empty. |
+ |
+ @returns: True if this is invalid and False if it's valid |
+ ''' |
+ return (self.tag is None and self.build is None) |
+ |
+ |
+ def _is_invalid_package_but_no_tag(self): |
+ ''' |
+ Checks if this package is invalid due to having a package name set |
+ but tag or build set, that is, both are empty. |
+ |
+ @returns: True if this is invalid and False if it's valid |
+ ''' |
+ return (self.package and not self.tag) |
+ |
+ |
+ def _is_invalid_subpackages_but_no_main_package(self): |
+ ''' |
+ Checks if this package is invalid due to having a tag set (this is Ok) |
+ but specifying subpackage names without specifying the main package |
+ name. |
+ |
+ Specifying subpackages without a main package name is only valid when |
+ a build is used instead of a tag. |
+ |
+ @returns: True if this is invalid and False if it's valid |
+ ''' |
+ return (self.tag and self.subpackages and not self.package) |
+ |
+ |
+ def is_valid(self): |
+ ''' |
+ Checks if this package specification is valid. |
+ |
+ Being valid means that it has enough and not conflicting information. |
+ It does not validate that the packages specified actually existe on |
+ the Koji server. |
+ |
+ @returns: True or False |
+ ''' |
+ if self._is_invalid_neither_tag_or_build(): |
+ return False |
+ elif self._is_invalid_package_but_no_tag(): |
+ return False |
+ elif self._is_invalid_subpackages_but_no_main_package(): |
+ return False |
+ |
+ return True |
+ |
+ |
+ def describe_invalid(self): |
+ ''' |
+ Describes why this is not valid, in a human friendly way |
+ ''' |
+ if self._is_invalid_neither_tag_or_build(): |
+ return 'neither a tag or build are set, and of them should be set' |
+ elif self._is_invalid_package_but_no_tag(): |
+ return 'package name specified but no tag is set' |
+ elif self._is_invalid_subpackages_but_no_main_package(): |
+ return 'subpackages specified but no main package is set' |
+ |
+ return 'unkwown reason, seems to be valid' |
+ |
+ |
+ def describe(self): |
+ ''' |
+ Describe this package specification, in a human friendly way |
+ |
+ @returns: package specification description |
+ ''' |
+ if self.is_valid(): |
+ description = '' |
+ if not self.subpackages: |
+ description += 'all subpackages from %s ' % self.package |
+ else: |
+ description += ('only subpackage(s) %s from package %s ' % |
+ (', '.join(self.subpackages), self.package)) |
+ |
+ if self.build: |
+ description += 'from build %s' % self.build |
+ elif self.tag: |
+ description += 'tagged with %s' % self.tag |
else: |
- download = True |
+ raise ValueError, 'neither build or tag is set' |
+ |
+ return description |
+ else: |
+ return ('Invalid package specification: %s' % |
+ self.describe_invalid()) |
- if download: |
- r = utils.get_file(url, |
- os.path.join(dst_dir, os.path.basename(url))) |
- rpm_paths.append(r) |
- return rpm_paths |
+ def __repr__(self): |
+ return ("<KojiPkgSpec tag=%s build=%s pkg=%s subpkgs=%s>" % |
+ (self.tag, self.build, self.package, |
+ ", ".join(self.subpackages))) |
def umount(src, mount_point, type): |
@@ -1726,3 +2245,73 @@ def mount(src, mount_point, type, perm="rw"): |
logging.error("Can't find mounted NFS share - /etc/mtab contents \n%s", |
file("/etc/mtab").read()) |
return False |
+ |
+ |
+def install_host_kernel(job, params): |
+ """ |
+ Install a host kernel, given the appropriate params. |
+ |
+ @param job: Job object. |
+ @param params: Dict with host kernel install params. |
+ """ |
+ install_type = params.get('host_kernel_install_type') |
+ |
+ rpm_url = params.get('host_kernel_rpm_url') |
+ |
+ koji_cmd = params.get('host_kernel_koji_cmd') |
+ koji_build = params.get('host_kernel_koji_build') |
+ koji_tag = params.get('host_kernel_koji_tag') |
+ |
+ git_repo = params.get('host_kernel_git_repo') |
+ git_branch = params.get('host_kernel_git_branch') |
+ git_commit = params.get('host_kernel_git_commit') |
+ patch_list = params.get('host_kernel_patch_list') |
+ if patch_list: |
+ patch_list = patch_list.split() |
+ kernel_config = params.get('host_kernel_config') |
+ |
+ if install_type == 'rpm': |
+ logging.info('Installing host kernel through rpm') |
+ dst = os.path.join("/tmp", os.path.basename(rpm_url)) |
+ k = utils.get_file(rpm_url, dst) |
+ host_kernel = job.kernel(k) |
+ host_kernel.install(install_vmlinux=False) |
+ host_kernel.boot() |
+ |
+ elif install_type in ['koji', 'brew']: |
+ k_deps = KojiPkgSpec(tag=koji_tag, package='kernel', |
+ subpackages=['kernel-devel', 'kernel-firmware']) |
+ k = KojiPkgSpec(tag=koji_tag, package='kernel', |
+ subpackages=['kernel']) |
+ |
+ c = KojiClient(koji_cmd) |
+ logging.info('Fetching kernel dependencies (-devel, -firmware)') |
+ c.get_pkgs(k_deps, job.tmpdir) |
+ logging.info('Installing kernel dependencies (-devel, -firmware) ' |
+ 'through %s', install_type) |
+ k_deps_rpm_file_names = [os.path.join(job.tmpdir, rpm_file_name) for |
+ rpm_file_name in c.get_pkg_rpm_file_names(k_deps)] |
+ utils.run('rpm -U --force %s' % " ".join(k_deps_rpm_file_names)) |
+ |
+ c.get_pkgs(k, job.tmpdir) |
+ k_rpm = os.path.join(job.tmpdir, |
+ c.get_pkg_rpm_file_names(k)[0]) |
+ host_kernel = job.kernel(k_rpm) |
+ host_kernel.install(install_vmlinux=False) |
+ host_kernel.boot() |
+ |
+ elif install_type == 'git': |
+ logging.info('Chose to install host kernel through git, proceeding') |
+ repodir = os.path.join("/tmp", 'kernel_src') |
+ r = get_git_branch(git_repo, git_branch, repodir, git_commit) |
+ host_kernel = job.kernel(r) |
+ if patch_list: |
+ host_kernel.patch(patch_list) |
+ host_kernel.config(kernel_config) |
+ host_kernel.build() |
+ host_kernel.install() |
+ host_kernel.boot() |
+ |
+ else: |
+ logging.info('Chose %s, using the current kernel for the host', |
+ install_type) |