1 import logging
, time
, socket
, re
, os
, shutil
, tempfile
, glob
, ConfigParser
3 from autotest_lib
.client
.common_lib
import error
4 from autotest_lib
.client
.bin
import utils
5 from autotest_lib
.client
.virt
import virt_vm
, virt_utils
11 If dir is a mountpoint, do what is possible to unmount it. Afterwards,
14 @param dir: Directory to be cleaned up.
16 error
.context("cleaning up unattended install directory %s" % dir)
17 if os
.path
.ismount(dir):
18 utils
.run('fuser -k %s' % dir, ignore_status
=True)
19 utils
.run('umount %s' % dir)
20 if os
.path
.isdir(dir):
25 def clean_old_image(image
):
27 Clean a leftover image file from previous processes. If it contains a
28 mounted file system, do the proper cleanup procedures.
30 @param image: Path to image to be cleaned up.
32 error
.context("cleaning up old leftover image %s" % image
)
33 if os
.path
.exists(image
):
34 mtab
= open('/etc/mtab', 'r')
35 mtab_contents
= mtab
.read()
37 if image
in mtab_contents
:
38 utils
.run('fuser -k %s' % image
, ignore_status
=True)
39 utils
.run('umount %s' % image
)
45 Abstract class for Disk objects, with the common methods implemented.
51 def get_answer_file_path(self
, filename
):
52 return os
.path
.join(self
.mount
, filename
)
55 def copy_to(self
, src
):
56 logging
.debug("Copying %s to disk image mount", src
)
57 dst
= os
.path
.join(self
.mount
, os
.path
.basename(src
))
58 if os
.path
.isdir(src
):
59 shutil
.copytree(src
, dst
)
60 elif os
.path
.isfile(src
):
61 shutil
.copyfile(src
, dst
)
65 os
.chmod(self
.path
, 0755)
67 logging
.debug("Disk %s successfuly set", self
.path
)
70 class FloppyDisk(Disk
):
72 Represents a 1.44 MB floppy disk. We can copy files to it, and setup it in
76 def __init__(self
, path
, qemu_img_binary
, tmpdir
):
77 error
.context("Creating unattended install floppy image %s" % path
)
79 self
.mount
= tempfile
.mkdtemp(prefix
='floppy_', dir=self
.tmpdir
)
80 self
.virtio_mount
= None
83 if not os
.path
.isdir(os
.path
.dirname(path
)):
84 os
.makedirs(os
.path
.dirname(path
))
87 c_cmd
= '%s create -f raw %s 1440k' % (qemu_img_binary
, path
)
89 f_cmd
= 'mkfs.msdos -s 1 %s' % path
91 m_cmd
= 'mount -o loop,rw %s %s' % (path
, self
.mount
)
93 except error
.CmdError
, e
:
98 def _copy_virtio_drivers(self
, virtio_floppy
):
100 Copy the virtio drivers on the virtio floppy to the install floppy.
102 1) Mount the floppy containing the viostor drivers
103 2) Copy its contents to the root of the install floppy
105 virtio_mount
= tempfile
.mkdtemp(prefix
='virtio_floppy_',
110 m_cmd
= 'mount -o loop,ro %s %s' % (virtio_floppy
, virtio_mount
)
112 os
.chdir(virtio_mount
)
113 path_list
= glob
.glob('*')
114 for path
in path_list
:
118 cleanup(virtio_mount
)
121 def setup_virtio_win2003(self
, virtio_floppy
, virtio_oemsetup_id
):
123 Setup the install floppy with the virtio storage drivers, win2003 style.
125 Win2003 and WinXP depend on the file txtsetup.oem file to install
126 the virtio drivers from the floppy, which is a .ini file.
129 1) Copy the virtio drivers on the virtio floppy to the install floppy
130 2) Parse the ini file with config parser
131 3) Modify the identifier of the default session that is going to be
132 executed on the config parser object
133 4) Re-write the config file to the disk
135 self
._copy
_virtio
_drivers
(virtio_floppy
)
136 txtsetup_oem
= os
.path
.join(self
.mount
, 'txtsetup.oem')
137 if not os
.path
.isfile(txtsetup_oem
):
138 raise IOError('File txtsetup.oem not found on the install '
139 'floppy. Please verify if your floppy virtio '
140 'driver image has this file')
141 parser
= ConfigParser
.ConfigParser()
142 parser
.read(txtsetup_oem
)
143 if not parser
.has_section('Defaults'):
144 raise ValueError('File txtsetup.oem does not have the session '
145 '"Defaults". Please check txtsetup.oem')
146 default_driver
= parser
.get('Defaults', 'SCSI')
147 if default_driver
!= virtio_oemsetup_id
:
148 parser
.set('Defaults', 'SCSI', virtio_oemsetup_id
)
149 fp
= open(txtsetup_oem
, 'w')
154 def setup_virtio_win2008(self
, virtio_floppy
):
156 Setup the install floppy with the virtio storage drivers, win2008 style.
158 Win2008, Vista and 7 require people to point out the path to the drivers
159 on the unattended file, so we just need to copy the drivers to the
160 driver floppy disk. Important to note that it's possible to specify
161 drivers from a CDROM, so the floppy driver copy is optional.
164 1) Copy the virtio drivers on the virtio floppy to the install floppy,
165 if there is one available
167 if os
.path
.isfile(virtio_floppy
):
168 self
._copy
_virtio
_drivers
(virtio_floppy
)
170 logging
.debug("No virtio floppy present, not needed for this OS anyway")
173 class CdromDisk(Disk
):
175 Represents a CDROM disk that we can master according to our needs.
177 def __init__(self
, path
, tmpdir
):
178 self
.mount
= tempfile
.mkdtemp(prefix
='cdrom_unattended_', dir=tmpdir
)
180 clean_old_image(path
)
181 if not os
.path
.isdir(os
.path
.dirname(path
)):
182 os
.makedirs(os
.path
.dirname(path
))
187 error
.context("Creating unattended install CD image %s" % self
.path
)
188 g_cmd
= ('mkisofs -o %s -max-iso9660-filenames '
189 '-relaxed-filenames -D --input-charset iso8859-1 '
190 '%s' % (self
.path
, self
.mount
))
193 os
.chmod(self
.path
, 0755)
195 logging
.debug("unattended install CD image %s successfuly created",
199 class UnattendedInstallConfig(object):
201 Creates a floppy disk image that will contain a config file for unattended
202 OS install. The parameters to the script are retrieved from environment
205 def __init__(self
, test
, params
):
207 Sets class atributes from test parameters.
209 @param test: KVM test object.
210 @param params: Dictionary with test parameters.
212 root_dir
= test
.bindir
213 images_dir
= os
.path
.join(root_dir
, 'images')
214 self
.deps_dir
= os
.path
.join(root_dir
, 'deps')
215 self
.unattended_dir
= os
.path
.join(root_dir
, 'unattended')
217 attributes
= ['kernel_args', 'finish_program', 'cdrom_cd1',
218 'unattended_file', 'medium', 'url', 'kernel', 'initrd',
219 'nfs_server', 'nfs_dir', 'install_virtio', 'floppy',
220 'cdrom_unattended', 'boot_path', 'extra_params',
221 'qemu_img_binary', 'cdkey', 'finish_program']
224 setattr(self
, a
, params
.get(a
, ''))
226 if self
.install_virtio
== 'yes':
227 v_attributes
= ['virtio_floppy', 'virtio_storage_path',
228 'virtio_network_path', 'virtio_oemsetup_id',
229 'virtio_network_installer']
230 for va
in v_attributes
:
231 setattr(self
, va
, params
.get(va
, ''))
233 self
.tmpdir
= test
.tmpdir
235 if getattr(self
, 'unattended_file'):
236 self
.unattended_file
= os
.path
.join(root_dir
, self
.unattended_file
)
238 if getattr(self
, 'finish_program'):
239 self
.finish_program
= os
.path
.join(root_dir
, self
.finish_program
)
241 if getattr(self
, 'qemu_img_binary'):
242 if not os
.path
.isfile(getattr(self
, 'qemu_img_binary')):
243 self
.qemu_img_binary
= os
.path
.join(root_dir
,
244 self
.qemu_img_binary
)
246 if getattr(self
, 'cdrom_cd1'):
247 self
.cdrom_cd1
= os
.path
.join(root_dir
, self
.cdrom_cd1
)
248 self
.cdrom_cd1_mount
= tempfile
.mkdtemp(prefix
='cdrom_cd1_',
250 if self
.medium
== 'nfs':
251 self
.nfs_mount
= tempfile
.mkdtemp(prefix
='nfs_',
254 if getattr(self
, 'floppy'):
255 self
.floppy
= os
.path
.join(root_dir
, self
.floppy
)
256 if not os
.path
.isdir(os
.path
.dirname(self
.floppy
)):
257 os
.makedirs(os
.path
.dirname(self
.floppy
))
259 self
.image_path
= os
.path
.dirname(self
.kernel
)
262 def answer_kickstart(self
, answer_path
):
264 Replace KVM_TEST_CDKEY (in the unattended file) with the cdkey
265 provided for this test and replace the KVM_TEST_MEDIUM with
266 the tree url or nfs address provided for this test.
268 @return: Answer file contents
270 contents
= open(self
.unattended_file
).read()
272 dummy_cdkey_re
= r
'\bKVM_TEST_CDKEY\b'
273 if re
.search(dummy_cdkey_re
, contents
):
275 contents
= re
.sub(dummy_cdkey_re
, self
.cdkey
, contents
)
277 dummy_medium_re
= r
'\bKVM_TEST_MEDIUM\b'
278 if self
.medium
== "cdrom":
280 elif self
.medium
== "url":
281 content
= "url --url %s" % self
.url
282 elif self
.medium
== "nfs":
283 content
= "nfs --server=%s --dir=%s" % (self
.nfs_server
,
286 raise ValueError("Unexpected installation medium %s" % self
.url
)
288 contents
= re
.sub(dummy_medium_re
, content
, contents
)
290 logging
.debug("Unattended install contents:")
291 for line
in contents
.splitlines():
294 utils
.open_write_close(answer_path
, contents
)
297 def answer_windows_ini(self
, answer_path
):
298 parser
= ConfigParser
.ConfigParser()
299 parser
.read(self
.unattended_file
)
300 # First, replacing the CDKEY
302 parser
.set('UserData', 'ProductKey', self
.cdkey
)
304 logging
.error("Param 'cdkey' required but not specified for "
305 "this unattended installation")
307 # Now, replacing the virtio network driver path, under double quotes
308 if self
.install_virtio
== 'yes':
309 parser
.set('Unattended', 'OemPnPDriversPath',
310 '"%s"' % self
.virtio_nework_path
)
312 parser
.remove_option('Unattended', 'OemPnPDriversPath')
314 # Last, replace the virtio installer command
315 if self
.install_virtio
== 'yes':
316 driver
= self
.virtio_network_installer_path
320 dummy_re
= 'KVM_TEST_VIRTIO_NETWORK_INSTALLER'
321 installer
= parser
.get('GuiRunOnce', 'Command0')
322 if dummy_re
in installer
:
323 installer
= re
.sub(dummy_re
, driver
, installer
)
324 parser
.set('GuiRunOnce', 'Command0', installer
)
326 # Now, writing the in memory config state to the unattended file
327 fp
= open(answer_path
, 'w')
330 # Let's read it so we can debug print the contents
331 fp
= open(answer_path
, 'r')
333 logging
.debug("Unattended install contents:")
334 for line
in contents
.splitlines():
339 def answer_windows_xml(self
, answer_path
):
340 doc
= xml
.dom
.minidom
.parse(self
.unattended_file
)
343 # First, replacing the CDKEY
344 product_key
= doc
.getElementsByTagName('ProductKey')[0]
345 key
= product_key
.getElementsByTagName('Key')[0]
346 key_text
= key
.childNodes
[0]
347 assert key_text
.nodeType
== doc
.TEXT_NODE
348 key_text
.data
= self
.cdkey
350 logging
.error("Param 'cdkey' required but not specified for "
351 "this unattended installation")
353 # Now, replacing the virtio driver paths or removing the entire
354 # component PnpCustomizationsWinPE Element Node
355 if self
.install_virtio
== 'yes':
356 paths
= doc
.getElementsByTagName("Path")
357 values
= [self
.virtio_storage_path
, self
.virtio_network_path
]
358 for path
, value
in zip(paths
, values
):
359 path_text
= path
.childNodes
[0]
360 assert key_text
.nodeType
== doc
.TEXT_NODE
361 path_text
.data
= value
363 settings
= doc
.getElementsByTagName("settings")
365 for c
in s
.getElementsByTagName("component"):
366 if (c
.getAttribute('name') ==
367 "Microsoft-Windows-PnpCustomizationsWinPE"):
370 # Last but not least important, replacing the virtio installer command
371 command_lines
= doc
.getElementsByTagName("CommandLine")
372 for command_line
in command_lines
:
373 command_line_text
= command_line
.childNodes
[0]
374 assert command_line_text
.nodeType
== doc
.TEXT_NODE
375 dummy_re
= 'KVM_TEST_VIRTIO_NETWORK_INSTALLER'
376 if (self
.install_virtio
== 'yes' and
377 hasattr(self
, 'virtio_network_installer_path')):
378 driver
= self
.virtio_network_installer_path
381 if driver
.endswith("msi"):
382 driver
= 'msiexec /passive /package ' + driver
383 if dummy_re
in command_line_text
.data
:
384 t
= command_line_text
.data
385 t
= re
.sub(dummy_re
, driver
, t
)
386 command_line_text
.data
= t
388 contents
= doc
.toxml()
389 logging
.debug("Unattended install contents:")
390 for line
in contents
.splitlines():
393 fp
= open(answer_path
, 'w')
397 def answer_suse_xml(self
, answer_path
):
398 # There's nothing to replace on SUSE files to date. Yay!
399 doc
= xml
.dom
.minidom
.parse(self
.unattended_file
)
401 contents
= doc
.toxml()
402 logging
.debug("Unattended install contents:")
403 for line
in contents
.splitlines():
406 fp
= open(answer_path
, 'w')
410 def setup_boot_disk(self
):
411 if self
.unattended_file
.endswith('.sif'):
412 dest_fname
= 'winnt.sif'
413 setup_file
= 'winnt.bat'
414 boot_disk
= FloppyDisk(self
.floppy
, self
.qemu_img_binary
,
416 answer_path
= boot_disk
.get_answer_file_path(dest_fname
)
417 self
.answer_windows_ini(answer_path
)
418 setup_file_path
= os
.path
.join(self
.unattended_dir
, setup_file
)
419 boot_disk
.copy_to(setup_file_path
)
420 if self
.install_virtio
== "yes":
421 boot_disk
.setup_virtio_win2003(self
.virtio_floppy
,
422 self
.virtio_oemsetup_id
)
423 boot_disk
.copy_to(self
.finish_program
)
425 elif self
.unattended_file
.endswith('.ks'):
426 # Red Hat kickstart install
427 dest_fname
= 'ks.cfg'
428 if self
.cdrom_unattended
:
429 boot_disk
= CdromDisk(self
.cdrom_unattended
, self
.tmpdir
)
431 boot_disk
= FloppyDisk(self
.floppy
, self
.qemu_img_binary
,
434 raise ValueError("Neither cdrom_unattended nor floppy set "
435 "on the config file, please verify")
436 answer_path
= boot_disk
.get_answer_file_path(dest_fname
)
437 self
.answer_kickstart(answer_path
)
439 elif self
.unattended_file
.endswith('.xml'):
440 if "autoyast" in self
.extra_params
:
441 # SUSE autoyast install
442 dest_fname
= "autoinst.xml"
443 if self
.cdrom_unattended
:
444 boot_disk
= CdromDisk(self
.cdrom_unattended
, self
.tmpdir
)
446 boot_disk
= FloppyDisk(self
.floppy
, self
.qemu_img_binary
,
449 raise ValueError("Neither cdrom_unattended nor floppy set "
450 "on the config file, please verify")
451 answer_path
= boot_disk
.get_answer_file_path(dest_fname
)
452 self
.answer_suse_xml(answer_path
)
455 # Windows unattended install
456 dest_fname
= "autounattend.xml"
457 boot_disk
= FloppyDisk(self
.floppy
, self
.qemu_img_binary
,
459 answer_path
= boot_disk
.get_answer_file_path(dest_fname
)
460 self
.answer_windows_xml(answer_path
)
462 if self
.install_virtio
== "yes":
463 boot_disk
.setup_virtio_win2008(self
.virtio_floppy
)
464 boot_disk
.copy_to(self
.finish_program
)
467 raise ValueError('Unknown answer file type: %s' %
468 self
.unattended_file
)
474 def setup_cdrom(self
):
476 Mount cdrom and copy vmlinuz and initrd.img.
478 error
.context("Copying vmlinuz and initrd.img from install cdrom %s" %
480 m_cmd
= ('mount -t iso9660 -v -o loop,ro %s %s' %
481 (self
.cdrom_cd1
, self
.cdrom_cd1_mount
))
485 if not os
.path
.isdir(self
.image_path
):
486 os
.makedirs(self
.image_path
)
487 kernel_fetch_cmd
= ("cp %s/%s/%s %s" %
488 (self
.cdrom_cd1_mount
, self
.boot_path
,
489 os
.path
.basename(self
.kernel
), self
.kernel
))
490 utils
.run(kernel_fetch_cmd
)
491 initrd_fetch_cmd
= ("cp %s/%s/%s %s" %
492 (self
.cdrom_cd1_mount
, self
.boot_path
,
493 os
.path
.basename(self
.initrd
), self
.initrd
))
494 utils
.run(initrd_fetch_cmd
)
496 cleanup(self
.cdrom_cd1_mount
)
502 Download the vmlinuz and initrd.img from URL.
504 error
.context("downloading vmlinuz and initrd.img from %s" % self
.url
)
505 os
.chdir(self
.image_path
)
506 kernel_fetch_cmd
= "wget -q %s/%s/%s" % (self
.url
, self
.boot_path
,
507 os
.path
.basename(self
.kernel
))
508 initrd_fetch_cmd
= "wget -q %s/%s/%s" % (self
.url
, self
.boot_path
,
509 os
.path
.basename(self
.initrd
))
511 if os
.path
.exists(self
.kernel
):
512 os
.remove(self
.kernel
)
513 if os
.path
.exists(self
.initrd
):
514 os
.remove(self
.initrd
)
516 utils
.run(kernel_fetch_cmd
)
517 utils
.run(initrd_fetch_cmd
)
522 Copy the vmlinuz and initrd.img from nfs.
524 error
.context("copying the vmlinuz and initrd.img from NFS share")
526 m_cmd
= ("mount %s:%s %s -o ro" %
527 (self
.nfs_server
, self
.nfs_dir
, self
.nfs_mount
))
531 kernel_fetch_cmd
= ("cp %s/%s/%s %s" %
532 (self
.nfs_mount
, self
.boot_path
,
533 os
.path
.basename(self
.kernel
), self
.image_path
))
534 utils
.run(kernel_fetch_cmd
)
535 initrd_fetch_cmd
= ("cp %s/%s/%s %s" %
536 (self
.nfs_mount
, self
.boot_path
,
537 os
.path
.basename(self
.initrd
), self
.image_path
))
538 utils
.run(initrd_fetch_cmd
)
540 cleanup(self
.nfs_mount
)
545 Configure the environment for unattended install.
547 Uses an appropriate strategy according to each install model.
549 logging
.info("Starting unattended install setup")
550 virt_utils
.display_attributes(self
)
552 if self
.unattended_file
and (self
.floppy
or self
.cdrom_unattended
):
553 self
.setup_boot_disk()
554 if self
.medium
== "cdrom":
555 if self
.kernel
and self
.initrd
:
557 elif self
.medium
== "url":
559 elif self
.medium
== "nfs":
562 raise ValueError("Unexpected installation method %s" %
567 def run_unattended_install(test
, params
, env
):
569 Unattended install test:
570 1) Starts a VM with an appropriated setup to start an unattended OS install.
571 2) Wait until the install reports to the install watcher its end.
573 @param test: KVM test object.
574 @param params: Dictionary with the test parameters.
575 @param env: Dictionary with test environment.
577 unattended_install_config
= UnattendedInstallConfig(test
, params
)
578 unattended_install_config
.setup()
579 vm
= env
.get_vm(params
["main_vm"])
582 install_timeout
= int(params
.get("timeout", 3000))
583 post_install_delay
= int(params
.get("post_install_delay", 0))
584 port
= vm
.get_port(int(params
.get("guest_port_unattended_install")))
586 migrate_background
= params
.get("migrate_background") == "yes"
587 if migrate_background
:
588 mig_timeout
= float(params
.get("mig_timeout", "3600"))
589 mig_protocol
= params
.get("migration_protocol", "tcp")
591 logging
.info("Waiting for installation to finish. Timeout set to %d s "
592 "(%d min)", install_timeout
, install_timeout
/60)
593 error
.context("waiting for installation to finish")
595 start_time
= time
.time()
596 while (time
.time() - start_time
) < install_timeout
:
599 except virt_vm
.VMDeadError
, e
:
600 if params
.get("wait_no_ack", "no") == "yes":
604 vm
.verify_kernel_crash()
605 if params
.get("wait_no_ack", "no") == "no":
606 client
= socket
.socket(socket
.AF_INET
, socket
.SOCK_STREAM
)
608 client
.connect((vm
.get_address(), port
))
609 if client
.recv(1024) == "done":
611 except (socket
.error
, virt_vm
.VMAddressError
):
614 if migrate_background
:
615 # Drop the params which may break the migration
616 # Better method is to use dnsmasq to do the
617 # unattended installation
618 if vm
.params
.get("initrd"):
619 vm
.params
["initrd"] = None
620 if vm
.params
.get("kernel"):
621 vm
.params
["kernel"] = None
622 if vm
.params
.get("extra_params"):
623 vm
.params
["extra_params"] = re
.sub("--append '.*'", "",
624 vm
.params
["extra_params"])
625 vm
.migrate(timeout
=mig_timeout
, protocol
=mig_protocol
)
628 if params
.get("wait_no_ack", "no") == "no":
631 raise error
.TestFail("Timeout elapsed while waiting for install to "
634 time_elapsed
= time
.time() - start_time
635 logging
.info("Guest reported successful installation after %d s (%d min)",
636 time_elapsed
, time_elapsed
/60)
638 if params
.get("shutdown_cleanly", "yes") == "yes":
639 shutdown_cleanly_timeout
= int(params
.get("shutdown_cleanly_timeout",
641 logging
.info("Wait for guest to shutdown cleanly")
642 if virt_utils
.wait_for(vm
.is_dead
, shutdown_cleanly_timeout
, 1, 1):
643 logging
.info("Guest managed to shutdown cleanly")