3 Utility classes and functions to handle Virtual Machine creation using qemu.
5 @copyright: 2008-2009 Red Hat Inc.
8 import time
, os
, logging
, fcntl
, re
, commands
, glob
9 import kvm_utils
, kvm_subprocess
, kvm_monitor
10 from autotest_lib
.client
.common_lib
import error
11 from autotest_lib
.client
.bin
import utils
14 class VMError(Exception):
18 class VMCreateError(VMError
):
19 def __init__(self
, cmd
, status
, output
):
20 VMError
.__init
__(self
, cmd
, status
, output
)
26 return ("VM creation command failed: %r (status: %s, "
27 "output: %r)" % (self
.cmd
, self
.status
, self
.output
))
30 class VMHashMismatchError(VMError
):
31 def __init__(self
, actual
, expected
):
32 VMError
.__init
__(self
, actual
, expected
)
33 self
.actual_hash
= actual
34 self
.expected_hash
= expected
37 return ("CD image hash (%s) differs from expected one (%s)" %
38 (self
.actual_hash
, self
.expected_hash
))
41 class VMImageMissingError(VMError
):
42 def __init__(self
, filename
):
43 VMError
.__init
__(self
, filename
)
44 self
.filename
= filename
47 return "CD image file not found: %r" % self
.filename
50 class VMImageCheckError(VMError
):
51 def __init__(self
, filename
):
52 VMError
.__init
__(self
, filename
)
53 self
.filename
= filename
56 return "Errors found on image: %r" % self
.filename
59 class VMBadPATypeError(VMError
):
60 def __init__(self
, pa_type
):
61 VMError
.__init
__(self
, pa_type
)
62 self
.pa_type
= pa_type
65 return "Unsupported PCI assignable type: %r" % self
.pa_type
68 class VMPAError(VMError
):
69 def __init__(self
, pa_type
):
70 VMError
.__init
__(self
, pa_type
)
71 self
.pa_type
= pa_type
74 return ("No PCI assignable devices could be assigned "
75 "(pci_assignable=%r)" % self
.pa_type
)
78 class VMPostCreateError(VMError
):
79 def __init__(self
, cmd
, output
):
80 VMError
.__init
__(self
, cmd
, output
)
85 class VMHugePageError(VMPostCreateError
):
87 return ("Cannot allocate hugepage memory (command: %r, "
88 "output: %r)" % (self
.cmd
, self
.output
))
91 class VMKVMInitError(VMPostCreateError
):
93 return ("Cannot initialize KVM (command: %r, output: %r)" %
94 (self
.cmd
, self
.output
))
97 class VMDeadError(VMError
):
98 def __init__(self
, status
, output
):
99 VMError
.__init
__(self
, status
, output
)
104 return ("VM process is dead (status: %s, output: %r)" %
105 (self
.status
, self
.output
))
108 class VMDeadKernelCrashError(VMError
):
109 def __init__(self
, kernel_crash
):
110 VMError
.__init
__(self
, kernel_crash
)
111 self
.kernel_crash
= kernel_crash
114 return ("VM is dead due to a kernel crash:\n%s" % self
.kernel_crash
)
117 class VMAddressError(VMError
):
121 class VMPortNotRedirectedError(VMAddressError
):
122 def __init__(self
, port
):
123 VMAddressError
.__init
__(self
, port
)
127 return "Port not redirected: %s" % self
.port
130 class VMAddressVerificationError(VMAddressError
):
131 def __init__(self
, mac
, ip
):
132 VMAddressError
.__init
__(self
, mac
, ip
)
137 return ("Cannot verify MAC-IP address mapping using arping: "
138 "%s ---> %s" % (self
.mac
, self
.ip
))
141 class VMMACAddressMissingError(VMAddressError
):
142 def __init__(self
, nic_index
):
143 VMAddressError
.__init
__(self
, nic_index
)
144 self
.nic_index
= nic_index
147 return "No MAC address defined for NIC #%s" % self
.nic_index
150 class VMIPAddressMissingError(VMAddressError
):
151 def __init__(self
, mac
):
152 VMAddressError
.__init
__(self
, mac
)
156 return "Cannot find IP address for MAC address %s" % self
.mac
159 class VMMigrateError(VMError
):
163 class VMMigrateTimeoutError(VMMigrateError
):
167 class VMMigrateCancelError(VMMigrateError
):
171 class VMMigrateFailedError(VMMigrateError
):
175 class VMMigrateStateMismatchError(VMMigrateError
):
176 def __init__(self
, src_hash
, dst_hash
):
177 VMMigrateError
.__init
__(self
, src_hash
, dst_hash
)
178 self
.src_hash
= src_hash
179 self
.dst_hash
= dst_hash
182 return ("Mismatch of VM state before and after migration (%s != %s)" %
183 (self
.src_hash
, self
.dst_hash
))
186 class VMRebootError(VMError
):
190 def get_image_filename(params
, root_dir
):
192 Generate an image path from params and root_dir.
194 @param params: Dictionary containing the test parameters.
195 @param root_dir: Base directory for relative filenames.
197 @note: params should contain:
198 image_name -- the name of the image file, without extension
199 image_format -- the format of the image (qcow2, raw etc)
201 image_name
= params
.get("image_name", "image")
202 image_format
= params
.get("image_format", "qcow2")
203 if params
.get("image_raw_device") == "yes":
205 image_filename
= "%s.%s" % (image_name
, image_format
)
206 image_filename
= kvm_utils
.get_path(root_dir
, image_filename
)
207 return image_filename
210 def create_image(params
, root_dir
):
212 Create an image using qemu_image.
214 @param params: Dictionary containing the test parameters.
215 @param root_dir: Base directory for relative filenames.
217 @note: params should contain:
218 image_name -- the name of the image file, without extension
219 image_format -- the format of the image (qcow2, raw etc)
220 image_size -- the requested size of the image (a string
221 qemu-img can understand, such as '10G')
223 qemu_img_cmd
= kvm_utils
.get_path(root_dir
, params
.get("qemu_img_binary",
225 qemu_img_cmd
+= " create"
227 format
= params
.get("image_format", "qcow2")
228 qemu_img_cmd
+= " -f %s" % format
230 image_filename
= get_image_filename(params
, root_dir
)
231 qemu_img_cmd
+= " %s" % image_filename
233 size
= params
.get("image_size", "10G")
234 qemu_img_cmd
+= " %s" % size
236 utils
.system(qemu_img_cmd
)
237 logging
.info("Image created in %r", image_filename
)
238 return image_filename
241 def remove_image(params
, root_dir
):
243 Remove an image file.
245 @param params: A dict
246 @param root_dir: Base directory for relative filenames.
248 @note: params should contain:
249 image_name -- the name of the image file, without extension
250 image_format -- the format of the image (qcow2, raw etc)
252 image_filename
= get_image_filename(params
, root_dir
)
253 logging
.debug("Removing image file %s...", image_filename
)
254 if os
.path
.exists(image_filename
):
255 os
.unlink(image_filename
)
257 logging
.debug("Image file %s not found")
260 def check_image(params
, root_dir
):
262 Check an image using qemu-img.
264 @param params: Dictionary containing the test parameters.
265 @param root_dir: Base directory for relative filenames.
267 @note: params should contain:
268 image_name -- the name of the image file, without extension
269 image_format -- the format of the image (qcow2, raw etc)
271 @raise VMImageCheckError: In case qemu-img check fails on the image.
273 image_filename
= get_image_filename(params
, root_dir
)
274 logging
.debug("Checking image file %s...", image_filename
)
275 qemu_img_cmd
= kvm_utils
.get_path(root_dir
,
276 params
.get("qemu_img_binary", "qemu-img"))
277 image_is_qcow2
= params
.get("image_format") == 'qcow2'
278 if os
.path
.exists(image_filename
) and image_is_qcow2
:
279 # Verifying if qemu-img supports 'check'
280 q_result
= utils
.run(qemu_img_cmd
, ignore_status
=True)
281 q_output
= q_result
.stdout
283 if not "check" in q_output
:
284 logging
.error("qemu-img does not support 'check', "
287 if not "info" in q_output
:
288 logging
.error("qemu-img does not support 'info', "
293 utils
.system("%s info %s" % (qemu_img_cmd
, image_filename
))
294 except error
.CmdError
:
295 logging
.error("Error getting info from image %s",
298 utils
.system("%s check %s" % (qemu_img_cmd
, image_filename
))
299 except error
.CmdError
:
300 raise VMImageCheckError(image_filename
)
303 if not os
.path
.exists(image_filename
):
304 logging
.debug("Image file %s not found, skipping check...",
306 elif not image_is_qcow2
:
307 logging
.debug("Image file %s not qcow2, skipping check...",
313 This class handles all basic VM operations.
316 def __init__(self
, name
, params
, root_dir
, address_cache
, state
=None):
318 Initialize the object and set a few attributes.
320 @param name: The name of the object
321 @param params: A dict containing VM params
322 (see method make_qemu_command for a full description)
323 @param root_dir: Base directory for relative filenames
324 @param address_cache: A dict that maps MAC addresses to IP addresses
325 @param state: If provided, use this as self.__dict__
328 self
.__dict
__ = state
331 self
.serial_console
= None
335 self
.pci_assignable
= None
340 # Find a unique identifier for this VM
342 self
.instance
= (time
.strftime("%Y%m%d-%H%M%S-") +
343 kvm_utils
.generate_random_string(4))
344 if not glob
.glob("/tmp/*%s" % self
.instance
):
347 self
.spice_port
= 8000
350 self
.root_dir
= root_dir
351 self
.address_cache
= address_cache
354 def clone(self
, name
=None, params
=None, root_dir
=None, address_cache
=None,
357 Return a clone of the VM object with optionally modified parameters.
358 The clone is initially not alive and needs to be started using create().
359 Any parameters not passed to this function are copied from the source
362 @param name: Optional new VM name
363 @param params: Optional new VM creation parameters
364 @param root_dir: Optional new base directory for relative filenames
365 @param address_cache: A dict that maps MAC addresses to IP addresses
366 @param copy_state: If True, copy the original VM's state to the clone.
367 Mainly useful for make_qemu_command().
372 params
= self
.params
.copy()
374 root_dir
= self
.root_dir
375 if address_cache
is None:
376 address_cache
= self
.address_cache
378 state
= self
.__dict
__.copy()
381 return VM(name
, params
, root_dir
, address_cache
, state
)
384 def make_qemu_command(self
, name
=None, params
=None, root_dir
=None):
386 Generate a qemu command line. All parameters are optional. If a
387 parameter is not supplied, the corresponding value stored in the
388 class attributes is used.
390 @param name: The name of the object
391 @param params: A dict containing VM params
392 @param root_dir: Base directory for relative filenames
394 @note: The params dict should contain:
395 mem -- memory size in MBs
396 cdrom -- ISO filename to use with the qemu -cdrom parameter
397 extra_params -- a string to append to the qemu command
398 shell_port -- port of the remote shell daemon on the guest
399 (SSH, Telnet or the home-made Remote Shell Server)
400 shell_client -- client program to use for connecting to the
401 remote shell daemon on the guest (ssh, telnet or nc)
402 x11_display -- if specified, the DISPLAY environment variable
403 will be be set to this value for the qemu process (useful for
405 images -- a list of image object names, separated by spaces
406 nics -- a list of NIC object names, separated by spaces
408 For each image in images:
409 drive_format -- string to pass as 'if' parameter for this
410 image (e.g. ide, scsi)
411 image_snapshot -- if yes, pass 'snapshot=on' to qemu for
413 image_boot -- if yes, pass 'boot=on' to qemu for this image
414 In addition, all parameters required by get_image_filename.
416 For each NIC in nics:
417 nic_model -- string to pass as 'model' parameter for this
420 # Helper function for command line option wrappers
421 def has_option(help, option
):
422 return bool(re
.search(r
"^-%s(\s|$)" % option
, help, re
.MULTILINE
))
424 # Wrappers for all supported qemu command line parameters.
425 # This is meant to allow support for multiple qemu versions.
426 # Each of these functions receives the output of 'qemu -help' as a
427 # parameter, and should add the requested command line option
430 def add_name(help, name
):
431 return " -name '%s'" % name
433 def add_human_monitor(help, filename
):
434 return " -monitor unix:'%s',server,nowait" % filename
436 def add_qmp_monitor(help, filename
):
437 return " -qmp unix:'%s',server,nowait" % filename
439 def add_serial(help, filename
):
440 return " -serial unix:'%s',server,nowait" % filename
442 def add_mem(help, mem
):
443 return " -m %s" % mem
445 def add_smp(help, smp
):
446 return " -smp %s" % smp
448 def add_cdrom(help, filename
, index
=None):
449 if has_option(help, "drive"):
450 cmd
= " -drive file='%s',media=cdrom" % filename
451 if index
is not None: cmd
+= ",index=%s" % index
454 return " -cdrom '%s'" % filename
456 def add_drive(help, filename
, index
=None, format
=None, cache
=None,
457 werror
=None, serial
=None, snapshot
=False, boot
=False):
458 cmd
= " -drive file='%s'" % filename
459 if index
is not None:
460 cmd
+= ",index=%s" % index
462 cmd
+= ",if=%s" % format
464 cmd
+= ",cache=%s" % cache
466 cmd
+= ",werror=%s" % werror
468 cmd
+= ",serial='%s'" % serial
470 cmd
+= ",snapshot=on"
475 def add_nic(help, vlan
, model
=None, mac
=None, device_id
=None, netdev_id
=None,
476 nic_extra_params
=None):
477 if has_option(help, "netdev"):
478 netdev_vlan_str
= ",netdev=%s" % netdev_id
480 netdev_vlan_str
= ",vlan=%d" % vlan
481 if has_option(help, "device"):
484 elif model
== "virtio":
485 model
= "virtio-net-pci"
486 cmd
= " -device %s" % model
+ netdev_vlan_str
488 cmd
+= ",mac='%s'" % mac
490 cmd
+= ",%s" % nic_extra_params
492 cmd
= " -net nic" + netdev_vlan_str
494 cmd
+= ",model=%s" % model
496 cmd
+= ",macaddr='%s'" % mac
498 cmd
+= ",id='%s'" % device_id
501 def add_net(help, vlan
, mode
, ifname
=None, script
=None,
502 downscript
=None, tftp
=None, bootfile
=None, hostfwd
=[],
503 netdev_id
=None, netdev_extra_params
=None):
504 if has_option(help, "netdev"):
505 cmd
= " -netdev %s,id=%s" % (mode
, netdev_id
)
506 if netdev_extra_params
:
507 cmd
+= ",%s" % netdev_extra_params
509 cmd
= " -net %s,vlan=%d" % (mode
, vlan
)
511 if ifname
: cmd
+= ",ifname='%s'" % ifname
512 if script
: cmd
+= ",script='%s'" % script
513 cmd
+= ",downscript='%s'" % (downscript
or "no")
515 if tftp
and "[,tftp=" in help:
516 cmd
+= ",tftp='%s'" % tftp
517 if bootfile
and "[,bootfile=" in help:
518 cmd
+= ",bootfile='%s'" % bootfile
519 if "[,hostfwd=" in help:
520 for host_port
, guest_port
in hostfwd
:
521 cmd
+= ",hostfwd=tcp::%s-:%s" % (host_port
, guest_port
)
524 def add_floppy(help, filename
):
525 return " -fda '%s'" % filename
527 def add_tftp(help, filename
):
528 # If the new syntax is supported, don't add -tftp
529 if "[,tftp=" in help:
532 return " -tftp '%s'" % filename
534 def add_bootp(help, filename
):
535 # If the new syntax is supported, don't add -bootp
536 if "[,bootfile=" in help:
539 return " -bootp '%s'" % filename
541 def add_tcp_redir(help, host_port
, guest_port
):
542 # If the new syntax is supported, don't add -redir
543 if "[,hostfwd=" in help:
546 return " -redir tcp:%s::%s" % (host_port
, guest_port
)
548 def add_vnc(help, vnc_port
):
549 return " -vnc :%d" % (vnc_port
- 5900)
552 if has_option(help, "sdl"):
557 def add_nographic(help):
560 def add_uuid(help, uuid
):
561 return " -uuid '%s'" % uuid
563 def add_pcidevice(help, host
):
564 return " -pcidevice host='%s'" % host
566 def add_spice(help, port
, param
):
567 if has_option(help,"spice"):
568 return " -spice port=%s,%s" % (port
, param
)
572 def add_qxl_vga(help, qxl
, vga
, qxl_dev_nr
=None):
574 if has_option(help, "qxl"):
575 if qxl
and qxl_dev_nr
is not None:
576 str += " -qxl %s" % qxl_dev_nr
577 if has_option(help, "vga") and vga
and vga
!= "qxl":
578 str += " -vga %s" % vga
579 elif has_option(help, "vga"):
583 str += " -vga %s" % vga
586 def add_kernel(help, filename
):
587 return " -kernel '%s'" % filename
589 def add_initrd(help, filename
):
590 return " -initrd '%s'" % filename
592 def add_kernel_cmdline(help, cmdline
):
593 return " -append %s" % cmdline
595 def add_testdev(help, filename
):
596 return (" -chardev file,id=testlog,path=%s"
597 " -device testdev,chardev=testlog" % filename
)
599 def add_no_hpet(help):
600 if has_option(help, "no-hpet"):
605 # End of command line option wrappers
612 root_dir
= self
.root_dir
614 # Clone this VM using the new params
615 vm
= self
.clone(name
, params
, root_dir
, copy_state
=True)
617 qemu_binary
= kvm_utils
.get_path(root_dir
, params
.get("qemu_binary",
619 # Get the output of 'qemu -help' (log a message in case this call never
620 # returns or causes some other kind of trouble)
621 logging
.debug("Getting output of 'qemu -help'")
622 help = commands
.getoutput("%s -help" % qemu_binary
)
624 # Start constructing the qemu command
626 # Set the X11 display parameter if requested
627 if params
.get("x11_display"):
628 qemu_cmd
+= "DISPLAY=%s " % params
.get("x11_display")
629 # Add the qemu binary
630 qemu_cmd
+= qemu_binary
632 qemu_cmd
+= add_name(help, name
)
634 for monitor_name
in params
.objects("monitors"):
635 monitor_params
= params
.object_params(monitor_name
)
636 monitor_filename
= vm
.get_monitor_filename(monitor_name
)
637 if monitor_params
.get("monitor_type") == "qmp":
638 qemu_cmd
+= add_qmp_monitor(help, monitor_filename
)
640 qemu_cmd
+= add_human_monitor(help, monitor_filename
)
642 # Add serial console redirection
643 qemu_cmd
+= add_serial(help, vm
.get_serial_console_filename())
645 for image_name
in params
.objects("images"):
646 image_params
= params
.object_params(image_name
)
647 if image_params
.get("boot_drive") == "no":
649 qemu_cmd
+= add_drive(help,
650 get_image_filename(image_params
, root_dir
),
651 image_params
.get("drive_index"),
652 image_params
.get("drive_format"),
653 image_params
.get("drive_cache"),
654 image_params
.get("drive_werror"),
655 image_params
.get("drive_serial"),
656 image_params
.get("image_snapshot") == "yes",
657 image_params
.get("image_boot") == "yes")
660 for redir_name
in params
.objects("redirs"):
661 redir_params
= params
.object_params(redir_name
)
662 guest_port
= int(redir_params
.get("guest_port"))
663 host_port
= vm
.redirs
.get(guest_port
)
664 redirs
+= [(host_port
, guest_port
)]
667 for nic_name
in params
.objects("nics"):
668 nic_params
= params
.object_params(nic_name
)
670 netdev_id
= vm
.netdev_id
[vlan
]
671 device_id
= vm
.device_id
[vlan
]
674 # Handle the '-net nic' part
676 mac
= vm
.get_mac_address(vlan
)
677 except VMAddressError
:
679 qemu_cmd
+= add_nic(help, vlan
, nic_params
.get("nic_model"), mac
,
680 device_id
, netdev_id
, nic_params
.get("nic_extra_params"))
681 # Handle the '-net tap' or '-net user' or '-netdev' part
682 script
= nic_params
.get("nic_script")
683 downscript
= nic_params
.get("nic_downscript")
684 tftp
= nic_params
.get("tftp")
686 script
= kvm_utils
.get_path(root_dir
, script
)
688 downscript
= kvm_utils
.get_path(root_dir
, downscript
)
690 tftp
= kvm_utils
.get_path(root_dir
, tftp
)
691 qemu_cmd
+= add_net(help, vlan
, nic_params
.get("nic_mode", "user"),
693 script
, downscript
, tftp
,
694 nic_params
.get("bootp"), redirs
, netdev_id
,
695 nic_params
.get("netdev_extra_params"))
696 # Proceed to next NIC
699 mem
= params
.get("mem")
701 qemu_cmd
+= add_mem(help, mem
)
703 smp
= params
.get("smp")
705 qemu_cmd
+= add_smp(help, smp
)
707 for cdrom
in params
.objects("cdroms"):
708 cdrom_params
= params
.object_params(cdrom
)
709 iso
= cdrom_params
.get("cdrom")
711 qemu_cmd
+= add_cdrom(help, kvm_utils
.get_path(root_dir
, iso
),
712 cdrom_params
.get("drive_index"))
714 # We may want to add {floppy_otps} parameter for -fda
715 # {fat:floppy:}/path/. However vvfat is not usually recommended.
716 floppy
= params
.get("floppy")
718 floppy
= kvm_utils
.get_path(root_dir
, floppy
)
719 qemu_cmd
+= add_floppy(help, floppy
)
721 tftp
= params
.get("tftp")
723 tftp
= kvm_utils
.get_path(root_dir
, tftp
)
724 qemu_cmd
+= add_tftp(help, tftp
)
726 bootp
= params
.get("bootp")
728 qemu_cmd
+= add_bootp(help, bootp
)
730 kernel
= params
.get("kernel")
732 kernel
= kvm_utils
.get_path(root_dir
, kernel
)
733 qemu_cmd
+= add_kernel(help, kernel
)
735 kernel_cmdline
= params
.get("kernel_cmdline")
737 qemu_cmd
+= add_kernel_cmdline(help, kernel_cmdline
)
739 initrd
= params
.get("initrd")
741 initrd
= kvm_utils
.get_path(root_dir
, initrd
)
742 qemu_cmd
+= add_initrd(help, initrd
)
744 for host_port
, guest_port
in redirs
:
745 qemu_cmd
+= add_tcp_redir(help, host_port
, guest_port
)
747 if params
.get("display") == "vnc":
748 qemu_cmd
+= add_vnc(help, vm
.vnc_port
)
749 elif params
.get("display") == "sdl":
750 qemu_cmd
+= add_sdl(help)
751 elif params
.get("display") == "nographic":
752 qemu_cmd
+= add_nographic(help)
753 elif params
.get("display") == "spice":
754 qemu_cmd
+= add_spice(help, self
.spice_port
, params
.get("spice"))
758 if params
.get("qxl"):
759 qxl
= params
.get("qxl")
760 if params
.get("vga"):
761 vga
= params
.get("vga")
763 if params
.get("display") == "spice":
764 qxl_dev_nr
= params
.get("qxl_dev_nr", None)
765 qemu_cmd
+= add_qxl_vga(help, qxl
, vga
, qxl_dev_nr
)
767 if params
.get("uuid") == "random":
768 qemu_cmd
+= add_uuid(help, vm
.uuid
)
769 elif params
.get("uuid"):
770 qemu_cmd
+= add_uuid(help, params
.get("uuid"))
772 if params
.get("testdev") == "yes":
773 qemu_cmd
+= add_testdev(help, vm
.get_testlog_filename())
775 if params
.get("disable_hpet") == "yes":
776 qemu_cmd
+= add_no_hpet(help)
778 # If the PCI assignment step went OK, add each one of the PCI assigned
779 # devices to the qemu command line.
780 if vm
.pci_assignable
:
781 for pci_id
in vm
.pa_pci_ids
:
782 qemu_cmd
+= add_pcidevice(help, pci_id
)
784 extra_params
= params
.get("extra_params")
786 qemu_cmd
+= " %s" % extra_params
792 def create(self
, name
=None, params
=None, root_dir
=None, timeout
=5.0,
793 migration_mode
=None, mac_source
=None):
795 Start the VM by running a qemu command.
796 All parameters are optional. If name, params or root_dir are not
797 supplied, the respective values stored as class attributes are used.
799 @param name: The name of the object
800 @param params: A dict containing VM params
801 @param root_dir: Base directory for relative filenames
802 @param migration_mode: If supplied, start VM for incoming migration
803 using this protocol (either 'tcp', 'unix' or 'exec')
804 @param migration_exec_cmd: Command to embed in '-incoming "exec: ..."'
805 (e.g. 'gzip -c -d filename') if migration_mode is 'exec'
806 @param mac_source: A VM object from which to copy MAC addresses. If not
807 specified, new addresses will be generated.
809 @raise VMCreateError: If qemu terminates unexpectedly
810 @raise VMKVMInitError: If KVM initialization fails
811 @raise VMHugePageError: If hugepage initialization fails
812 @raise VMImageMissingError: If a CD image is missing
813 @raise VMHashMismatchError: If a CD image hash has doesn't match the
815 @raise VMBadPATypeError: If an unsupported PCI assignment type is
817 @raise VMPAError: If no PCI assignable devices could be assigned
819 error
.context("creating '%s'" % self
.name
)
820 self
.destroy(free_mac_addresses
=False)
824 if params
is not None:
826 if root_dir
is not None:
827 self
.root_dir
= root_dir
830 root_dir
= self
.root_dir
832 # Verify the md5sum of the ISO images
833 for cdrom
in params
.objects("cdroms"):
834 cdrom_params
= params
.object_params(cdrom
)
835 iso
= cdrom_params
.get("cdrom")
837 iso
= kvm_utils
.get_path(root_dir
, iso
)
838 if not os
.path
.exists(iso
):
839 raise VMImageMissingError(iso
)
841 if cdrom_params
.get("md5sum_1m"):
842 logging
.debug("Comparing expected MD5 sum with MD5 sum of "
843 "first MB of ISO file...")
844 actual_hash
= utils
.hash_file(iso
, 1048576, method
="md5")
845 expected_hash
= cdrom_params
.get("md5sum_1m")
847 elif cdrom_params
.get("md5sum"):
848 logging
.debug("Comparing expected MD5 sum with MD5 sum of "
850 actual_hash
= utils
.hash_file(iso
, method
="md5")
851 expected_hash
= cdrom_params
.get("md5sum")
853 elif cdrom_params
.get("sha1sum"):
854 logging
.debug("Comparing expected SHA1 sum with SHA1 sum "
856 actual_hash
= utils
.hash_file(iso
, method
="sha1")
857 expected_hash
= cdrom_params
.get("sha1sum")
860 if actual_hash
== expected_hash
:
861 logging
.debug("Hashes match")
863 raise VMHashMismatchError(actual_hash
, expected_hash
)
865 # Make sure the following code is not executed by more than one thread
867 lockfile
= open("/tmp/kvm-autotest-vm-create.lock", "w+")
868 fcntl
.lockf(lockfile
, fcntl
.LOCK_EX
)
871 # Handle port redirections
872 redir_names
= params
.objects("redirs")
873 host_ports
= kvm_utils
.find_free_ports(5000, 6000, len(redir_names
))
875 for i
in range(len(redir_names
)):
876 redir_params
= params
.object_params(redir_names
[i
])
877 guest_port
= int(redir_params
.get("guest_port"))
878 self
.redirs
[guest_port
] = host_ports
[i
]
880 # Generate netdev/device IDs for all NICs
883 for nic
in params
.objects("nics"):
884 self
.netdev_id
.append(kvm_utils
.generate_random_id())
885 self
.device_id
.append(kvm_utils
.generate_random_id())
887 # Find available VNC port, if needed
888 if params
.get("display") == "vnc":
889 self
.vnc_port
= kvm_utils
.find_free_port(5900, 6100)
891 # Find available spice port
892 if params
.get("spice"):
893 self
.spice_port
= kvm_utils
.find_free_port(8000, 8100)
895 # Find random UUID if specified 'uuid = random' in config file
896 if params
.get("uuid") == "random":
897 f
= open("/proc/sys/kernel/random/uuid")
898 self
.uuid
= f
.read().strip()
901 # Generate or copy MAC addresses for all NICs
902 num_nics
= len(params
.objects("nics"))
903 for vlan
in range(num_nics
):
904 nic_name
= params
.objects("nics")[vlan
]
905 nic_params
= params
.object_params(nic_name
)
906 mac
= (nic_params
.get("nic_mac") or
907 mac_source
and mac_source
.get_mac_address(vlan
))
909 kvm_utils
.set_mac_address(self
.instance
, vlan
, mac
)
911 kvm_utils
.generate_mac_address(self
.instance
, vlan
)
913 # Assign a PCI assignable device
914 self
.pci_assignable
= None
915 pa_type
= params
.get("pci_assignable")
916 if pa_type
and pa_type
!= "no":
917 pa_devices_requested
= params
.get("devices_requested")
919 # Virtual Functions (VF) assignable devices
921 self
.pci_assignable
= kvm_utils
.PciAssignable(
923 driver
=params
.get("driver"),
924 driver_option
=params
.get("driver_option"),
925 devices_requested
=pa_devices_requested
)
926 # Physical NIC (PF) assignable devices
927 elif pa_type
== "pf":
928 self
.pci_assignable
= kvm_utils
.PciAssignable(
930 names
=params
.get("device_names"),
931 devices_requested
=pa_devices_requested
)
932 # Working with both VF and PF
933 elif pa_type
== "mixed":
934 self
.pci_assignable
= kvm_utils
.PciAssignable(
936 driver
=params
.get("driver"),
937 driver_option
=params
.get("driver_option"),
938 names
=params
.get("device_names"),
939 devices_requested
=pa_devices_requested
)
941 raise VMBadPATypeError(pa_type
)
943 self
.pa_pci_ids
= self
.pci_assignable
.request_devs()
946 logging
.debug("Successfuly assigned devices: %s",
949 raise VMPAError(pa_type
)
952 qemu_command
= self
.make_qemu_command()
954 # Add migration parameters if required
955 if migration_mode
== "tcp":
956 self
.migration_port
= kvm_utils
.find_free_port(5200, 6000)
957 qemu_command
+= " -incoming tcp:0:%d" % self
.migration_port
958 elif migration_mode
== "unix":
959 self
.migration_file
= "/tmp/migration-unix-%s" % self
.instance
960 qemu_command
+= " -incoming unix:%s" % self
.migration_file
961 elif migration_mode
== "exec":
962 self
.migration_port
= kvm_utils
.find_free_port(5200, 6000)
963 qemu_command
+= (' -incoming "exec:nc -l %s"' %
966 logging
.info("Running qemu command:\n%s", qemu_command
)
967 self
.process
= kvm_subprocess
.run_bg(qemu_command
, None,
968 logging
.info
, "(qemu) ")
970 # Make sure the process was started successfully
971 if not self
.process
.is_alive():
972 e
= VMCreateError(qemu_command
,
973 self
.process
.get_status(),
974 self
.process
.get_output())
978 # Establish monitor connections
980 for monitor_name
in params
.objects("monitors"):
981 monitor_params
= params
.object_params(monitor_name
)
982 # Wait for monitor connection to succeed
983 end_time
= time
.time() + timeout
984 while time
.time() < end_time
:
986 if monitor_params
.get("monitor_type") == "qmp":
988 monitor
= kvm_monitor
.QMPMonitor(
990 self
.get_monitor_filename(monitor_name
))
992 # Add a "human" monitor
993 monitor
= kvm_monitor
.HumanMonitor(
995 self
.get_monitor_filename(monitor_name
))
996 monitor
.verify_responsive()
998 except kvm_monitor
.MonitorError
, e
:
1004 # Add this monitor to the list
1005 self
.monitors
+= [monitor
]
1007 # Get the output so far, to see if we have any problems with
1008 # KVM modules or with hugepage setup.
1009 output
= self
.process
.get_output()
1011 if re
.search("Could not initialize KVM", output
, re
.IGNORECASE
):
1012 e
= VMKVMInitError(qemu_command
, self
.process
.get_output())
1016 if "alloc_mem_area" in output
:
1017 e
= VMHugePageError(qemu_command
, self
.process
.get_output())
1021 logging
.debug("VM appears to be alive with PID %s", self
.get_pid())
1023 # Establish a session with the serial console -- requires a version
1024 # of netcat that supports -U
1025 self
.serial_console
= kvm_subprocess
.ShellSession(
1026 "nc -U %s" % self
.get_serial_console_filename(),
1028 output_func
=kvm_utils
.log_line
,
1029 output_params
=("serial-%s.log" % name
,))
1032 fcntl
.lockf(lockfile
, fcntl
.LOCK_UN
)
1036 def destroy(self
, gracefully
=True, free_mac_addresses
=True):
1040 If gracefully is True, first attempt to shutdown the VM with a shell
1041 command. Then, attempt to destroy the VM via the monitor with a 'quit'
1042 command. If that fails, send SIGKILL to the qemu process.
1044 @param gracefully: If True, an attempt will be made to end the VM
1045 using a shell command before trying to end the qemu process
1046 with a 'quit' or a kill signal.
1047 @param free_mac_addresses: If True, the MAC addresses used by the VM
1051 # Is it already dead?
1055 logging
.debug("Destroying VM with PID %s...", self
.get_pid())
1057 if gracefully
and self
.params
.get("shutdown_command"):
1058 # Try to destroy with shell command
1059 logging
.debug("Trying to shutdown VM with shell command...")
1061 session
= self
.login()
1062 except (kvm_utils
.LoginError
, VMError
), e
:
1066 # Send the shutdown command
1067 session
.sendline(self
.params
.get("shutdown_command"))
1068 logging
.debug("Shutdown command sent; waiting for VM "
1070 if kvm_utils
.wait_for(self
.is_dead
, 60, 1, 1):
1071 logging
.debug("VM is down")
1077 # Try to destroy with a monitor command
1078 logging
.debug("Trying to kill VM with monitor command...")
1081 except kvm_monitor
.MonitorError
, e
:
1084 # Wait for the VM to be really dead
1085 if kvm_utils
.wait_for(self
.is_dead
, 5, 0.5, 0.5):
1086 logging
.debug("VM is down")
1089 # If the VM isn't dead yet...
1090 logging
.debug("Cannot quit normally; sending a kill to close the "
1092 kvm_utils
.kill_process_tree(self
.process
.get_pid(), 9)
1093 # Wait for the VM to be really dead
1094 if kvm_utils
.wait_for(self
.is_dead
, 5, 0.5, 0.5):
1095 logging
.debug("VM is down")
1098 logging
.error("Process %s is a zombie!", self
.process
.get_pid())
1102 if self
.pci_assignable
:
1103 self
.pci_assignable
.release_devs()
1105 self
.process
.close()
1106 if self
.serial_console
:
1107 self
.serial_console
.close()
1108 for f
in ([self
.get_testlog_filename(),
1109 self
.get_serial_console_filename()] +
1110 self
.get_monitor_filenames()):
1115 if hasattr(self
, "migration_file"):
1117 os
.unlink(self
.migration_file
)
1120 if free_mac_addresses
:
1121 num_nics
= len(self
.params
.objects("nics"))
1122 for vlan
in range(num_nics
):
1123 self
.free_mac_address(vlan
)
1129 Return the main monitor object, selected by the parameter main_monitor.
1130 If main_monitor isn't defined, return the first monitor.
1131 If no monitors exist, or if main_monitor refers to a nonexistent
1132 monitor, return None.
1134 for m
in self
.monitors
:
1135 if m
.name
== self
.params
.get("main_monitor"):
1137 if self
.monitors
and not self
.params
.get("main_monitor"):
1138 return self
.monitors
[0]
1141 def verify_alive(self
):
1143 Make sure the VM is alive and that the main monitor is responsive.
1145 @raise VMDeadError: If the VM is dead
1146 @raise: Various monitor exceptions if the monitor is unresponsive
1149 raise VMDeadError(self
.process
.get_status(),
1150 self
.process
.get_output())
1152 self
.monitor
.verify_responsive()
1157 Return True if the VM is alive and its monitor is responsive.
1159 return not self
.is_dead() and (not self
.monitors
or
1160 self
.monitor
.is_responsive())
1165 Return True if the qemu process is dead.
1167 return not self
.process
or not self
.process
.is_alive()
1170 def verify_kernel_crash(self
):
1172 Find kernel crash message on the VM serial console.
1174 @raise: VMDeadKernelCrashError, in case a kernel crash message was
1177 data
= self
.serial_console
.get_output()
1178 match
= re
.search(r
"BUG:.*---\[ end trace .* \]---", data
,
1179 re
.DOTALL|re
.MULTILINE
)
1180 if match
is not None:
1181 raise VMDeadKernelCrashError(match
.group(0))
1184 def get_params(self
):
1186 Return the VM's params dict. Most modified params take effect only
1192 def get_monitor_filename(self
, monitor_name
):
1194 Return the filename corresponding to a given monitor name.
1196 return "/tmp/monitor-%s-%s" % (monitor_name
, self
.instance
)
1199 def get_monitor_filenames(self
):
1201 Return a list of all monitor filenames (as specified in the VM's
1204 return [self
.get_monitor_filename(m
) for m
in
1205 self
.params
.objects("monitors")]
1208 def get_serial_console_filename(self
):
1210 Return the serial console filename.
1212 return "/tmp/serial-%s" % self
.instance
1215 def get_testlog_filename(self
):
1217 Return the testlog filename.
1219 return "/tmp/testlog-%s" % self
.instance
1222 def get_address(self
, index
=0):
1224 Return the address of a NIC of the guest, in host space.
1226 If port redirection is used, return 'localhost' (the NIC has no IP
1227 address of its own). Otherwise return the NIC's IP address.
1229 @param index: Index of the NIC whose address is requested.
1230 @raise VMMACAddressMissingError: If no MAC address is defined for the
1232 @raise VMIPAddressMissingError: If no IP address is found for the the
1234 @raise VMAddressVerificationError: If the MAC-IP address mapping cannot
1235 be verified (using arping)
1237 nics
= self
.params
.objects("nics")
1238 nic_name
= nics
[index
]
1239 nic_params
= self
.params
.object_params(nic_name
)
1240 if nic_params
.get("nic_mode") == "tap":
1241 mac
= self
.get_mac_address(index
).lower()
1242 # Get the IP address from the cache
1243 ip
= self
.address_cache
.get(mac
)
1245 raise VMIPAddressMissingError(mac
)
1246 # Make sure the IP address is assigned to this guest
1247 macs
= [self
.get_mac_address(i
) for i
in range(len(nics
))]
1248 if not kvm_utils
.verify_ip_address_ownership(ip
, macs
):
1249 raise VMAddressVerificationError(mac
, ip
)
1255 def get_port(self
, port
, nic_index
=0):
1257 Return the port in host space corresponding to port in guest space.
1259 @param port: Port number in host space.
1260 @param nic_index: Index of the NIC.
1261 @return: If port redirection is used, return the host port redirected
1262 to guest port port. Otherwise return port.
1263 @raise VMPortNotRedirectedError: If an unredirected port is requested
1266 nic_name
= self
.params
.objects("nics")[nic_index
]
1267 nic_params
= self
.params
.object_params(nic_name
)
1268 if nic_params
.get("nic_mode") == "tap":
1272 return self
.redirs
[port
]
1274 raise VMPortNotRedirectedError(port
)
1277 def get_peer(self
, netid
):
1279 Return the peer of netdev or network deivce.
1281 @param netid: id of netdev or device
1282 @return: id of the peer device otherwise None
1284 network_info
= self
.monitor
.info("network")
1286 return re
.findall("%s:.*peer=(.*)" % netid
, network_info
)[0]
1291 def get_ifname(self
, nic_index
=0):
1293 Return the ifname of a tap device associated with a NIC.
1295 @param nic_index: Index of the NIC
1297 nics
= self
.params
.objects("nics")
1298 nic_name
= nics
[nic_index
]
1299 nic_params
= self
.params
.object_params(nic_name
)
1300 if nic_params
.get("nic_ifname"):
1301 return nic_params
.get("nic_ifname")
1303 return "t%d-%s" % (nic_index
, self
.instance
[-11:])
1306 def get_mac_address(self
, nic_index
=0):
1308 Return the MAC address of a NIC.
1310 @param nic_index: Index of the NIC
1311 @raise VMMACAddressMissingError: If no MAC address is defined for the
1314 nic_name
= self
.params
.objects("nics")[nic_index
]
1315 nic_params
= self
.params
.object_params(nic_name
)
1316 mac
= (nic_params
.get("nic_mac") or
1317 kvm_utils
.get_mac_address(self
.instance
, nic_index
))
1319 raise VMMACAddressMissingError(nic_index
)
1323 def free_mac_address(self
, nic_index
=0):
1325 Free a NIC's MAC address.
1327 @param nic_index: Index of the NIC
1329 kvm_utils
.free_mac_address(self
.instance
, nic_index
)
1334 Return the VM's PID. If the VM is dead return None.
1336 @note: This works under the assumption that self.process.get_pid()
1337 returns the PID of the parent shell process.
1340 children
= commands
.getoutput("ps --ppid=%d -o pid=" %
1341 self
.process
.get_pid()).split()
1342 return int(children
[0])
1343 except (TypeError, IndexError, ValueError):
1347 def get_shell_pid(self
):
1349 Return the PID of the parent shell process.
1351 @note: This works under the assumption that self.process.get_pid()
1352 returns the PID of the parent shell process.
1354 return self
.process
.get_pid()
1357 def get_shared_meminfo(self
):
1359 Returns the VM's shared memory information.
1361 @return: Shared memory used by VM (MB)
1364 logging
.error("Could not get shared memory info from dead VM.")
1367 filename
= "/proc/%d/statm" % self
.get_pid()
1368 shm
= int(open(filename
).read().split()[2])
1369 # statm stores informations in pages, translate it to MB
1370 return shm
* 4.0 / 1024
1373 @error.context_aware
1374 def login(self
, nic_index
=0, timeout
=10):
1376 Log into the guest via SSH/Telnet/Netcat.
1377 If timeout expires while waiting for output from the guest (e.g. a
1378 password prompt or a shell prompt) -- fail.
1380 @param nic_index: The index of the NIC to connect to.
1381 @param timeout: Time (seconds) before giving up logging into the
1383 @return: A ShellSession object.
1385 error
.context("logging into '%s'" % self
.name
)
1386 username
= self
.params
.get("username", "")
1387 password
= self
.params
.get("password", "")
1388 prompt
= self
.params
.get("shell_prompt", "[\#\$]")
1389 linesep
= eval("'%s'" % self
.params
.get("shell_linesep", r
"\n"))
1390 client
= self
.params
.get("shell_client")
1391 address
= self
.get_address(nic_index
)
1392 port
= self
.get_port(int(self
.params
.get("shell_port")))
1393 log_filename
= ("session-%s-%s.log" %
1394 (self
.name
, kvm_utils
.generate_random_string(4)))
1395 session
= kvm_utils
.remote_login(client
, address
, port
, username
,
1396 password
, prompt
, linesep
,
1397 log_filename
, timeout
)
1398 session
.set_status_test_command(self
.params
.get("status_test_command",
1403 def remote_login(self
, nic_index
=0, timeout
=10):
1405 Alias for login() for backward compatibility.
1407 return self
.login(nic_index
, timeout
)
1410 def wait_for_login(self
, nic_index
=0, timeout
=240, internal_timeout
=10):
1412 Make multiple attempts to log into the guest via SSH/Telnet/Netcat.
1414 @param nic_index: The index of the NIC to connect to.
1415 @param timeout: Time (seconds) to keep trying to log in.
1416 @param internal_timeout: Timeout to pass to login().
1417 @return: A ShellSession object.
1419 logging
.debug("Attempting to log into '%s' (timeout %ds)", self
.name
,
1421 end_time
= time
.time() + timeout
1422 while time
.time() < end_time
:
1424 return self
.login(nic_index
, internal_timeout
)
1425 except (kvm_utils
.LoginError
, VMError
), e
:
1428 # Timeout expired; try one more time but don't catch exceptions
1429 return self
.login(nic_index
, internal_timeout
)
1432 @error.context_aware
1433 def copy_files_to(self
, host_path
, guest_path
, nic_index
=0, verbose
=False,
1436 Transfer files to the remote host(guest).
1438 @param host_path: Host path
1439 @param guest_path: Guest path
1440 @param nic_index: The index of the NIC to connect to.
1441 @param verbose: If True, log some stats using logging.debug (RSS only)
1442 @param timeout: Time (seconds) before giving up on doing the remote
1445 error
.context("sending file(s) to '%s'" % self
.name
)
1446 username
= self
.params
.get("username", "")
1447 password
= self
.params
.get("password", "")
1448 client
= self
.params
.get("file_transfer_client")
1449 address
= self
.get_address(nic_index
)
1450 port
= self
.get_port(int(self
.params
.get("file_transfer_port")))
1451 log_filename
= ("transfer-%s-to-%s-%s.log" %
1452 (self
.name
, address
,
1453 kvm_utils
.generate_random_string(4)))
1454 kvm_utils
.copy_files_to(address
, client
, username
, password
, port
,
1455 host_path
, guest_path
, log_filename
, verbose
,
1459 @error.context_aware
1460 def copy_files_from(self
, guest_path
, host_path
, nic_index
=0,
1461 verbose
=False, timeout
=600):
1463 Transfer files from the guest.
1465 @param host_path: Guest path
1466 @param guest_path: Host path
1467 @param nic_index: The index of the NIC to connect to.
1468 @param verbose: If True, log some stats using logging.debug (RSS only)
1469 @param timeout: Time (seconds) before giving up on doing the remote
1472 error
.context("receiving file(s) from '%s'" % self
.name
)
1473 username
= self
.params
.get("username", "")
1474 password
= self
.params
.get("password", "")
1475 client
= self
.params
.get("file_transfer_client")
1476 address
= self
.get_address(nic_index
)
1477 port
= self
.get_port(int(self
.params
.get("file_transfer_port")))
1478 log_filename
= ("transfer-%s-from-%s-%s.log" %
1479 (self
.name
, address
,
1480 kvm_utils
.generate_random_string(4)))
1481 kvm_utils
.copy_files_from(address
, client
, username
, password
, port
,
1482 guest_path
, host_path
, log_filename
,
1486 @error.context_aware
1487 def serial_login(self
, timeout
=10):
1489 Log into the guest via the serial console.
1490 If timeout expires while waiting for output from the guest (e.g. a
1491 password prompt or a shell prompt) -- fail.
1493 @param timeout: Time (seconds) before giving up logging into the guest.
1494 @return: ShellSession object on success and None on failure.
1496 error
.context("logging into '%s' via serial console" % self
.name
)
1497 username
= self
.params
.get("username", "")
1498 password
= self
.params
.get("password", "")
1499 prompt
= self
.params
.get("shell_prompt", "[\#\$]")
1500 linesep
= eval("'%s'" % self
.params
.get("shell_linesep", r
"\n"))
1501 status_test_command
= self
.params
.get("status_test_command", "")
1503 self
.serial_console
.set_linesep(linesep
)
1504 self
.serial_console
.set_status_test_command(status_test_command
)
1506 # Try to get a login prompt
1507 self
.serial_console
.sendline()
1509 kvm_utils
._remote
_login
(self
.serial_console
, username
, password
,
1511 return self
.serial_console
1514 def wait_for_serial_login(self
, timeout
=240, internal_timeout
=10):
1516 Make multiple attempts to log into the guest via serial console.
1518 @param timeout: Time (seconds) to keep trying to log in.
1519 @param internal_timeout: Timeout to pass to serial_login().
1520 @return: A ShellSession object.
1522 logging
.debug("Attempting to log into '%s' via serial console "
1523 "(timeout %ds)", self
.name
, timeout
)
1524 end_time
= time
.time() + timeout
1525 while time
.time() < end_time
:
1527 return self
.serial_login(internal_timeout
)
1528 except kvm_utils
.LoginError
, e
:
1531 # Timeout expired; try one more time but don't catch exceptions
1532 return self
.serial_login(internal_timeout
)
1535 @error.context_aware
1536 def migrate(self
, timeout
=3600, protocol
="tcp", cancel_delay
=None,
1537 offline
=False, stable_check
=False, clean
=True,
1538 save_path
="/tmp", dest_host
="localhost", remote_port
=None):
1542 If the migration is local, the VM object's state is switched with that
1543 of the destination VM. Otherwise, the state is switched with that of
1544 a dead VM (returned by self.clone()).
1546 @param timeout: Time to wait for migration to complete.
1547 @param protocol: Migration protocol ('tcp', 'unix' or 'exec').
1548 @param cancel_delay: If provided, specifies a time duration after which
1549 migration will be canceled. Used for testing migrate_cancel.
1550 @param offline: If True, pause the source VM before migration.
1551 @param stable_check: If True, compare the VM's state after migration to
1552 its state before migration and raise an exception if they
1554 @param clean: If True, delete the saved state files (relevant only if
1555 stable_check is also True).
1556 @save_path: The path for state files.
1557 @param dest_host: Destination host (defaults to 'localhost').
1558 @param remote_port: Port to use for remote migration.
1560 error
.base_context("migrating '%s'" % self
.name
)
1563 o
= self
.monitor
.info("migrate")
1564 if isinstance(o
, str):
1565 return "status: active" not in o
1567 return o
.get("status") != "active"
1569 def mig_succeeded():
1570 o
= self
.monitor
.info("migrate")
1571 if isinstance(o
, str):
1572 return "status: completed" in o
1574 return o
.get("status") == "completed"
1577 o
= self
.monitor
.info("migrate")
1578 if isinstance(o
, str):
1579 return "status: failed" in o
1581 return o
.get("status") == "failed"
1583 def mig_cancelled():
1584 o
= self
.monitor
.info("migrate")
1585 if isinstance(o
, str):
1586 return ("Migration status: cancelled" in o
or
1587 "Migration status: canceled" in o
)
1589 return (o
.get("status") == "cancelled" or
1590 o
.get("status") == "canceled")
1592 def wait_for_migration():
1593 if not kvm_utils
.wait_for(mig_finished
, timeout
, 2, 2,
1594 "Waiting for migration to complete"):
1595 raise VMMigrateTimeoutError("Timeout expired while waiting "
1596 "for migration to finish")
1598 local
= dest_host
== "localhost"
1600 clone
= self
.clone()
1602 error
.context("creating destination VM")
1604 # Pause the dest vm after creation
1605 extra_params
= clone
.params
.get("extra_params", "") + " -S"
1606 clone
.params
["extra_params"] = extra_params
1607 clone
.create(migration_mode
=protocol
, mac_source
=self
)
1611 if protocol
== "tcp":
1613 uri
= "tcp:localhost:%d" % clone
.migration_port
1615 uri
= "tcp:%s:%d" % (dest_host
, remote_port
)
1616 elif protocol
== "unix":
1617 uri
= "unix:%s" % clone
.migration_file
1618 elif protocol
== "exec":
1619 uri
= '"exec:nc localhost %s"' % clone
.migration_port
1622 self
.monitor
.cmd("stop")
1624 logging
.info("Migrating to %s", uri
)
1625 self
.monitor
.migrate(uri
)
1628 time
.sleep(cancel_delay
)
1629 self
.monitor
.cmd("migrate_cancel")
1630 if not kvm_utils
.wait_for(mig_cancelled
, 60, 2, 2,
1631 "Waiting for migration "
1633 raise VMMigrateCancelError("Cannot cancel migration")
1636 wait_for_migration()
1638 # Report migration status
1640 logging
.info("Migration completed successfully")
1642 raise VMMigrateFailedError("Migration failed")
1644 raise VMMigrateFailedError("Migration ended with unknown "
1647 # Switch self <-> clone
1648 temp
= self
.clone(copy_state
=True)
1649 self
.__dict
__ = clone
.__dict
__
1652 # From now on, clone is the source VM that will soon be destroyed
1653 # and self is the destination VM that will remain alive. If this
1654 # is remote migration, self is a dead VM object.
1656 error
.context("after migration")
1661 if local
and stable_check
:
1663 save1
= os
.path
.join(save_path
, "src-" + clone
.instance
)
1664 save2
= os
.path
.join(save_path
, "dst-" + self
.instance
)
1665 clone
.save_to_file(save1
)
1666 self
.save_to_file(save2
)
1667 # Fail if we see deltas
1668 md5_save1
= utils
.hash_file(save1
)
1669 md5_save2
= utils
.hash_file(save2
)
1670 if md5_save1
!= md5_save2
:
1671 raise VMMigrateStateMismatchError(md5_save1
, md5_save2
)
1674 if os
.path
.isfile(save1
):
1676 if os
.path
.isfile(save2
):
1680 # If we're doing remote migration and it's completed successfully,
1681 # self points to a dead VM object
1683 self
.monitor
.cmd("cont")
1684 clone
.destroy(gracefully
=False)
1687 @error.context_aware
1688 def reboot(self
, session
=None, method
="shell", nic_index
=0, timeout
=240):
1690 Reboot the VM and wait for it to come back up by trying to log in until
1693 @param session: A shell session object or None.
1694 @param method: Reboot method. Can be "shell" (send a shell reboot
1695 command) or "system_reset" (send a system_reset monitor command).
1696 @param nic_index: Index of NIC to access in the VM, when logging in
1698 @param timeout: Time to wait for login to succeed (after rebooting).
1699 @return: A new shell session object.
1701 error
.base_context("rebooting '%s'" % self
.name
, logging
.info
)
1702 error
.context("before reboot")
1703 session
= session
or self
.login()
1706 if method
== "shell":
1707 session
.sendline(self
.params
.get("reboot_command"))
1708 elif method
== "system_reset":
1709 # Clear the event list of all QMP monitors
1710 qmp_monitors
= [m
for m
in self
.monitors
if m
.protocol
== "qmp"]
1711 for m
in qmp_monitors
:
1713 # Send a system_reset monitor command
1714 self
.monitor
.cmd("system_reset")
1715 # Look for RESET QMP events
1717 for m
in qmp_monitors
:
1718 if m
.get_event("RESET"):
1719 logging
.info("RESET QMP event received")
1721 raise VMRebootError("RESET QMP event not received after "
1722 "system_reset (monitor '%s')" % m
.name
)
1724 raise VMRebootError("Unknown reboot method: %s" % method
)
1726 error
.context("waiting for guest to go down", logging
.info
)
1727 if not kvm_utils
.wait_for(lambda:
1728 not session
.is_responsive(timeout
=30),
1730 raise VMRebootError("Guest refuses to go down")
1733 error
.context("logging in after reboot", logging
.info
)
1734 return self
.wait_for_login(nic_index
, timeout
=timeout
)
1737 def send_key(self
, keystr
):
1739 Send a key event to the VM.
1741 @param: keystr: A key event string (e.g. "ctrl-alt-delete")
1743 # For compatibility with versions of QEMU that do not recognize all
1744 # key names: replace keyname with the hex value from the dict, which
1745 # QEMU will definitely accept
1746 dict = {"comma": "0x33",
1749 for key
, value
in dict.items():
1750 keystr
= keystr
.replace(key
, value
)
1751 self
.monitor
.sendkey(keystr
)
1755 def send_string(self
, str):
1757 Send a string to the VM.
1759 @param str: String, that must consist of alphanumeric characters only.
1760 Capital letters are allowed.
1764 self
.send_key("shift-%s" % char
.lower())
1771 Catch UUID of the VM.
1773 @return: None,if not specified in config file
1775 if self
.params
.get("uuid") == "random":
1778 return self
.params
.get("uuid", None)
1781 def get_cpu_count(self
):
1783 Get the cpu count of the VM.
1785 session
= self
.login()
1787 return int(session
.cmd(self
.params
.get("cpu_chk_cmd")))
1792 def get_memory_size(self
, cmd
=None):
1794 Get bootup memory size of the VM.
1796 @param check_cmd: Command used to check memory. If not provided,
1797 self.params.get("mem_chk_cmd") will be used.
1799 session
= self
.login()
1802 cmd
= self
.params
.get("mem_chk_cmd")
1803 mem_str
= session
.cmd(cmd
)
1804 mem
= re
.findall("([0-9]+)", mem_str
)
1810 elif "MB" in mem_str
:
1814 return int(mem_size
)
1819 def get_current_memory_size(self
):
1821 Get current memory size of the VM, rather than bootup memory.
1823 cmd
= self
.params
.get("mem_chk_cur_cmd")
1824 return self
.get_memory_size(cmd
)
1827 def save_to_file(self
, path
):
1829 Save the state of virtual machine to a file through migrate to
1832 # Make sure we only get one iteration
1833 self
.monitor
.cmd("migrate_set_speed 1000g")
1834 self
.monitor
.cmd("migrate_set_downtime 100000000")
1835 self
.monitor
.migrate('"exec:cat>%s"' % path
)
1836 # Restore the speed and downtime of migration
1837 self
.monitor
.cmd("migrate_set_speed %d" % (32<<20))
1838 self
.monitor
.cmd("migrate_set_downtime 0.03")