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)