1''' 2This module implements classes that perform the installation of the 3virtualization software on a host system. 4 5These classes can be, and usually are, inherited by subclasses that implement 6custom logic for each virtualization hypervisor/software. 7''' 8 9import os, logging 10from autotest_lib.client.bin import utils, os_dep 11from autotest_lib.client.common_lib import error 12from autotest_lib.client.virt import virt_utils 13 14class VirtInstallException(Exception): 15 ''' 16 Base virtualization software components installation exception 17 ''' 18 pass 19 20 21class VirtInstallFailed(VirtInstallException): 22 ''' 23 Installation of virtualization software components failed 24 ''' 25 pass 26 27 28class VirtInstallNotInstalled(VirtInstallException): 29 ''' 30 Virtualization software components are not installed 31 ''' 32 pass 33 34 35class BaseInstaller(object): 36 ''' 37 Base virtualization software installer 38 39 This class holds all the skeleton features for installers and should be 40 inherited from when creating a new installer. 41 ''' 42 def __init__(self, mode, name, test=None, params=None): 43 ''' 44 Instantiates a new base installer 45 46 @param mode: installer mode, such as git_repo, local_src, etc 47 @param name: installer short name, foo for git_repo_foo 48 @param test: test 49 @param params: params 50 ''' 51 self.mode = mode 52 self.name = name 53 self.params = params 54 self.param_key_prefix = '%s_%s' % (self.mode, 55 self.name) 56 57 if test and params: 58 self.set_install_params(test, params) 59 60 61 def _set_test_dirs(self, test): 62 ''' 63 Save common test directories paths (srcdir, bindir) as class attributes 64 65 Test variables values are saved here again because it's not possible to 66 pickle the test instance inside BaseInstaller due to limitations 67 in the pickle protocol. And, in case this pickle thing needs more 68 explanation, take a loot at the Env class inside virt_utils. 69 70 Besides that, we also ensure that srcdir exists, by creating it if 71 necessary. 72 73 For reference: 74 * bindir = tests/<test> 75 * srcdir = tests/<test>/src 76 77 So, for KVM tests, it'd evaluate to: 78 * bindir = tests/kvm/ 79 * srcdir = tests/kvm/src 80 ''' 81 self.test_bindir = test.bindir 82 self.test_srcdir = test.srcdir 83 84 # 85 # test_bindir is guaranteed to exist, but test_srcdir is not 86 # 87 if not os.path.isdir(test.srcdir): 88 os.makedirs(test.srcdir) 89 90 91 def _set_param_load_module(self): 92 ''' 93 Checks whether kernel modules should be loaded 94 95 Default behavior is to load modules unless set to 'no' 96 97 Configuration file parameter: load_modules 98 Class attribute set: should_load_modules 99 ''' 100 load_modules = self.params.get('load_modules', 'no') 101 if not load_modules or load_modules == 'yes': 102 self.should_load_modules = True 103 elif load_modules == 'no': 104 self.should_load_modules = False 105 106 107 def _set_param_module_list(self): 108 ''' 109 Sets the list of kernel modules to be loaded during installation 110 111 Configuration file parameter: module_list 112 Class attribute set: module_list 113 ''' 114 self.module_list = self.params.get('module_list', '').split() 115 116 117 def _set_param_save_results(self): 118 ''' 119 Checks whether to save the result of the build on test.resultsdir 120 121 Configuration file parameter: save_results 122 Class attribute set: save_results 123 ''' 124 self.save_results = True 125 save_results = self.params.get('save_results', 'no') 126 if save_results == 'no': 127 self.save_results = False 128 129 130 def set_install_params(self, test=None, params=None): 131 ''' 132 Called by test to setup parameters from the configuration file 133 ''' 134 if test is not None: 135 self._set_test_dirs(test) 136 137 if params is not None: 138 self.params = params 139 self._set_param_load_module() 140 self._set_param_module_list() 141 self._set_param_save_results() 142 143 144 def _install_phase_cleanup(self): 145 ''' 146 Optional install phase for removing previous version of the software 147 148 If a particular virtualization software installation mechanism 149 needs to download files (it most probably does), override this 150 method with custom functionality. 151 152 This replaces methods such as KojiInstaller._get_packages() 153 ''' 154 pass 155 156 157 def _install_phase_cleanup_verify(self): 158 ''' 159 Optional install phase for removing previous version of the software 160 161 If a particular virtualization software installation mechanism 162 needs to download files (it most probably does), override this 163 method with custom functionality. 164 165 This replaces methods such as KojiInstaller._get_packages() 166 ''' 167 pass 168 169 170 def _install_phase_download(self): 171 ''' 172 Optional install phase for downloading software 173 174 If a particular virtualization software installation mechanism 175 needs to download files (it most probably does), override this 176 method with custom functionality. 177 178 This replaces methods such as KojiInstaller._get_packages() 179 ''' 180 pass 181 182 183 def _install_phase_download_verify(self): 184 ''' 185 Optional install phase for checking downloaded software 186 187 If you want to make sure the downloaded software is in good shape, 188 override this method. 189 190 Ideas for using this method: 191 * check MD5SUM/SHA1SUM for tarball downloads 192 * check RPM files, probaly by signature (rpm -k) 193 * git status and check if there's no locally modified files 194 ''' 195 pass 196 197 198 def _install_phase_prepare(self): 199 ''' 200 Optional install phase for preparing software 201 202 If a particular virtualization software installation mechanism 203 needs to do something to the obtained software, such as extracting 204 a tarball or applying patches, this should be done here. 205 ''' 206 pass 207 208 209 def _install_phase_prepare_verify(self): 210 ''' 211 Optional install phase for checking software preparation 212 213 Ideas for using this method: 214 * git status and check if there are locally patched files 215 ''' 216 pass 217 218 219 def _install_phase_build(self): 220 ''' 221 Optional install phase for building software 222 223 If a particular virtualization software installation mechanism 224 needs to compile source code, it should be done here. 225 ''' 226 pass 227 228 229 def _install_phase_build_verify(self): 230 ''' 231 Optional install phase for checking software build 232 233 Ideas for using this method: 234 * running 'make test' or something similar to it 235 ''' 236 pass 237 238 239 def _install_phase_install(self): 240 ''' 241 Optional install phase for actually installing software 242 243 Ideas for using this method: 244 * running 'make install' or something similar to it 245 * running 'yum localinstall *.rpm' 246 ''' 247 pass 248 249 250 def _install_phase_install_verify(self): 251 ''' 252 Optional install phase for checking the installed software 253 254 This should verify the installed software is in a desirable state. 255 Ideas for using this include: 256 * checking if installed files exists (like os.path.exists()) 257 * checking packages are indeed installed (rpm -q <pkg>.rpm) 258 ''' 259 pass 260 261 262 def _install_phase_init(self): 263 ''' 264 Optional install phase for initializing the installed software 265 266 This should initialize the installed software. Ideas for using this: 267 * loading kernel modules 268 * running services: 'service <daemon> start' 269 * linking software (whether built or downloaded) to a common path 270 ''' 271 pass 272 273 274 def _install_phase_init_verify(self): 275 ''' 276 Optional install phase for checking that software is initialized 277 278 This should verify that the installed software is running. Ideas for 279 using this include: 280 * checking service (daemon) status: 'service <daemon> status' 281 * checking service (functionality) status: 'virsh capabilities' 282 ''' 283 pass 284 285 286 def load_modules(self, module_list=None): 287 ''' 288 Load Linux Kernel modules the virtualization software may depend on 289 290 If module_directory is not set, the list of modules will simply be 291 loaded by the system stock modprobe tool, meaning that modules will be 292 looked for in the system default module paths. 293 294 @type module_list: list 295 @param module_list: list of kernel modules names to load 296 ''' 297 if module_list is None: 298 module_list = self.module_list 299 300 logging.info("Loading modules from default locations through " 301 "modprobe") 302 for module in module_list: 303 utils.system("modprobe %s" % module) 304 305 306 def unload_modules(self, module_list=None): 307 ''' 308 Unloads kernel modules 309 310 By default, if no module list is explicitly provided, the list on 311 params (coming from the configuration file) will be used. 312 ''' 313 if module_list is None: 314 module_list = self.module_list 315 module_list = reversed(module_list) 316 logging.info("Unloading kernel modules: %s" % ",".join(module_list)) 317 for module in module_list: 318 utils.unload_module(module) 319 320 321 def reload_modules(self): 322 """ 323 Reload the kernel modules (unload, then load) 324 """ 325 self.unload_modules() 326 self.load_modules() 327 328 329 def reload_modules_if_needed(self): 330 if self.should_load_modules: 331 self.reload_modules() 332 333 334 def install(self): 335 ''' 336 Performs the installation of the virtualization software 337 338 This is the main entry point of this class, and should either 339 be reimplemented completely, or simply implement one or many of the 340 install phases. 341 ''' 342 self._install_phase_cleanup() 343 self._install_phase_cleanup_verify() 344 345 self._install_phase_download() 346 self._install_phase_download_verify() 347 348 self._install_phase_prepare() 349 self._install_phase_prepare_verify() 350 351 self._install_phase_build() 352 self._install_phase_build_verify() 353 354 self._install_phase_install() 355 self._install_phase_install_verify() 356 357 self._install_phase_init() 358 self._install_phase_init_verify() 359 360 self.reload_modules_if_needed() 361 if self.save_results: 362 virt_utils.archive_as_tarball(self.srcdir, self.results_dir) 363 364 365 def uninstall(self): 366 ''' 367 Performs the uninstallations of the virtualization software 368 369 Note: This replaces old kvm_installer._clean_previous_install() 370 ''' 371 raise NotImplementedError 372 373 374class NoopInstaller(BaseInstaller): 375 ''' 376 Dummy installer that does nothing, useful when software is pre-installed 377 ''' 378 def install(self): 379 logging.info("Assuming virtualization software to be already " 380 "installed. Doing nothing") 381 382 383class YumInstaller(BaseInstaller): 384 ''' 385 Installs virtualization software using YUM 386 387 Notice: this class implements a change of behaviour if compared to 388 kvm_installer.YumInstaller.set_install_params(). There's no longer 389 a default package list, as each virtualization technology will have 390 a completely different default. This should now be kept at the 391 configuration file only. 392 393 For now this class implements support for installing from the configured 394 yum repos only. If the use case of installing from local RPM packages 395 arises, we'll implement that. 396 ''' 397 def set_install_params(self, test, params): 398 super(YumInstaller, self).set_install_params(test, params) 399 os_dep.command("rpm") 400 os_dep.command("yum") 401 self.yum_pkgs = eval(params.get("%s_pkgs" % self.param_key_prefix, 402 "[]")) 403 404 405 def _install_phase_cleanup(self): 406 packages_to_remove = " ".join(self.yum_pkgs) 407 utils.system("yum remove -y %s" % packages_to_remove) 408 409 410 def _install_phase_install(self): 411 if self.yum_pkgs: 412 os.chdir(self.test_srcdir) 413 utils.system("yum --nogpgcheck -y install %s" % 414 " ".join(self.yum_pkgs)) 415 416 417class KojiInstaller(BaseInstaller): 418 ''' 419 Handles virtualization software installation via koji/brew 420 421 It uses YUM to install and remove packages. 422 423 Change notice: this is not a subclass of YumInstaller anymore. The 424 parameters this class uses are different (koji_tag, koji_pgks) and 425 the install process runs YUM. 426 ''' 427 def set_install_params(self, test, params): 428 super(KojiInstaller, self).set_install_params(test, params) 429 os_dep.command("rpm") 430 os_dep.command("yum") 431 432 self.tag = params.get("%s_tag" % self.param_key_prefix, None) 433 self.koji_cmd = params.get("%s_cmd" % self.param_key_prefix, None) 434 if self.tag is not None: 435 virt_utils.set_default_koji_tag(self.tag) 436 self.koji_pkgs = eval(params.get("%s_pkgs" % self.param_key_prefix, 437 "[]")) 438 439 440 def _get_rpm_names(self): 441 all_rpm_names = [] 442 koji_client = virt_utils.KojiClient(cmd=self.koji_cmd) 443 for pkg_text in self.koji_pkgs: 444 pkg = virt_utils.KojiPkgSpec(pkg_text) 445 rpm_names = koji_client.get_pkg_rpm_names(pkg) 446 all_rpm_names += rpm_names 447 return all_rpm_names 448 449 450 def _get_rpm_file_names(self): 451 all_rpm_file_names = [] 452 koji_client = virt_utils.KojiClient(cmd=self.koji_cmd) 453 for pkg_text in self.koji_pkgs: 454 pkg = virt_utils.KojiPkgSpec(pkg_text) 455 rpm_file_names = koji_client.get_pkg_rpm_file_names(pkg) 456 all_rpm_file_names += rpm_file_names 457 return all_rpm_file_names 458 459 460 def _install_phase_cleanup(self): 461 removable_packages = " ".join(self._get_rpm_names()) 462 utils.system("yum -y remove %s" % removable_packages) 463 464 465 def _install_phase_download(self): 466 koji_client = virt_utils.KojiClient(cmd=self.koji_cmd) 467 for pkg_text in self.koji_pkgs: 468 pkg = virt_utils.KojiPkgSpec(pkg_text) 469 if pkg.is_valid(): 470 koji_client.get_pkgs(pkg, dst_dir=self.test_srcdir) 471 else: 472 logging.error('Package specification (%s) is invalid: %s' % 473 (pkg, pkg.describe_invalid())) 474 475 476 def _install_phase_install(self): 477 os.chdir(self.test_srcdir) 478 rpm_file_names = " ".join(self._get_rpm_file_names()) 479 utils.system("yum --nogpgcheck -y localinstall %s" % rpm_file_names) 480 481 482class BaseLocalSourceInstaller(BaseInstaller): 483 def set_install_params(self, test, params): 484 super(BaseLocalSourceInstaller, self).set_install_params(test, params) 485 self._set_install_prefix() 486 self._set_source_destination() 487 488 # 489 # There are really no choices for patch helpers 490 # 491 self.patch_helper = virt_utils.PatchParamHelper( 492 self.params, 493 self.param_key_prefix, 494 self.source_destination) 495 496 # 497 # These helpers should be set by child classes 498 # 499 self.content_helper = None 500 self.build_helper = None 501 502 503 def _set_install_prefix(self): 504 ''' 505 Prefix for installation of application built from source 506 507 When installing virtualization software from *source*, this is where 508 the resulting binaries will be installed. Usually this is the value 509 passed to the configure script, ie: ./configure --prefix=<value> 510 ''' 511 prefix = os.path.join(self.test_bindir, 'install_root') 512 self.install_prefix = os.path.abspath(prefix) 513 514 515 def _set_source_destination(self): 516 ''' 517 Sets the source code destination directory path 518 ''' 519 self.source_destination = os.path.join(self.test_srcdir, 520 self.name) 521 522 523 def _set_build_helper(self): 524 ''' 525 Sets the build helper, default is 'gnu_autotools' 526 ''' 527 build_helper_name = self.params.get('%s_build_helper' % 528 self.param_key_prefix, 529 'gnu_autotools') 530 if build_helper_name == 'gnu_autotools': 531 self.build_helper = virt_utils.GnuSourceBuildParamHelper( 532 self.params, self.param_key_prefix, 533 self.source_destination, self.install_prefix) 534 535 536 def _install_phase_download(self): 537 if self.content_helper is not None: 538 self.content_helper.execute() 539 540 541 def _install_phase_build(self): 542 if self.build_helper is not None: 543 self.build_helper.execute() 544 545 546 def _install_phase_install(self): 547 if self.build_helper is not None: 548 self.build_helper.install() 549 550 551class LocalSourceDirInstaller(BaseLocalSourceInstaller): 552 ''' 553 Handles software installation by building/installing from a source dir 554 ''' 555 def set_install_params(self, test, params): 556 super(LocalSourceDirInstaller, self).set_install_params(test, params) 557 558 self.content_helper = virt_utils.LocalSourceDirParamHelper( 559 params, 560 self.name, 561 self.source_destination) 562 563 self._set_build_helper() 564 565 566class LocalSourceTarInstaller(BaseLocalSourceInstaller): 567 ''' 568 Handles software installation by building/installing from a tarball 569 ''' 570 def set_install_params(self, test, params): 571 super(LocalSourceTarInstaller, self).set_install_params(test, params) 572 573 self.content_helper = virt_utils.LocalTarParamHelper( 574 params, 575 self.name, 576 self.source_destination) 577 578 self._set_build_helper() 579 580 581class RemoteSourceTarInstaller(BaseLocalSourceInstaller): 582 ''' 583 Handles software installation by building/installing from a remote tarball 584 ''' 585 def set_install_params(self, test, params): 586 super(RemoteSourceTarInstaller, self).set_install_params(test, params) 587 588 self.content_helper = virt_utils.RemoteTarParamHelper( 589 params, 590 self.name, 591 self.source_destination) 592 593 self._set_build_helper() 594 595 596class GitRepoInstaller(BaseLocalSourceInstaller): 597 def set_install_params(self, test, params): 598 super(GitRepoInstaller, self).set_install_params(test, params) 599 600 self.content_helper = virt_utils.GitRepoParamHelper( 601 params, 602 self.name, 603 self.source_destination) 604 605 self._set_build_helper() 606 607 608class FailedInstaller: 609 """ 610 Class used to be returned instead of the installer if a installation fails 611 612 Useful to make sure no installer object is used if virt installation fails 613 """ 614 def __init__(self, msg="Virtualization software install failed"): 615 self._msg = msg 616 617 618 def load_modules(self): 619 """ 620 Will refuse to load the kerkel modules as install failed 621 """ 622 raise VirtInstallFailed("Kernel modules not available. reason: %s" % 623 self._msg) 624