• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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