forked from autotest/virt-test
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathbase_installer.py
808 lines (618 loc) · 26.4 KB
/
base_installer.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
'''
This module implements classes that perform the installation of the
virtualization software on a host system.
These classes can be, and usually are, inherited by subclasses that implement
custom logic for each virtualization hypervisor/software.
'''
import os, logging
from autotest.client import utils, os_dep
from autotest.client.shared import error
import build_helper, utils_misc, utils_koji, yumrepo, arch
class NoModuleError(Exception):
'''
Error raised when no suitable modules were found to load
'''
pass
class VirtInstallException(Exception):
'''
Base virtualization software components installation exception
'''
pass
class VirtInstallFailed(VirtInstallException):
'''
Installation of virtualization software components failed
'''
pass
class VirtInstallNotInstalled(VirtInstallException):
'''
Virtualization software components are not installed
'''
pass
class BaseInstaller(object):
'''
Base virtualization software installer
This class holds all the skeleton features for installers and should be
inherited from when creating a new installer.
'''
def __init__(self, mode, name, test=None, params=None):
'''
Instantiates a new base installer
@param mode: installer mode, such as git_repo, local_src, etc
@param name: installer short name, foo for git_repo_foo
@param test: test
@param params: params
'''
self.mode = mode
self.name = name
self.params = params
self.param_key_prefix = '%s_%s' % (self.mode,
self.name)
# If a installer has a failure that can be worked around, save that
self.minor_failure = False
self.minor_failure_reason = ''
if test and params:
self.set_install_params(test, params)
def _set_test_dirs(self, test):
'''
Save common test directories paths as class attributes
Test variables values are saved here again because it's not possible to
pickle the test instance inside BaseInstaller due to limitations
in the pickle protocol. And, in case this pickle thing needs more
explanation, take a loot at the Env class inside build_helper.
Besides that, we also ensure that srcdir exists, by creating it if
necessary.
For reference:
* bindir = tests/<test>
* srcdir = tests/<test>/src
* resultsdir = results/<job>/<testname.tag>/results
So, for KVM tests, it'd evaluate to:
* bindir = tests/kvm/
* srcdir = tests/kvm/src
* resultsdir = results/<job>/kvm.<other_variant_names>.build/results
'''
self.test_bindir = test.bindir
self.test_srcdir = test.srcdir
self.test_builddir = test.builddir
self.test_resultsdir = test.resultsdir
#
# test_builddir is guaranteed to exist, but test_srcdir is not
#
if not os.path.isdir(test.srcdir):
os.makedirs(test.srcdir)
def _set_param_load_module(self):
'''
Checks whether kernel modules should be loaded
Default behavior is to load modules unless set to 'no'
Configuration file parameter: load_modules
Class attribute set: should_load_modules
'''
load_modules = self.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
def _set_param_module_list(self):
'''
Sets the list of kernel modules to be loaded during installation
Configuration file parameter: module_list
Class attribute set: module_list
'''
self.module_list = self.params.get('module_list', [])
def _set_param_save_results(self):
'''
Checks whether to save the result of the build on test.resultsdir
Configuration file parameter: save_results
Class attribute set: save_results
'''
self.save_results = True
save_results = self.params.get('save_results', 'no')
if save_results == 'no':
self.save_results = False
def _set_param_install_debug_info(self):
'''
Sets whether to enable debug information on installed software
Configuration file parameter: install_debug_info
Class attribute set: install_debug_info
'''
self.install_debug_info = True
install_debug_info = self.params.get('install_debug_info', 'no')
if install_debug_info == 'no':
self.install_debug_info = False
def _set_param_cleanup(self):
'''
Sets whether to enable debug information on installed software
Configuration file parameter: installer_cleanup
Class attribute set: cleanup
'''
self.cleanup = True
cleanup = self.params.get('installer_cleanup', 'yes')
if cleanup == 'no':
logging.debug("Setting installer cleanup attribute to False")
self.cleanup = False
def set_install_params(self, test=None, params=None):
'''
Called by test to setup parameters from the configuration file
'''
logging.info("calling set install params")
if test is not None:
self._set_test_dirs(test)
if params is not None:
self.params = params
self._set_param_load_module()
self._set_param_module_list()
self._set_param_save_results()
self._set_param_install_debug_info()
self._set_param_cleanup()
def _install_phase_cleanup(self):
'''
Optional install phase for removing previous version of the software
If a particular virtualization software installation mechanism
needs to download files (it most probably does), override this
method with custom functionality.
This replaces methods such as KojiInstaller._get_packages()
'''
pass
def _install_phase_cleanup_verify(self):
'''
Optional install phase for removing previous version of the software
If a particular virtualization software installation mechanism
needs to download files (it most probably does), override this
method with custom functionality.
This replaces methods such as KojiInstaller._get_packages()
'''
pass
def _install_phase_download(self):
'''
Optional install phase for downloading software
If a particular virtualization software installation mechanism
needs to download files (it most probably does), override this
method with custom functionality.
This replaces methods such as KojiInstaller._get_packages()
'''
pass
def _install_phase_download_verify(self):
'''
Optional install phase for checking downloaded software
If you want to make sure the downloaded software is in good shape,
override this method.
Ideas for using this method:
* check MD5SUM/SHA1SUM for tarball downloads
* check RPM files, probaly by signature (rpm -k)
* git status and check if there's no locally modified files
'''
pass
def _install_phase_prepare(self):
'''
Optional install phase for preparing software
If a particular virtualization software installation mechanism
needs to do something to the obtained software, such as extracting
a tarball or applying patches, this should be done here.
'''
pass
def _install_phase_prepare_verify(self):
'''
Optional install phase for checking software preparation
Ideas for using this method:
* git status and check if there are locally patched files
'''
pass
def _install_phase_build(self):
'''
Optional install phase for building software
If a particular virtualization software installation mechanism
needs to compile source code, it should be done here.
'''
pass
def _install_phase_build_verify(self):
'''
Optional install phase for checking software build
Ideas for using this method:
* running 'make test' or something similar to it
'''
pass
def _install_phase_install(self):
'''
Optional install phase for actually installing software
Ideas for using this method:
* running 'make install' or something similar to it
* running 'yum localinstall *.rpm'
'''
pass
def _install_phase_install_verify(self):
'''
Optional install phase for checking the installed software
This should verify the installed software is in a desirable state.
Ideas for using this include:
* checking if installed files exists (like os.path.exists())
* checking packages are indeed installed (rpm -q <pkg>.rpm)
'''
pass
def _install_phase_init(self):
'''
Optional install phase for initializing the installed software
This should initialize the installed software. Ideas for using this:
* loading kernel modules
* running services: 'service <daemon> start'
* linking software (whether built or downloaded) to a common path
'''
pass
def _install_phase_init_verify(self):
'''
Optional install phase for checking that software is initialized
This should verify that the installed software is running. Ideas for
using this include:
* checking service (daemon) status: 'service <daemon> status'
* checking service (functionality) status: 'virsh capabilities'
'''
pass
def write_version_keyval(self, test):
try:
version = self.get_version()
except AttributeError:
version = "Unknown"
sw_version = {('software_version_%s' % self.name): version}
logging.debug("Writing test keyval %s", sw_version)
test.write_test_keyval(sw_version)
def load_modules(self, module_list=None):
'''
Load Linux Kernel modules the virtualization software may depend on
If module_directory is not set, the list of modules will simply be
loaded by the system stock modprobe tool, meaning that modules will be
looked for in the system default module paths.
@type module_list: list
@param module_list: list of kernel modules names to load
'''
if module_list is None:
module_list = self.module_list
if not module_list:
raise NoModuleError("Module list empty")
logging.info("Loading modules from default locations through "
"modprobe")
for module in module_list:
utils.system("modprobe %s" % module)
def unload_modules(self, module_list=None):
'''
Unloads kernel modules
By default, if no module list is explicitly provided, the list on
params (coming from the configuration file) will be used.
'''
if module_list is None:
module_list = self.module_list
logging.info("Unloading kernel modules: %s" % " ".join(module_list))
for module in module_list:
utils.unload_module(module)
def reload_modules(self):
"""
Reload the kernel modules (unload, then load)
"""
self.unload_modules()
self.load_modules()
def reload_modules_if_needed(self):
if self.should_load_modules:
self.reload_modules()
def install(self, cleanup=True, download=True, prepare=True,
build=True, install=True, init=True):
'''
Performs the installation of the virtualization software
This is the main entry point of this class, and should either
be reimplemented completely, or simply implement one or many of the
install phases.
'''
if (cleanup and self.cleanup):
self._install_phase_cleanup()
self._install_phase_cleanup_verify()
if download:
self._install_phase_download()
self._install_phase_download_verify()
if prepare:
self._install_phase_prepare()
self._install_phase_prepare_verify()
if build:
self._install_phase_build()
self._install_phase_build_verify()
if install:
self._install_phase_install()
self._install_phase_install_verify()
if init:
self._install_phase_init()
self._install_phase_init_verify()
self.reload_modules_if_needed()
if self.save_results:
utils_misc.archive_as_tarball(self.test_srcdir,
self.test_resultsdir)
def uninstall(self):
'''
Performs the uninstallations of the virtualization software
Note: This replaces old qemu_installer._clean_previous_install()
'''
raise NotImplementedError
class NoopInstaller(BaseInstaller):
'''
Dummy installer that does nothing, useful when software is pre-installed
'''
def __init__(self, mode, name, test=None, params=None):
'''
If no previous install test ran, try to figure out modules to load.
@param mode (str): Install mode (yum, git, etc).
@param name (str): Installer name.
@param test: Virt test object.
@param params: Dict with test params.
'''
if params['vm_type'] == 'qemu':
params['module_list'] = arch.get_kvm_module_list()
super(NoopInstaller, self).__init__(mode, name, test, params)
def install(self):
logging.info("Assuming virtualization software to be already "
"installed. Doing nothing")
class YumInstaller(BaseInstaller):
'''
Installs virtualization software using YUM
Notice: this class implements a change of behaviour if compared to
qemu_installer.YumInstaller.set_install_params(). There's no longer
a default package list, as each virtualization technology will have
a completely different default. This should now be kept at the
configuration file only.
For now this class implements support for installing from the configured
yum repos only. If the use case of installing from local RPM packages
arises, we'll implement that.
'''
def set_install_params(self, test, params):
super(YumInstaller, self).set_install_params(test, params)
os_dep.command("rpm")
os_dep.command("yum")
if self.install_debug_info:
os_dep.command("debuginfo-install")
self.yum_pkgs = eval(params.get("%s_pkgs" % self.param_key_prefix,
"[]"))
def get_version(self):
return " ".join(self.yum_pkgs)
def _install_phase_cleanup(self):
packages_to_remove = " ".join(self.yum_pkgs)
utils.system("yum remove -y %s" % packages_to_remove)
def _install_phase_install(self):
if self.yum_pkgs:
os.chdir(self.test_srcdir)
utils.system("yum --nogpgcheck -y install %s" %
" ".join(self.yum_pkgs))
if self.install_debug_info:
utils.system("debuginfo-install --enablerepo='*debuginfo' -y %s" %
" ".join(self.yum_pkgs))
class KojiInstaller(BaseInstaller):
'''
Handles virtualization software installation via koji/brew
It uses YUM to install and remove packages.
Change notice: this is not a subclass of YumInstaller anymore. The
parameters this class uses are different (koji_tag, koji_pgks) and
the install process runs YUM.
'''
def set_install_params(self, test, params):
super(KojiInstaller, self).set_install_params(test, params)
os_dep.command("rpm")
os_dep.command("yum")
self.tag = params.get("%s_tag" % self.param_key_prefix, None)
self.koji_cmd = params.get("%s_cmd" % self.param_key_prefix, None)
if self.tag is not None:
utils_koji.set_default_koji_tag(self.tag)
self.koji_pkgs = params.get("%s_pkgs" % self.param_key_prefix,
"").split()
self.koji_scratch_pkgs = params.get("%s_scratch_pkgs" %
self.param_key_prefix,
"").split()
self.koji_yumrepo_baseurl = params.get("%s_yumrepo_baseurl" %
self.param_key_prefix, None)
if self.install_debug_info:
self._expand_koji_pkgs_with_debuginfo()
def get_version(self):
return " ".join(self._get_rpm_file_names())
def _expand_koji_pkgs_with_debuginfo(self):
'''
Include debuginfo RPMs on koji_pkgs
@returns: None
'''
logging.debug("Koji package list to be updated with debuginfo pkgs")
koji_pkgs_with_debug = []
for pkg_text in self.koji_pkgs:
pkg = utils_koji.KojiPkgSpec(pkg_text)
debuginfo_pkg_name = '%s-debuginfo' % pkg.package
# if no subpackages are set, then all packages will be installed
# so there's no need to manually include debuginfo packages
if pkg.subpackages:
# make sure we do not include the debuginfo package if
# already specified in the list of subpackages
if not debuginfo_pkg_name in pkg.subpackages:
pkg.subpackages.append(debuginfo_pkg_name)
pkg_with_debug_text = pkg.to_text()
logging.debug("KojiPkgSpec with debuginfo package added: %s",
pkg_with_debug_text)
koji_pkgs_with_debug.append(pkg_with_debug_text)
# swap current koji_pkgs with on that includes debuginfo pkgs
self.koji_pkgs = koji_pkgs_with_debug
def _get_rpm_names(self):
all_rpm_names = []
koji_client = utils_koji.KojiClient(cmd=self.koji_cmd)
for pkg_text in self.koji_pkgs:
pkg = utils_koji.KojiPkgSpec(pkg_text)
rpm_names = koji_client.get_pkg_rpm_names(pkg)
all_rpm_names += rpm_names
for scratch_pkg_text in self.koji_scratch_pkgs:
pkg = utils_koji.KojiScratchPkgSpec(scratch_pkg_text)
rpm_urls = koji_client.get_scratch_pkg_urls(pkg)
file_names = map(os.path.basename, rpm_urls)
for f in file_names:
r = utils_koji.RPMFileNameInfo(f)
all_rpm_names.append(r.get_nvr_info()['name'])
return all_rpm_names
def _get_rpm_file_names(self):
all_rpm_file_names = []
koji_client = utils_koji.KojiClient(cmd=self.koji_cmd)
for pkg_text in self.koji_pkgs:
pkg = utils_koji.KojiPkgSpec(pkg_text)
rpm_file_names = koji_client.get_pkg_rpm_file_names(pkg)
all_rpm_file_names += rpm_file_names
for scratch_pkg_text in self.koji_scratch_pkgs:
pkg = utils_koji.KojiScratchPkgSpec(scratch_pkg_text)
rpm_urls = koji_client.get_scratch_pkg_urls(pkg)
file_names = map(os.path.basename, rpm_urls)
all_rpm_file_names += file_names
return all_rpm_file_names
def _install_phase_cleanup(self):
removable_packages = " ".join(self._get_rpm_names())
utils.system("yum -y remove %s" % removable_packages)
def _install_phase_download(self):
koji_client = utils_koji.KojiClient(cmd=self.koji_cmd)
for pkg_text in self.koji_pkgs:
pkg = utils_koji.KojiPkgSpec(pkg_text)
if pkg.is_valid():
koji_client.get_pkgs(pkg, dst_dir=self.test_srcdir)
else:
logging.error('Package specification (%s) is invalid: %s' %
(pkg, pkg.describe_invalid()))
for pkg_text in self.koji_scratch_pkgs:
pkg = utils_koji.KojiScratchPkgSpec(pkg_text)
koji_client.get_scratch_pkgs(pkg, dst_dir=self.test_srcdir)
def _install_phase_install(self):
if self.koji_yumrepo_baseurl is not None:
repo = yumrepo.YumRepo(self.param_key_prefix,
self.koji_yumrepo_baseurl)
logging.debug('Enabling YUM Repo "%s" at "%s"',
self.param_key_prefix, self.koji_yumrepo_baseurl)
repo.save()
os.chdir(self.test_srcdir)
rpm_file_names = " ".join(self._get_rpm_file_names())
utils.system("yum --nogpgcheck -y install %s" % rpm_file_names)
if self.koji_yumrepo_baseurl is not None:
logging.debug('Disabling YUM Repo "%s" at "%s"',
self.param_key_prefix, self.koji_yumrepo_baseurl)
repo.remove()
class BaseLocalSourceInstaller(BaseInstaller):
def set_install_params(self, test, params):
super(BaseLocalSourceInstaller, self).set_install_params(test, params)
self._set_install_prefix()
self._set_source_destination()
#
# There are really no choices for patch helpers
#
self.patch_helper = build_helper.PatchParamHelper(
self.params,
self.param_key_prefix,
self.source_destination)
#
# These helpers should be set by child classes
#
self.content_helper = None
self.build_helper = None
def _set_install_prefix(self):
'''
Prefix for installation of application built from source
When installing virtualization software from *source*, this is where
the resulting binaries will be installed. Usually this is the value
passed to the configure script, ie: ./configure --prefix=<value>
'''
prefix = os.path.join(self.test_builddir, 'install_root')
self.install_prefix = os.path.abspath(prefix)
def _set_source_destination(self):
'''
Sets the source code destination directory path
'''
self.source_destination = os.path.join(self.test_srcdir,
self.name)
def _set_build_helper(self):
'''
Sets the build helper, default is 'gnu_autotools'
'''
build_helper_name = self.params.get('%s_build_helper' %
self.param_key_prefix,
'gnu_autotools')
if build_helper_name == 'gnu_autotools':
self.build_helper = build_helper.GnuSourceBuildParamHelper(
self.params, self.param_key_prefix,
self.source_destination, self.install_prefix)
elif build_helper_name == 'linux_kernel':
self.build_helper = build_helper.LinuxKernelBuildHelper(
self.params, self.param_key_prefix,
self.source_destination)
def _install_phase_prepare(self):
if self.patch_helper is not None:
self.patch_helper.execute()
def _install_phase_download(self):
if self.content_helper is not None:
self.content_helper.execute()
def _install_phase_build(self):
if self.build_helper is not None:
if (isinstance(self.build_helper,
build_helper.GnuSourceBuildHelper) or
isinstance(self.build_helper,
build_helper.LinuxKernelBuildHelper)):
try:
self.build_helper.execute()
except build_helper.SourceBuildParallelFailed:
# Flag minor the failure
self.minor_failure = True
self.minor_failure_reason = "Failed to do parallel build"
except build_helper.SourceBuildFailed:
# Failed the current test
raise error.TestFail("Failed to build %s" % self.name)
else:
self.build_helper.execute()
def _install_phase_install(self):
if self.build_helper is not None:
self.build_helper.install()
class LocalSourceDirInstaller(BaseLocalSourceInstaller):
'''
Handles software installation by building/installing from a source dir
'''
def set_install_params(self, test, params):
super(LocalSourceDirInstaller, self).set_install_params(test, params)
self.content_helper = build_helper.LocalSourceDirParamHelper(
params,
self.name,
self.source_destination)
self._set_build_helper()
class LocalSourceTarInstaller(BaseLocalSourceInstaller):
'''
Handles software installation by building/installing from a tarball
'''
def set_install_params(self, test, params):
super(LocalSourceTarInstaller, self).set_install_params(test, params)
self.content_helper = build_helper.LocalTarParamHelper(
params,
self.name,
self.source_destination)
self._set_build_helper()
class RemoteSourceTarInstaller(BaseLocalSourceInstaller):
'''
Handles software installation by building/installing from a remote tarball
'''
def set_install_params(self, test, params):
super(RemoteSourceTarInstaller, self).set_install_params(test, params)
self.content_helper = build_helper.RemoteTarParamHelper(
params,
self.name,
self.source_destination)
self._set_build_helper()
class GitRepoInstaller(BaseLocalSourceInstaller):
def set_install_params(self, test, params):
super(GitRepoInstaller, self).set_install_params(test, params)
self.content_helper = build_helper.GitRepoParamHelper(
params,
self.name,
self.source_destination)
self._set_build_helper()
def get_version(self):
uri = self.content_helper.uri
branch = self.content_helper.branch
commit = self.content_helper.get_top_commit()
return "%s:%s:%s" % (uri, branch, commit)
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 virt installation fails
"""
def __init__(self, msg="Virtualization software install failed"):
self._msg = msg
def load_modules(self):
"""
Will refuse to load the kerkel modules as install failed
"""
raise VirtInstallFailed("Kernel modules not available. reason: %s" %
self._msg)