1 import os, logging, datetime, glob, shutil 2 from autotest_lib.client.bin import utils, os_dep 3 from autotest_lib.client.common_lib import error 4 import virt_utils, virt_installer 5 6 7 def kill_qemu_processes(): 8 """ 9 Kills all qemu processes, also kills all processes holding /dev/kvm down. 10 """ 11 logging.debug("Killing any qemu processes that might be left behind") 12 utils.system("pkill qemu", ignore_status=True) 13 # Let's double check to see if some other process is holding /dev/kvm 14 if os.path.isfile("/dev/kvm"): 15 utils.system("fuser -k /dev/kvm", ignore_status=True) 16 17 18 def create_symlinks(test_bindir, prefix=None, bin_list=None, unittest=None): 19 """ 20 Create symbolic links for the appropriate qemu and qemu-img commands on 21 the kvm test bindir. 22 23 @param test_bindir: KVM test bindir 24 @param prefix: KVM prefix path 25 @param bin_list: List of qemu binaries to link 26 @param unittest: Path to configuration file unittests.cfg 27 """ 28 qemu_path = os.path.join(test_bindir, "qemu") 29 qemu_img_path = os.path.join(test_bindir, "qemu-img") 30 qemu_unittest_path = os.path.join(test_bindir, "unittests") 31 if os.path.lexists(qemu_path): 32 os.unlink(qemu_path) 33 if os.path.lexists(qemu_img_path): 34 os.unlink(qemu_img_path) 35 if unittest and os.path.lexists(qemu_unittest_path): 36 os.unlink(qemu_unittest_path) 37 38 logging.debug("Linking qemu binaries") 39 40 if bin_list: 41 for bin in bin_list: 42 if os.path.basename(bin) == 'qemu-kvm': 43 os.symlink(bin, qemu_path) 44 elif os.path.basename(bin) == 'qemu-img': 45 os.symlink(bin, qemu_img_path) 46 47 elif prefix: 48 kvm_qemu = os.path.join(prefix, "bin", "qemu-system-x86_64") 49 if not os.path.isfile(kvm_qemu): 50 raise error.TestError('Invalid qemu path') 51 kvm_qemu_img = os.path.join(prefix, "bin", "qemu-img") 52 if not os.path.isfile(kvm_qemu_img): 53 raise error.TestError('Invalid qemu-img path') 54 os.symlink(kvm_qemu, qemu_path) 55 os.symlink(kvm_qemu_img, qemu_img_path) 56 57 if unittest: 58 logging.debug("Linking unittest dir") 59 os.symlink(unittest, qemu_unittest_path) 60 61 62 def install_roms(rom_dir, prefix): 63 logging.debug("Path to roms specified. Copying roms to install prefix") 64 rom_dst_dir = os.path.join(prefix, 'share', 'qemu') 65 for rom_src in glob.glob('%s/*.bin' % rom_dir): 66 rom_dst = os.path.join(rom_dst_dir, os.path.basename(rom_src)) 67 logging.debug("Copying rom file %s to %s", rom_src, rom_dst) 68 shutil.copy(rom_src, rom_dst) 69 70 71 class KvmInstallException(Exception): 72 pass 73 74 75 class FailedKvmInstall(KvmInstallException): 76 pass 77 78 79 class KvmNotInstalled(KvmInstallException): 80 pass 81 82 83 class BaseInstaller(object): 84 def __init__(self, mode=None): 85 self.install_mode = mode 86 self._full_module_list = None 87 88 def set_install_params(self, test, params): 89 self.params = params 90 91 load_modules = params.get('load_modules', 'no') 92 if not load_modules or load_modules == 'yes': 93 self.should_load_modules = True 94 elif load_modules == 'no': 95 self.should_load_modules = False 96 default_extra_modules = str(None) 97 self.extra_modules = eval(params.get("extra_modules", 98 default_extra_modules)) 99 100 self.cpu_vendor = virt_utils.get_cpu_vendor() 101 102 self.srcdir = test.srcdir 103 if not os.path.isdir(self.srcdir): 104 os.makedirs(self.srcdir) 105 106 self.test_bindir = test.bindir 107 self.results_dir = test.resultsdir 108 109 # KVM build prefix, for the modes that do need it 110 prefix = os.path.join(test.bindir, 'build') 111 self.prefix = os.path.abspath(prefix) 112 113 # Current host kernel directory 114 default_host_kernel_source = '/lib/modules/%s/build' % os.uname()[2] 115 self.host_kernel_srcdir = params.get('host_kernel_source', 116 default_host_kernel_source) 117 118 # Extra parameters that can be passed to the configure script 119 self.extra_configure_options = params.get('extra_configure_options', 120 None) 121 122 # Do we want to save the result of the build on test.resultsdir? 123 self.save_results = True 124 save_results = params.get('save_results', 'no') 125 if save_results == 'no': 126 self.save_results = False 127 128 self._full_module_list = list(self._module_list()) 129 130 131 def install_unittests(self): 132 userspace_srcdir = os.path.join(self.srcdir, "kvm_userspace") 133 test_repo = self.params.get("test_git_repo") 134 test_branch = self.params.get("test_branch", "master") 135 test_commit = self.params.get("test_commit", None) 136 test_lbranch = self.params.get("test_lbranch", "master") 137 138 if test_repo: 139 test_srcdir = os.path.join(self.srcdir, "kvm-unit-tests") 140 virt_utils.get_git_branch(test_repo, test_branch, test_srcdir, 141 test_commit, test_lbranch) 142 unittest_cfg = os.path.join(test_srcdir, 'x86', 143 'unittests.cfg') 144 self.test_srcdir = test_srcdir 145 else: 146 unittest_cfg = os.path.join(userspace_srcdir, 'kvm', 'test', 'x86', 147 'unittests.cfg') 148 self.unittest_cfg = None 149 if os.path.isfile(unittest_cfg): 150 self.unittest_cfg = unittest_cfg 151 else: 152 if test_repo: 153 logging.error("No unittest config file %s found, skipping " 154 "unittest build", self.unittest_cfg) 155 156 self.unittest_prefix = None 157 if self.unittest_cfg: 158 logging.info("Building and installing unittests") 159 os.chdir(os.path.dirname(os.path.dirname(self.unittest_cfg))) 160 utils.system('./configure --prefix=%s' % self.prefix) 161 utils.system('make') 162 utils.system('make install') 163 self.unittest_prefix = os.path.join(self.prefix, 'share', 'qemu', 164 'tests') 165 166 167 def full_module_list(self): 168 """Return the module list used by the installer 169 170 Used by the module_probe test, to avoid using utils.unload_module(). 171 """ 172 if self._full_module_list is None: 173 raise KvmNotInstalled("KVM modules not installed yet (installer: %s)" % (type(self))) 174 return self._full_module_list 175 176 177 def _module_list(self): 178 """Generate the list of modules that need to be loaded 179 """ 180 yield 'kvm' 181 yield 'kvm-%s' % (self.cpu_vendor) 182 if self.extra_modules: 183 for module in self.extra_modules: 184 yield module 185 186 187 def _load_modules(self, mod_list): 188 """ 189 Load the KVM modules 190 191 May be overridden by subclasses. 192 """ 193 logging.info("Loading KVM modules") 194 for module in mod_list: 195 utils.system("modprobe %s" % module) 196 197 198 def load_modules(self, mod_list=None): 199 if mod_list is None: 200 mod_list = self.full_module_list() 201 self._load_modules(mod_list) 202 203 204 def _unload_modules(self, mod_list=None): 205 """ 206 Just unload the KVM modules, without trying to kill Qemu 207 """ 208 if mod_list is None: 209 mod_list = self.full_module_list() 210 logging.info("Unloading previously loaded KVM modules") 211 for module in reversed(mod_list): 212 utils.unload_module(module) 213 214 215 def unload_modules(self, mod_list=None): 216 """ 217 Kill Qemu and unload the KVM modules 218 """ 219 kill_qemu_processes() 220 self._unload_modules(mod_list) 221 222 223 def reload_modules(self): 224 """ 225 Reload the KVM modules after killing Qemu and unloading the current modules 226 """ 227 self.unload_modules() 228 self.load_modules() 229 230 231 def reload_modules_if_needed(self): 232 if self.should_load_modules: 233 self.reload_modules() 234 235 236 class YumInstaller(BaseInstaller): 237 """ 238 Class that uses yum to install and remove packages. 239 """ 240 def set_install_params(self, test, params): 241 super(YumInstaller, self).set_install_params(test, params) 242 # Checking if all required dependencies are available 243 os_dep.command("rpm") 244 os_dep.command("yum") 245 246 default_pkg_list = str(['qemu-kvm', 'qemu-kvm-tools']) 247 default_qemu_bin_paths = str(['/usr/bin/qemu-kvm', '/usr/bin/qemu-img']) 248 default_pkg_path_list = str(None) 249 self.pkg_list = eval(params.get("pkg_list", default_pkg_list)) 250 self.pkg_path_list = eval(params.get("pkg_path_list", 251 default_pkg_path_list)) 252 self.qemu_bin_paths = eval(params.get("qemu_bin_paths", 253 default_qemu_bin_paths)) 254 255 256 def _clean_previous_installs(self): 257 kill_qemu_processes() 258 removable_packages = "" 259 for pkg in self.pkg_list: 260 removable_packages += " %s" % pkg 261 262 utils.system("yum remove -y %s" % removable_packages) 263 264 265 def _get_packages(self): 266 for pkg in self.pkg_path_list: 267 utils.get_file(pkg, os.path.join(self.srcdir, 268 os.path.basename(pkg))) 269 270 271 def _install_packages(self): 272 """ 273 Install all downloaded packages. 274 """ 275 os.chdir(self.srcdir) 276 utils.system("yum install --nogpgcheck -y *.rpm") 277 278 279 def install(self): 280 self.install_unittests() 281 self._clean_previous_installs() 282 self._get_packages() 283 self._install_packages() 284 create_symlinks(test_bindir=self.test_bindir, 285 bin_list=self.qemu_bin_paths, 286 unittest=self.unittest_prefix) 287 self.reload_modules_if_needed() 288 if self.save_results: 289 virt_utils.archive_as_tarball(self.srcdir, self.results_dir) 290 291 292 class KojiInstaller(YumInstaller): 293 """ 294 Class that handles installing KVM from the fedora build service, koji. 295 296 It uses yum to install and remove packages. Packages are specified 297 according to the syntax defined in the PkgSpec class. 298 """ 299 def set_install_params(self, test, params): 300 """ 301 Gets parameters and initializes the package downloader. 302 303 @param test: kvm test object 304 @param params: Dictionary with test arguments 305 """ 306 super(KojiInstaller, self).set_install_params(test, params) 307 self.tag = params.get("koji_tag", None) 308 self.koji_cmd = params.get("koji_cmd", None) 309 if self.tag is not None: 310 virt_utils.set_default_koji_tag(self.tag) 311 self.koji_pkgs = eval(params.get("koji_pkgs", "[]")) 312 313 314 def _get_packages(self): 315 """ 316 Downloads the specific arch RPMs for the specific build name. 317 """ 318 koji_client = virt_utils.KojiClient(cmd=self.koji_cmd) 319 for pkg_text in self.koji_pkgs: 320 pkg = virt_utils.KojiPkgSpec(pkg_text) 321 if pkg.is_valid(): 322 koji_client.get_pkgs(pkg, dst_dir=self.srcdir) 323 else: 324 logging.error('Package specification (%s) is invalid: %s', pkg, 325 pkg.describe_invalid()) 326 327 328 def _clean_previous_installs(self): 329 kill_qemu_processes() 330 removable_packages = " ".join(self._get_rpm_names()) 331 utils.system("yum -y remove %s" % removable_packages) 332 333 334 def install(self): 335 self._clean_previous_installs() 336 self._get_packages() 337 self._install_packages() 338 self.install_unittests() 339 create_symlinks(test_bindir=self.test_bindir, 340 bin_list=self.qemu_bin_paths, 341 unittest=self.unittest_prefix) 342 self.reload_modules_if_needed() 343 if self.save_results: 344 virt_utils.archive_as_tarball(self.srcdir, self.results_dir) 345 346 347 def _get_rpm_names(self): 348 all_rpm_names = [] 349 koji_client = virt_utils.KojiClient(cmd=self.koji_cmd) 350 for pkg_text in self.koji_pkgs: 351 pkg = virt_utils.KojiPkgSpec(pkg_text) 352 rpm_names = koji_client.get_pkg_rpm_names(pkg) 353 all_rpm_names += rpm_names 354 return all_rpm_names 355 356 357 def _get_rpm_file_names(self): 358 all_rpm_file_names = [] 359 koji_client = virt_utils.KojiClient(cmd=self.koji_cmd) 360 for pkg_text in self.koji_pkgs: 361 pkg = virt_utils.KojiPkgSpec(pkg_text) 362 rpm_file_names = koji_client.get_pkg_rpm_file_names(pkg) 363 all_rpm_file_names += rpm_file_names 364 return all_rpm_file_names 365 366 367 def _install_packages(self): 368 """ 369 Install all downloaded packages. 370 """ 371 os.chdir(self.srcdir) 372 rpm_file_names = " ".join(self._get_rpm_file_names()) 373 utils.system("yum --nogpgcheck -y localinstall %s" % rpm_file_names) 374 375 376 class SourceDirInstaller(BaseInstaller): 377 """ 378 Class that handles building/installing KVM directly from a tarball or 379 a single source code dir. 380 """ 381 def set_install_params(self, test, params): 382 """ 383 Initializes class attributes, and retrieves KVM code. 384 385 @param test: kvm test object 386 @param params: Dictionary with test arguments 387 """ 388 super(SourceDirInstaller, self).set_install_params(test, params) 389 390 self.mod_install_dir = os.path.join(self.prefix, 'modules') 391 392 srcdir = params.get("srcdir", None) 393 self.path_to_roms = params.get("path_to_rom_images", None) 394 395 if self.install_mode == 'localsrc': 396 if srcdir is None: 397 raise error.TestError("Install from source directory specified" 398 "but no source directory provided on the" 399 "control file.") 400 else: 401 shutil.copytree(srcdir, self.srcdir) 402 403 elif self.install_mode == 'localtar': 404 tarball = params.get("tarball") 405 if not tarball: 406 raise error.TestError("KVM Tarball install specified but no" 407 " tarball provided on control file.") 408 logging.info("Installing KVM from a local tarball") 409 logging.info("Using tarball %s") 410 tarball = utils.unmap_url("/", params.get("tarball"), "/tmp") 411 utils.extract_tarball_to_dir(tarball, self.srcdir) 412 413 if self.install_mode in ['localtar', 'srcdir']: 414 self.repo_type = virt_utils.check_kvm_source_dir(self.srcdir) 415 p = os.path.join(self.srcdir, 'configure') 416 self.configure_options = virt_installer.check_configure_options(p) 417 418 419 def _build(self): 420 make_jobs = utils.count_cpus() 421 os.chdir(self.srcdir) 422 # For testing purposes, it's better to build qemu binaries with 423 # debugging symbols, so we can extract more meaningful stack traces. 424 cfg = "./configure --prefix=%s" % self.prefix 425 if "--disable-strip" in self.configure_options: 426 cfg += " --disable-strip" 427 steps = [cfg, "make clean", "make -j %s" % make_jobs] 428 logging.info("Building KVM") 429 for step in steps: 430 utils.system(step) 431 432 433 def _install(self): 434 os.chdir(self.srcdir) 435 logging.info("Installing KVM userspace") 436 if self.repo_type == 1: 437 utils.system("make -C qemu install") 438 elif self.repo_type == 2: 439 utils.system("make install") 440 if self.path_to_roms: 441 install_roms(self.path_to_roms, self.prefix) 442 self.install_unittests() 443 create_symlinks(test_bindir=self.test_bindir, 444 prefix=self.prefix, 445 unittest=self.unittest_prefix) 446 447 448 def install(self): 449 self._build() 450 self._install() 451 self.reload_modules_if_needed() 452 if self.save_results: 453 virt_utils.archive_as_tarball(self.srcdir, self.results_dir) 454 455 class GitRepo(object): 456 def __init__(self, installer, prefix, 457 srcdir, build_steps=[], repo_param=None): 458 params = installer.params 459 self.installer = installer 460 self.repo = params.get(repo_param or (prefix + '_repo')) 461 self.branch = params.get(prefix + '_branch', 'master') 462 self.lbranch = params.get(prefix + '_lbranch', 'master') 463 self.commit = params.get(prefix + '_commit', None) 464 # The config system yields strings, which have to be evalued 465 self.patches = eval(params.get(prefix + '_patches', "[]")) 466 self.build_steps = build_steps 467 self.srcdir = os.path.join(self.installer.srcdir, srcdir) 468 469 470 def fetch_and_patch(self): 471 if not self.repo: 472 return 473 virt_utils.get_git_branch(self.repo, self.branch, self.srcdir, 474 self.commit, self.lbranch) 475 os.chdir(self.srcdir) 476 for patch in self.patches: 477 utils.get_file(patch, os.path.join(self.srcdir, 478 os.path.basename(patch))) 479 utils.system('patch -p1 < %s' % os.path.basename(patch)) 480 481 482 def build(self): 483 os.chdir(self.srcdir) 484 for step in self.build_steps: 485 logging.info(step) 486 utils.run(step) 487 488 489 class GitInstaller(SourceDirInstaller): 490 def _pull_code(self): 491 """ 492 Retrieves code from git repositories. 493 """ 494 params = self.params 495 make_jobs = utils.count_cpus() 496 cfg = 'PKG_CONFIG_PATH="%s/lib/pkgconfig:%s/share/pkgconfig" ./configure' % ( 497 self.prefix, self.prefix) 498 499 self.spice_protocol = GitRepo(installer=self, prefix='spice_protocol', 500 srcdir='spice-protocol', 501 build_steps= ['./autogen.sh', 502 './configure --prefix=%s' % self.prefix, 503 'make clean', 504 'make -j %s' % (make_jobs), 505 'make install']) 506 507 self.spice = GitRepo(installer=self, prefix='spice', srcdir='spice', 508 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), 509 'make clean', 510 'make -j %s' % (make_jobs), 511 'make install']) 512 513 self.userspace = GitRepo(installer=self, prefix='user', 514 repo_param='user_git_repo', srcdir='kvm_userspace') 515 516 p = os.path.join(self.userspace.srcdir, 'configure') 517 self.configure_options = virt_installer.check_configure_options(p) 518 519 cfg = cfg + ' --prefix=%s' % self.prefix 520 if "--disable-strip" in self.configure_options: 521 cfg += ' --disable-strip' 522 if self.extra_configure_options: 523 cfg += ' %s' % self.extra_configure_options 524 525 self.userspace.build_steps=[cfg, 'make clean', 'make -j %s' % make_jobs] 526 527 if not self.userspace.repo: 528 message = "KVM user git repository path not specified" 529 logging.error(message) 530 raise error.TestError(message) 531 532 for repo in [self.userspace, self.spice_protocol, self.spice]: 533 if not repo.repo: 534 continue 535 repo.fetch_and_patch() 536 537 def _build(self): 538 if self.spice_protocol.repo: 539 logging.info('Building Spice-protocol') 540 self.spice_protocol.build() 541 542 if self.spice.repo: 543 logging.info('Building Spice') 544 self.spice.build() 545 546 logging.info('Building KVM userspace code') 547 self.userspace.build() 548 549 550 def _install(self): 551 os.chdir(self.userspace.srcdir) 552 utils.system('make install') 553 554 if self.path_to_roms: 555 install_roms(self.path_to_roms, self.prefix) 556 self.install_unittests() 557 create_symlinks(test_bindir=self.test_bindir, prefix=self.prefix, 558 bin_list=None, 559 unittest=self.unittest_prefix) 560 561 562 def install(self): 563 self._pull_code() 564 self._build() 565 self._install() 566 self.reload_modules_if_needed() 567 if self.save_results: 568 virt_utils.archive_as_tarball(self.srcdir, self.results_dir) 569 570 571 class PreInstalledKvm(BaseInstaller): 572 def install(self): 573 logging.info("Expecting KVM to be already installed. Doing nothing") 574 575 576 class FailedInstaller: 577 """ 578 Class used to be returned instead of the installer if a installation fails 579 580 Useful to make sure no installer object is used if KVM installation fails. 581 """ 582 def __init__(self, msg="KVM install failed"): 583 self._msg = msg 584 585 586 def load_modules(self): 587 """Will refuse to load the KVM modules as install failed""" 588 raise FailedKvmInstall("KVM modules not available. reason: %s" % (self._msg)) 589 590 591 installer_classes = { 592 'localsrc': SourceDirInstaller, 593 'localtar': SourceDirInstaller, 594 'git': GitInstaller, 595 'yum': YumInstaller, 596 'koji': KojiInstaller, 597 'preinstalled': PreInstalledKvm, 598 } 599 600 601 def _installer_class(install_mode): 602 c = installer_classes.get(install_mode) 603 if c is None: 604 raise error.TestError('Invalid or unsupported' 605 ' install mode: %s' % install_mode) 606 return c 607 608 609 def make_installer(params): 610 # priority: 611 # - 'install_mode' param 612 # - 'mode' param 613 mode = params.get("install_mode", params.get("mode")) 614 klass = _installer_class(mode) 615 return klass(mode) 616