import os, logging, datetime, glob, shutil
from autotest_lib.client.bin import utils, os_dep
from autotest_lib.client.common_lib import error
import virt_utils, virt_installer
def kill_qemu_processes():
"""
Kills all qemu processes, also kills all processes holding /dev/kvm down.
"""
logging.debug("Killing any qemu processes that might be left behind")
utils.system("pkill qemu", ignore_status=True)
# Let's double check to see if some other process is holding /dev/kvm
if os.path.isfile("/dev/kvm"):
utils.system("fuser -k /dev/kvm", ignore_status=True)
def create_symlinks(test_bindir, prefix=None, bin_list=None, unittest=None):
"""
Create symbolic links for the appropriate qemu and qemu-img commands on
the kvm test bindir.
@param test_bindir: KVM test bindir
@param prefix: KVM prefix path
@param bin_list: List of qemu binaries to link
@param unittest: Path to configuration file unittests.cfg
"""
qemu_path = os.path.join(test_bindir, "qemu")
qemu_img_path = os.path.join(test_bindir, "qemu-img")
qemu_unittest_path = os.path.join(test_bindir, "unittests")
if os.path.lexists(qemu_path):
os.unlink(qemu_path)
if os.path.lexists(qemu_img_path):
os.unlink(qemu_img_path)
if unittest and os.path.lexists(qemu_unittest_path):
os.unlink(qemu_unittest_path)
logging.debug("Linking qemu binaries")
if bin_list:
for bin in bin_list:
if os.path.basename(bin) == 'qemu-kvm':
os.symlink(bin, qemu_path)
elif os.path.basename(bin) == 'qemu-img':
os.symlink(bin, qemu_img_path)
elif prefix:
kvm_qemu = os.path.join(prefix, "bin", "qemu-system-x86_64")
if not os.path.isfile(kvm_qemu):
raise error.TestError('Invalid qemu path')
kvm_qemu_img = os.path.join(prefix, "bin", "qemu-img")
if not os.path.isfile(kvm_qemu_img):
raise error.TestError('Invalid qemu-img path')
os.symlink(kvm_qemu, qemu_path)
os.symlink(kvm_qemu_img, qemu_img_path)
if unittest:
logging.debug("Linking unittest dir")
os.symlink(unittest, qemu_unittest_path)
def install_roms(rom_dir, prefix):
logging.debug("Path to roms specified. Copying roms to install prefix")
rom_dst_dir = os.path.join(prefix, 'share', 'qemu')
for rom_src in glob.glob('%s/*.bin' % rom_dir):
rom_dst = os.path.join(rom_dst_dir, os.path.basename(rom_src))
logging.debug("Copying rom file %s to %s", rom_src, rom_dst)
shutil.copy(rom_src, rom_dst)
class KvmInstallException(Exception):
pass
class FailedKvmInstall(KvmInstallException):
pass
class KvmNotInstalled(KvmInstallException):
pass
class BaseInstaller(object):
def __init__(self, mode=None):
self.install_mode = mode
self._full_module_list = None
def set_install_params(self, test, params):
self.params = params
load_modules = 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
default_extra_modules = str(None)
self.extra_modules = eval(params.get("extra_modules",
default_extra_modules))
self.cpu_vendor = virt_utils.get_cpu_vendor()
self.srcdir = test.srcdir
if not os.path.isdir(self.srcdir):
os.makedirs(self.srcdir)
self.test_bindir = test.bindir
self.results_dir = test.resultsdir
# KVM build prefix, for the modes that do need it
prefix = os.path.join(test.bindir, 'build')
self.prefix = os.path.abspath(prefix)
# Current host kernel directory
default_host_kernel_source = '/lib/modules/%s/build' % os.uname()[2]
self.host_kernel_srcdir = params.get('host_kernel_source',
default_host_kernel_source)
# Extra parameters that can be passed to the configure script
self.extra_configure_options = params.get('extra_configure_options',
None)
# Do we want to save the result of the build on test.resultsdir?
self.save_results = True
save_results = params.get('save_results', 'no')
if save_results == 'no':
self.save_results = False
self._full_module_list = list(self._module_list())
def install_unittests(self):
userspace_srcdir = os.path.join(self.srcdir, "kvm_userspace")
test_repo = self.params.get("test_git_repo")
test_branch = self.params.get("test_branch", "master")
test_commit = self.params.get("test_commit", None)
test_lbranch = self.params.get("test_lbranch", "master")
if test_repo:
test_srcdir = os.path.join(self.srcdir, "kvm-unit-tests")
virt_utils.get_git_branch(test_repo, test_branch, test_srcdir,
test_commit, test_lbranch)
unittest_cfg = os.path.join(test_srcdir, 'x86',
'unittests.cfg')
self.test_srcdir = test_srcdir
else:
unittest_cfg = os.path.join(userspace_srcdir, 'kvm', 'test', 'x86',
'unittests.cfg')
self.unittest_cfg = None
if os.path.isfile(unittest_cfg):
self.unittest_cfg = unittest_cfg
else:
if test_repo:
logging.error("No unittest config file %s found, skipping "
"unittest build", self.unittest_cfg)
self.unittest_prefix = None
if self.unittest_cfg:
logging.info("Building and installing unittests")
os.chdir(os.path.dirname(os.path.dirname(self.unittest_cfg)))
utils.system('./configure --prefix=%s' % self.prefix)
utils.system('make')
utils.system('make install')
self.unittest_prefix = os.path.join(self.prefix, 'share', 'qemu',
'tests')
def full_module_list(self):
"""Return the module list used by the installer
Used by the module_probe test, to avoid using utils.unload_module().
"""
if self._full_module_list is None:
raise KvmNotInstalled("KVM modules not installed yet (installer: %s)" % (type(self)))
return self._full_module_list
def _module_list(self):
"""Generate the list of modules that need to be loaded
"""
yield 'kvm'
yield 'kvm-%s' % (self.cpu_vendor)
if self.extra_modules:
for module in self.extra_modules:
yield module
def _load_modules(self, mod_list):
"""
Load the KVM modules
May be overridden by subclasses.
"""
logging.info("Loading KVM modules")
for module in mod_list:
utils.system("modprobe %s" % module)
def load_modules(self, mod_list=None):
if mod_list is None:
mod_list = self.full_module_list()
self._load_modules(mod_list)
def _unload_modules(self, mod_list=None):
"""
Just unload the KVM modules, without trying to kill Qemu
"""
if mod_list is None:
mod_list = self.full_module_list()
logging.info("Unloading previously loaded KVM modules")
for module in reversed(mod_list):
utils.unload_module(module)
def unload_modules(self, mod_list=None):
"""
Kill Qemu and unload the KVM modules
"""
kill_qemu_processes()
self._unload_modules(mod_list)
def reload_modules(self):
"""
Reload the KVM modules after killing Qemu and unloading the current modules
"""
self.unload_modules()
self.load_modules()
def reload_modules_if_needed(self):
if self.should_load_modules:
self.reload_modules()
class YumInstaller(BaseInstaller):
"""
Class that uses yum to install and remove packages.
"""
def set_install_params(self, test, params):
super(YumInstaller, self).set_install_params(test, params)
# Checking if all required dependencies are available
os_dep.command("rpm")
os_dep.command("yum")
default_pkg_list = str(['qemu-kvm', 'qemu-kvm-tools'])
default_qemu_bin_paths = str(['/usr/bin/qemu-kvm', '/usr/bin/qemu-img'])
default_pkg_path_list = str(None)
self.pkg_list = eval(params.get("pkg_list", default_pkg_list))
self.pkg_path_list = eval(params.get("pkg_path_list",
default_pkg_path_list))
self.qemu_bin_paths = eval(params.get("qemu_bin_paths",
default_qemu_bin_paths))
def _clean_previous_installs(self):
kill_qemu_processes()
removable_packages = ""
for pkg in self.pkg_list:
removable_packages += " %s" % pkg
utils.system("yum remove -y %s" % removable_packages)
def _get_packages(self):
for pkg in self.pkg_path_list:
utils.get_file(pkg, os.path.join(self.srcdir,
os.path.basename(pkg)))
def _install_packages(self):
"""
Install all downloaded packages.
"""
os.chdir(self.srcdir)
utils.system("yum install --nogpgcheck -y *.rpm")
def install(self):
self.install_unittests()
self._clean_previous_installs()
self._get_packages()
self._install_packages()
create_symlinks(test_bindir=self.test_bindir,
bin_list=self.qemu_bin_paths,
unittest=self.unittest_prefix)
self.reload_modules_if_needed()
if self.save_results:
virt_utils.archive_as_tarball(self.srcdir, self.results_dir)
class KojiInstaller(YumInstaller):
"""
Class that handles installing KVM from the fedora build service, koji.
It uses yum to install and remove packages. Packages are specified
according to the syntax defined in the PkgSpec class.
"""
def set_install_params(self, test, params):
"""
Gets parameters and initializes the package downloader.
@param test: kvm test object
@param params: Dictionary with test arguments
"""
super(KojiInstaller, self).set_install_params(test, params)
self.tag = params.get("koji_tag", None)
self.koji_cmd = params.get("koji_cmd", None)
if self.tag is not None:
virt_utils.set_default_koji_tag(self.tag)
self.koji_pkgs = eval(params.get("koji_pkgs", "[]"))
def _get_packages(self):
"""
Downloads the specific arch RPMs for the specific build name.
"""
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.srcdir)
else:
logging.error('Package specification (%s) is invalid: %s', pkg,
pkg.describe_invalid())
def _clean_previous_installs(self):
kill_qemu_processes()
removable_packages = " ".join(self._get_rpm_names())
utils.system("yum -y remove %s" % removable_packages)
def install(self):
self._clean_previous_installs()
self._get_packages()
self._install_packages()
self.install_unittests()
create_symlinks(test_bindir=self.test_bindir,
bin_list=self.qemu_bin_paths,
unittest=self.unittest_prefix)
self.reload_modules_if_needed()
if self.save_results:
virt_utils.archive_as_tarball(self.srcdir, self.results_dir)
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_packages(self):
"""
Install all downloaded packages.
"""
os.chdir(self.srcdir)
rpm_file_names = " ".join(self._get_rpm_file_names())
utils.system("yum --nogpgcheck -y localinstall %s" % rpm_file_names)
class SourceDirInstaller(BaseInstaller):
"""
Class that handles building/installing KVM directly from a tarball or
a single source code dir.
"""
def set_install_params(self, test, params):
"""
Initializes class attributes, and retrieves KVM code.
@param test: kvm test object
@param params: Dictionary with test arguments
"""
super(SourceDirInstaller, self).set_install_params(test, params)
self.mod_install_dir = os.path.join(self.prefix, 'modules')
srcdir = params.get("srcdir", None)
self.path_to_roms = params.get("path_to_rom_images", None)
if self.install_mode == 'localsrc':
if srcdir is None:
raise error.TestError("Install from source directory specified"
"but no source directory provided on the"
"control file.")
else:
shutil.copytree(srcdir, self.srcdir)
elif self.install_mode == 'localtar':
tarball = params.get("tarball")
if not tarball:
raise error.TestError("KVM Tarball install specified but no"
" tarball provided on control file.")
logging.info("Installing KVM from a local tarball")
logging.info("Using tarball %s")
tarball = utils.unmap_url("/", params.get("tarball"), "/tmp")
utils.extract_tarball_to_dir(tarball, self.srcdir)
if self.install_mode in ['localtar', 'srcdir']:
self.repo_type = virt_utils.check_kvm_source_dir(self.srcdir)
p = os.path.join(self.srcdir, 'configure')
self.configure_options = virt_installer.check_configure_options(p)
def _build(self):
make_jobs = utils.count_cpus()
os.chdir(self.srcdir)
# For testing purposes, it's better to build qemu binaries with
# debugging symbols, so we can extract more meaningful stack traces.
cfg = "./configure --prefix=%s" % self.prefix
if "--disable-strip" in self.configure_options:
cfg += " --disable-strip"
steps = [cfg, "make clean", "make -j %s" % make_jobs]
logging.info("Building KVM")
for step in steps:
utils.system(step)
def _install(self):
os.chdir(self.srcdir)
logging.info("Installing KVM userspace")
if self.repo_type == 1:
utils.system("make -C qemu install")
elif self.repo_type == 2:
utils.system("make install")
if self.path_to_roms:
install_roms(self.path_to_roms, self.prefix)
self.install_unittests()
create_symlinks(test_bindir=self.test_bindir,
prefix=self.prefix,
unittest=self.unittest_prefix)
def install(self):
self._build()
self._install()
self.reload_modules_if_needed()
if self.save_results:
virt_utils.archive_as_tarball(self.srcdir, self.results_dir)
class GitRepo(object):
def __init__(self, installer, prefix,
srcdir, build_steps=[], repo_param=None):
params = installer.params
self.installer = installer
self.repo = params.get(repo_param or (prefix + '_repo'))
self.branch = params.get(prefix + '_branch', 'master')
self.lbranch = params.get(prefix + '_lbranch', 'master')
self.commit = params.get(prefix + '_commit', None)
# The config system yields strings, which have to be evalued
self.patches = eval(params.get(prefix + '_patches', "[]"))
self.build_steps = build_steps
self.srcdir = os.path.join(self.installer.srcdir, srcdir)
def fetch_and_patch(self):
if not self.repo:
return
virt_utils.get_git_branch(self.repo, self.branch, self.srcdir,
self.commit, self.lbranch)
os.chdir(self.srcdir)
for patch in self.patches:
utils.get_file(patch, os.path.join(self.srcdir,
os.path.basename(patch)))
utils.system('patch -p1 < %s' % os.path.basename(patch))
def build(self):
os.chdir(self.srcdir)
for step in self.build_steps:
logging.info(step)
utils.run(step)
class GitInstaller(SourceDirInstaller):
def _pull_code(self):
"""
Retrieves code from git repositories.
"""
params = self.params
make_jobs = utils.count_cpus()
cfg = 'PKG_CONFIG_PATH="%s/lib/pkgconfig:%s/share/pkgconfig" ./configure' % (
self.prefix, self.prefix)
self.spice_protocol = GitRepo(installer=self, prefix='spice_protocol',
srcdir='spice-protocol',
build_steps= ['./autogen.sh',
'./configure --prefix=%s' % self.prefix,
'make clean',
'make -j %s' % (make_jobs),
'make install'])
self.spice = GitRepo(installer=self, prefix='spice', srcdir='spice',
build_steps= ['PKG_CONFIG_PATH="%s/lib/pkgconfig:%s/share/pkgconfig" CXXFLAGS=-Wl,--add-needed ./autogen.sh --prefix=%s' % (self.prefix, self.prefix, self.prefix),
'make clean',
'make -j %s' % (make_jobs),
'make install'])
self.userspace = GitRepo(installer=self, prefix='user',
repo_param='user_git_repo', srcdir='kvm_userspace')
p = os.path.join(self.userspace.srcdir, 'configure')
self.configure_options = virt_installer.check_configure_options(p)
cfg = cfg + ' --prefix=%s' % self.prefix
if "--disable-strip" in self.configure_options:
cfg += ' --disable-strip'
if self.extra_configure_options:
cfg += ' %s' % self.extra_configure_options
self.userspace.build_steps=[cfg, 'make clean', 'make -j %s' % make_jobs]
if not self.userspace.repo:
message = "KVM user git repository path not specified"
logging.error(message)
raise error.TestError(message)
for repo in [self.userspace, self.spice_protocol, self.spice]:
if not repo.repo:
continue
repo.fetch_and_patch()
def _build(self):
if self.spice_protocol.repo:
logging.info('Building Spice-protocol')
self.spice_protocol.build()
if self.spice.repo:
logging.info('Building Spice')
self.spice.build()
logging.info('Building KVM userspace code')
self.userspace.build()
def _install(self):
os.chdir(self.userspace.srcdir)
utils.system('make install')
if self.path_to_roms:
install_roms(self.path_to_roms, self.prefix)
self.install_unittests()
create_symlinks(test_bindir=self.test_bindir, prefix=self.prefix,
bin_list=None,
unittest=self.unittest_prefix)
def install(self):
self._pull_code()
self._build()
self._install()
self.reload_modules_if_needed()
if self.save_results:
virt_utils.archive_as_tarball(self.srcdir, self.results_dir)
class PreInstalledKvm(BaseInstaller):
def install(self):
logging.info("Expecting KVM to be already installed. Doing nothing")
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 KVM installation fails.
"""
def __init__(self, msg="KVM install failed"):
self._msg = msg
def load_modules(self):
"""Will refuse to load the KVM modules as install failed"""
raise FailedKvmInstall("KVM modules not available. reason: %s" % (self._msg))
installer_classes = {
'localsrc': SourceDirInstaller,
'localtar': SourceDirInstaller,
'git': GitInstaller,
'yum': YumInstaller,
'koji': KojiInstaller,
'preinstalled': PreInstalledKvm,
}
def _installer_class(install_mode):
c = installer_classes.get(install_mode)
if c is None:
raise error.TestError('Invalid or unsupported'
' install mode: %s' % install_mode)
return c
def make_installer(params):
# priority:
# - 'install_mode' param
# - 'mode' param
mode = params.get("install_mode", params.get("mode"))
klass = _installer_class(mode)
return klass(mode)