| 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) |