623 lines
19 KiB
Python
623 lines
19 KiB
Python
'''
|
|
This module implements classes that perform the installation of the
|
|
virtualization software on a host system.
|
|
|
|
These classes can be, and usually are, inherited by subclasses that implement
|
|
custom logic for each virtualization hypervisor/software.
|
|
'''
|
|
|
|
import os, logging
|
|
from autotest_lib.client.bin import utils, os_dep
|
|
from autotest_lib.client.common_lib import error
|
|
from autotest_lib.client.virt import virt_utils
|
|
|
|
class VirtInstallException(Exception):
|
|
'''
|
|
Base virtualization software components installation exception
|
|
'''
|
|
pass
|
|
|
|
|
|
class VirtInstallFailed(VirtInstallException):
|
|
'''
|
|
Installation of virtualization software components failed
|
|
'''
|
|
pass
|
|
|
|
|
|
class VirtInstallNotInstalled(VirtInstallException):
|
|
'''
|
|
Virtualization software components are not installed
|
|
'''
|
|
pass
|
|
|
|
|
|
class BaseInstaller(object):
|
|
'''
|
|
Base virtualization software installer
|
|
|
|
This class holds all the skeleton features for installers and should be
|
|
inherited from when creating a new installer.
|
|
'''
|
|
def __init__(self, mode, name, test=None, params=None):
|
|
'''
|
|
Instantiates a new base installer
|
|
|
|
@param mode: installer mode, such as git_repo, local_src, etc
|
|
@param name: installer short name, foo for git_repo_foo
|
|
@param test: test
|
|
@param params: params
|
|
'''
|
|
self.mode = mode
|
|
self.name = name
|
|
self.params = params
|
|
self.param_key_prefix = '%s_%s' % (self.mode,
|
|
self.name)
|
|
|
|
if test and params:
|
|
self.set_install_params(test, params)
|
|
|
|
|
|
def _set_test_dirs(self, test):
|
|
'''
|
|
Save common test directories paths (srcdir, bindir) as class attributes
|
|
|
|
Test variables values are saved here again because it's not possible to
|
|
pickle the test instance inside BaseInstaller due to limitations
|
|
in the pickle protocol. And, in case this pickle thing needs more
|
|
explanation, take a loot at the Env class inside virt_utils.
|
|
|
|
Besides that, we also ensure that srcdir exists, by creating it if
|
|
necessary.
|
|
|
|
For reference:
|
|
* bindir = tests/<test>
|
|
* srcdir = tests/<test>/src
|
|
|
|
So, for KVM tests, it'd evaluate to:
|
|
* bindir = tests/kvm/
|
|
* srcdir = tests/kvm/src
|
|
'''
|
|
self.test_bindir = test.bindir
|
|
self.test_srcdir = test.srcdir
|
|
|
|
#
|
|
# test_bindir is guaranteed to exist, but test_srcdir is not
|
|
#
|
|
if not os.path.isdir(test.srcdir):
|
|
os.makedirs(test.srcdir)
|
|
|
|
|
|
def _set_param_load_module(self):
|
|
'''
|
|
Checks whether kernel modules should be loaded
|
|
|
|
Default behavior is to load modules unless set to 'no'
|
|
|
|
Configuration file parameter: load_modules
|
|
Class attribute set: should_load_modules
|
|
'''
|
|
load_modules = self.params.get('load_modules', 'no')
|
|
if not load_modules or load_modules == 'yes':
|
|
self.should_load_modules = True
|
|
elif load_modules == 'no':
|
|
self.should_load_modules = False
|
|
|
|
|
|
def _set_param_module_list(self):
|
|
'''
|
|
Sets the list of kernel modules to be loaded during installation
|
|
|
|
Configuration file parameter: module_list
|
|
Class attribute set: module_list
|
|
'''
|
|
self.module_list = self.params.get('module_list', '').split()
|
|
|
|
|
|
def _set_param_save_results(self):
|
|
'''
|
|
Checks whether to save the result of the build on test.resultsdir
|
|
|
|
Configuration file parameter: save_results
|
|
Class attribute set: save_results
|
|
'''
|
|
self.save_results = True
|
|
save_results = self.params.get('save_results', 'no')
|
|
if save_results == 'no':
|
|
self.save_results = False
|
|
|
|
|
|
def set_install_params(self, test=None, params=None):
|
|
'''
|
|
Called by test to setup parameters from the configuration file
|
|
'''
|
|
if test is not None:
|
|
self._set_test_dirs(test)
|
|
|
|
if params is not None:
|
|
self.params = params
|
|
self._set_param_load_module()
|
|
self._set_param_module_list()
|
|
self._set_param_save_results()
|
|
|
|
|
|
def _install_phase_cleanup(self):
|
|
'''
|
|
Optional install phase for removing previous version of the software
|
|
|
|
If a particular virtualization software installation mechanism
|
|
needs to download files (it most probably does), override this
|
|
method with custom functionality.
|
|
|
|
This replaces methods such as KojiInstaller._get_packages()
|
|
'''
|
|
pass
|
|
|
|
|
|
def _install_phase_cleanup_verify(self):
|
|
'''
|
|
Optional install phase for removing previous version of the software
|
|
|
|
If a particular virtualization software installation mechanism
|
|
needs to download files (it most probably does), override this
|
|
method with custom functionality.
|
|
|
|
This replaces methods such as KojiInstaller._get_packages()
|
|
'''
|
|
pass
|
|
|
|
|
|
def _install_phase_download(self):
|
|
'''
|
|
Optional install phase for downloading software
|
|
|
|
If a particular virtualization software installation mechanism
|
|
needs to download files (it most probably does), override this
|
|
method with custom functionality.
|
|
|
|
This replaces methods such as KojiInstaller._get_packages()
|
|
'''
|
|
pass
|
|
|
|
|
|
def _install_phase_download_verify(self):
|
|
'''
|
|
Optional install phase for checking downloaded software
|
|
|
|
If you want to make sure the downloaded software is in good shape,
|
|
override this method.
|
|
|
|
Ideas for using this method:
|
|
* check MD5SUM/SHA1SUM for tarball downloads
|
|
* check RPM files, probaly by signature (rpm -k)
|
|
* git status and check if there's no locally modified files
|
|
'''
|
|
pass
|
|
|
|
|
|
def _install_phase_prepare(self):
|
|
'''
|
|
Optional install phase for preparing software
|
|
|
|
If a particular virtualization software installation mechanism
|
|
needs to do something to the obtained software, such as extracting
|
|
a tarball or applying patches, this should be done here.
|
|
'''
|
|
pass
|
|
|
|
|
|
def _install_phase_prepare_verify(self):
|
|
'''
|
|
Optional install phase for checking software preparation
|
|
|
|
Ideas for using this method:
|
|
* git status and check if there are locally patched files
|
|
'''
|
|
pass
|
|
|
|
|
|
def _install_phase_build(self):
|
|
'''
|
|
Optional install phase for building software
|
|
|
|
If a particular virtualization software installation mechanism
|
|
needs to compile source code, it should be done here.
|
|
'''
|
|
pass
|
|
|
|
|
|
def _install_phase_build_verify(self):
|
|
'''
|
|
Optional install phase for checking software build
|
|
|
|
Ideas for using this method:
|
|
* running 'make test' or something similar to it
|
|
'''
|
|
pass
|
|
|
|
|
|
def _install_phase_install(self):
|
|
'''
|
|
Optional install phase for actually installing software
|
|
|
|
Ideas for using this method:
|
|
* running 'make install' or something similar to it
|
|
* running 'yum localinstall *.rpm'
|
|
'''
|
|
pass
|
|
|
|
|
|
def _install_phase_install_verify(self):
|
|
'''
|
|
Optional install phase for checking the installed software
|
|
|
|
This should verify the installed software is in a desirable state.
|
|
Ideas for using this include:
|
|
* checking if installed files exists (like os.path.exists())
|
|
* checking packages are indeed installed (rpm -q <pkg>.rpm)
|
|
'''
|
|
pass
|
|
|
|
|
|
def _install_phase_init(self):
|
|
'''
|
|
Optional install phase for initializing the installed software
|
|
|
|
This should initialize the installed software. Ideas for using this:
|
|
* loading kernel modules
|
|
* running services: 'service <daemon> start'
|
|
* linking software (whether built or downloaded) to a common path
|
|
'''
|
|
pass
|
|
|
|
|
|
def _install_phase_init_verify(self):
|
|
'''
|
|
Optional install phase for checking that software is initialized
|
|
|
|
This should verify that the installed software is running. Ideas for
|
|
using this include:
|
|
* checking service (daemon) status: 'service <daemon> status'
|
|
* checking service (functionality) status: 'virsh capabilities'
|
|
'''
|
|
pass
|
|
|
|
|
|
def load_modules(self, module_list=None):
|
|
'''
|
|
Load Linux Kernel modules the virtualization software may depend on
|
|
|
|
If module_directory is not set, the list of modules will simply be
|
|
loaded by the system stock modprobe tool, meaning that modules will be
|
|
looked for in the system default module paths.
|
|
|
|
@type module_list: list
|
|
@param module_list: list of kernel modules names to load
|
|
'''
|
|
if module_list is None:
|
|
module_list = self.module_list
|
|
|
|
logging.info("Loading modules from default locations through "
|
|
"modprobe")
|
|
for module in module_list:
|
|
utils.system("modprobe %s" % module)
|
|
|
|
|
|
def unload_modules(self, module_list=None):
|
|
'''
|
|
Unloads kernel modules
|
|
|
|
By default, if no module list is explicitly provided, the list on
|
|
params (coming from the configuration file) will be used.
|
|
'''
|
|
if module_list is None:
|
|
module_list = self.module_list
|
|
module_list = reversed(module_list)
|
|
logging.info("Unloading kernel modules: %s" % ",".join(module_list))
|
|
for module in module_list:
|
|
utils.unload_module(module)
|
|
|
|
|
|
def reload_modules(self):
|
|
"""
|
|
Reload the kernel modules (unload, then load)
|
|
"""
|
|
self.unload_modules()
|
|
self.load_modules()
|
|
|
|
|
|
def reload_modules_if_needed(self):
|
|
if self.should_load_modules:
|
|
self.reload_modules()
|
|
|
|
|
|
def install(self):
|
|
'''
|
|
Performs the installation of the virtualization software
|
|
|
|
This is the main entry point of this class, and should either
|
|
be reimplemented completely, or simply implement one or many of the
|
|
install phases.
|
|
'''
|
|
self._install_phase_cleanup()
|
|
self._install_phase_cleanup_verify()
|
|
|
|
self._install_phase_download()
|
|
self._install_phase_download_verify()
|
|
|
|
self._install_phase_prepare()
|
|
self._install_phase_prepare_verify()
|
|
|
|
self._install_phase_build()
|
|
self._install_phase_build_verify()
|
|
|
|
self._install_phase_install()
|
|
self._install_phase_install_verify()
|
|
|
|
self._install_phase_init()
|
|
self._install_phase_init_verify()
|
|
|
|
self.reload_modules_if_needed()
|
|
if self.save_results:
|
|
virt_utils.archive_as_tarball(self.srcdir, self.results_dir)
|
|
|
|
|
|
def uninstall(self):
|
|
'''
|
|
Performs the uninstallations of the virtualization software
|
|
|
|
Note: This replaces old kvm_installer._clean_previous_install()
|
|
'''
|
|
raise NotImplementedError
|
|
|
|
|
|
class NoopInstaller(BaseInstaller):
|
|
'''
|
|
Dummy installer that does nothing, useful when software is pre-installed
|
|
'''
|
|
def install(self):
|
|
logging.info("Assuming virtualization software to be already "
|
|
"installed. Doing nothing")
|
|
|
|
|
|
class YumInstaller(BaseInstaller):
|
|
'''
|
|
Installs virtualization software using YUM
|
|
|
|
Notice: this class implements a change of behaviour if compared to
|
|
kvm_installer.YumInstaller.set_install_params(). There's no longer
|
|
a default package list, as each virtualization technology will have
|
|
a completely different default. This should now be kept at the
|
|
configuration file only.
|
|
|
|
For now this class implements support for installing from the configured
|
|
yum repos only. If the use case of installing from local RPM packages
|
|
arises, we'll implement that.
|
|
'''
|
|
def set_install_params(self, test, params):
|
|
super(YumInstaller, self).set_install_params(test, params)
|
|
os_dep.command("rpm")
|
|
os_dep.command("yum")
|
|
self.yum_pkgs = eval(params.get("%s_pkgs" % self.param_key_prefix,
|
|
"[]"))
|
|
|
|
|
|
def _install_phase_cleanup(self):
|
|
packages_to_remove = " ".join(self.yum_pkgs)
|
|
utils.system("yum remove -y %s" % packages_to_remove)
|
|
|
|
|
|
def _install_phase_install(self):
|
|
if self.yum_pkgs:
|
|
os.chdir(self.test_srcdir)
|
|
utils.system("yum --nogpgcheck -y install %s" %
|
|
" ".join(self.yum_pkgs))
|
|
|
|
|
|
class KojiInstaller(BaseInstaller):
|
|
'''
|
|
Handles virtualization software installation via koji/brew
|
|
|
|
It uses YUM to install and remove packages.
|
|
|
|
Change notice: this is not a subclass of YumInstaller anymore. The
|
|
parameters this class uses are different (koji_tag, koji_pgks) and
|
|
the install process runs YUM.
|
|
'''
|
|
def set_install_params(self, test, params):
|
|
super(KojiInstaller, self).set_install_params(test, params)
|
|
os_dep.command("rpm")
|
|
os_dep.command("yum")
|
|
|
|
self.tag = params.get("%s_tag" % self.param_key_prefix, None)
|
|
self.koji_cmd = params.get("%s_cmd" % self.param_key_prefix, None)
|
|
if self.tag is not None:
|
|
virt_utils.set_default_koji_tag(self.tag)
|
|
self.koji_pkgs = eval(params.get("%s_pkgs" % self.param_key_prefix,
|
|
"[]"))
|
|
|
|
|
|
def _get_rpm_names(self):
|
|
all_rpm_names = []
|
|
koji_client = virt_utils.KojiClient(cmd=self.koji_cmd)
|
|
for pkg_text in self.koji_pkgs:
|
|
pkg = virt_utils.KojiPkgSpec(pkg_text)
|
|
rpm_names = koji_client.get_pkg_rpm_names(pkg)
|
|
all_rpm_names += rpm_names
|
|
return all_rpm_names
|
|
|
|
|
|
def _get_rpm_file_names(self):
|
|
all_rpm_file_names = []
|
|
koji_client = virt_utils.KojiClient(cmd=self.koji_cmd)
|
|
for pkg_text in self.koji_pkgs:
|
|
pkg = virt_utils.KojiPkgSpec(pkg_text)
|
|
rpm_file_names = koji_client.get_pkg_rpm_file_names(pkg)
|
|
all_rpm_file_names += rpm_file_names
|
|
return all_rpm_file_names
|
|
|
|
|
|
def _install_phase_cleanup(self):
|
|
removable_packages = " ".join(self._get_rpm_names())
|
|
utils.system("yum -y remove %s" % removable_packages)
|
|
|
|
|
|
def _install_phase_download(self):
|
|
koji_client = virt_utils.KojiClient(cmd=self.koji_cmd)
|
|
for pkg_text in self.koji_pkgs:
|
|
pkg = virt_utils.KojiPkgSpec(pkg_text)
|
|
if pkg.is_valid():
|
|
koji_client.get_pkgs(pkg, dst_dir=self.test_srcdir)
|
|
else:
|
|
logging.error('Package specification (%s) is invalid: %s' %
|
|
(pkg, pkg.describe_invalid()))
|
|
|
|
|
|
def _install_phase_install(self):
|
|
os.chdir(self.test_srcdir)
|
|
rpm_file_names = " ".join(self._get_rpm_file_names())
|
|
utils.system("yum --nogpgcheck -y localinstall %s" % rpm_file_names)
|
|
|
|
|
|
class BaseLocalSourceInstaller(BaseInstaller):
|
|
def set_install_params(self, test, params):
|
|
super(BaseLocalSourceInstaller, self).set_install_params(test, params)
|
|
self._set_install_prefix()
|
|
self._set_source_destination()
|
|
|
|
#
|
|
# There are really no choices for patch helpers
|
|
#
|
|
self.patch_helper = virt_utils.PatchParamHelper(
|
|
self.params,
|
|
self.param_key_prefix,
|
|
self.source_destination)
|
|
|
|
#
|
|
# These helpers should be set by child classes
|
|
#
|
|
self.content_helper = None
|
|
self.build_helper = None
|
|
|
|
|
|
def _set_install_prefix(self):
|
|
'''
|
|
Prefix for installation of application built from source
|
|
|
|
When installing virtualization software from *source*, this is where
|
|
the resulting binaries will be installed. Usually this is the value
|
|
passed to the configure script, ie: ./configure --prefix=<value>
|
|
'''
|
|
prefix = os.path.join(self.test_bindir, 'install_root')
|
|
self.install_prefix = os.path.abspath(prefix)
|
|
|
|
|
|
def _set_source_destination(self):
|
|
'''
|
|
Sets the source code destination directory path
|
|
'''
|
|
self.source_destination = os.path.join(self.test_srcdir,
|
|
self.name)
|
|
|
|
|
|
def _set_build_helper(self):
|
|
'''
|
|
Sets the build helper, default is 'gnu_autotools'
|
|
'''
|
|
build_helper_name = self.params.get('%s_build_helper' %
|
|
self.param_key_prefix,
|
|
'gnu_autotools')
|
|
if build_helper_name == 'gnu_autotools':
|
|
self.build_helper = virt_utils.GnuSourceBuildParamHelper(
|
|
self.params, self.param_key_prefix,
|
|
self.source_destination, self.install_prefix)
|
|
|
|
|
|
def _install_phase_download(self):
|
|
if self.content_helper is not None:
|
|
self.content_helper.execute()
|
|
|
|
|
|
def _install_phase_build(self):
|
|
if self.build_helper is not None:
|
|
self.build_helper.execute()
|
|
|
|
|
|
def _install_phase_install(self):
|
|
if self.build_helper is not None:
|
|
self.build_helper.install()
|
|
|
|
|
|
class LocalSourceDirInstaller(BaseLocalSourceInstaller):
|
|
'''
|
|
Handles software installation by building/installing from a source dir
|
|
'''
|
|
def set_install_params(self, test, params):
|
|
super(LocalSourceDirInstaller, self).set_install_params(test, params)
|
|
|
|
self.content_helper = virt_utils.LocalSourceDirParamHelper(
|
|
params,
|
|
self.name,
|
|
self.source_destination)
|
|
|
|
self._set_build_helper()
|
|
|
|
|
|
class LocalSourceTarInstaller(BaseLocalSourceInstaller):
|
|
'''
|
|
Handles software installation by building/installing from a tarball
|
|
'''
|
|
def set_install_params(self, test, params):
|
|
super(LocalSourceTarInstaller, self).set_install_params(test, params)
|
|
|
|
self.content_helper = virt_utils.LocalTarParamHelper(
|
|
params,
|
|
self.name,
|
|
self.source_destination)
|
|
|
|
self._set_build_helper()
|
|
|
|
|
|
class RemoteSourceTarInstaller(BaseLocalSourceInstaller):
|
|
'''
|
|
Handles software installation by building/installing from a remote tarball
|
|
'''
|
|
def set_install_params(self, test, params):
|
|
super(RemoteSourceTarInstaller, self).set_install_params(test, params)
|
|
|
|
self.content_helper = virt_utils.RemoteTarParamHelper(
|
|
params,
|
|
self.name,
|
|
self.source_destination)
|
|
|
|
self._set_build_helper()
|
|
|
|
|
|
class GitRepoInstaller(BaseLocalSourceInstaller):
|
|
def set_install_params(self, test, params):
|
|
super(GitRepoInstaller, self).set_install_params(test, params)
|
|
|
|
self.content_helper = virt_utils.GitRepoParamHelper(
|
|
params,
|
|
self.name,
|
|
self.source_destination)
|
|
|
|
self._set_build_helper()
|
|
|
|
|
|
class FailedInstaller:
|
|
"""
|
|
Class used to be returned instead of the installer if a installation fails
|
|
|
|
Useful to make sure no installer object is used if virt installation fails
|
|
"""
|
|
def __init__(self, msg="Virtualization software install failed"):
|
|
self._msg = msg
|
|
|
|
|
|
def load_modules(self):
|
|
"""
|
|
Will refuse to load the kerkel modules as install failed
|
|
"""
|
|
raise VirtInstallFailed("Kernel modules not available. reason: %s" %
|
|
self._msg)
|