KVM test: Create a verify_kernel_crash() VM method
[autotest-zwu.git] / client / tests / kvm / kvm_vm.py
blob8114670aafa632f33a075405200d9ee479259886
1 #!/usr/bin/python
2 """
3 Utility classes and functions to handle Virtual Machine creation using qemu.
5 @copyright: 2008-2009 Red Hat Inc.
6 """
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):
15 pass
18 class VMCreateError(VMError):
19 def __init__(self, cmd, status, output):
20 VMError.__init__(self, cmd, status, output)
21 self.cmd = cmd
22 self.status = status
23 self.output = output
25 def __str__(self):
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
36 def __str__(self):
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
46 def __str__(self):
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
55 def __str__(self):
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
64 def __str__(self):
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
73 def __str__(self):
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)
81 self.cmd = cmd
82 self.output = output
85 class VMHugePageError(VMPostCreateError):
86 def __str__(self):
87 return ("Cannot allocate hugepage memory (command: %r, "
88 "output: %r)" % (self.cmd, self.output))
91 class VMKVMInitError(VMPostCreateError):
92 def __str__(self):
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)
100 self.status = status
101 self.output = output
103 def __str__(self):
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
113 def __str__(self):
114 return ("VM is dead due to a kernel crash:\n%s" % self.kernel_crash)
117 class VMAddressError(VMError):
118 pass
121 class VMPortNotRedirectedError(VMAddressError):
122 def __init__(self, port):
123 VMAddressError.__init__(self, port)
124 self.port = port
126 def __str__(self):
127 return "Port not redirected: %s" % self.port
130 class VMAddressVerificationError(VMAddressError):
131 def __init__(self, mac, ip):
132 VMAddressError.__init__(self, mac, ip)
133 self.mac = mac
134 self.ip = ip
136 def __str__(self):
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
146 def __str__(self):
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)
153 self.mac = mac
155 def __str__(self):
156 return "Cannot find IP address for MAC address %s" % self.mac
159 class VMMigrateError(VMError):
160 pass
163 class VMMigrateTimeoutError(VMMigrateError):
164 pass
167 class VMMigrateCancelError(VMMigrateError):
168 pass
171 class VMMigrateFailedError(VMMigrateError):
172 pass
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
181 def __str__(self):
182 return ("Mismatch of VM state before and after migration (%s != %s)" %
183 (self.src_hash, self.dst_hash))
186 class VMRebootError(VMError):
187 pass
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":
204 return image_name
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",
224 "qemu-img"))
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)
256 else:
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
282 check_img = True
283 if not "check" in q_output:
284 logging.error("qemu-img does not support 'check', "
285 "skipping check...")
286 check_img = False
287 if not "info" in q_output:
288 logging.error("qemu-img does not support 'info', "
289 "skipping check...")
290 check_img = False
291 if check_img:
292 try:
293 utils.system("%s info %s" % (qemu_img_cmd, image_filename))
294 except error.CmdError:
295 logging.error("Error getting info from image %s",
296 image_filename)
297 try:
298 utils.system("%s check %s" % (qemu_img_cmd, image_filename))
299 except error.CmdError:
300 raise VMImageCheckError(image_filename)
302 else:
303 if not os.path.exists(image_filename):
304 logging.debug("Image file %s not found, skipping check...",
305 image_filename)
306 elif not image_is_qcow2:
307 logging.debug("Image file %s not qcow2, skipping check...",
308 image_filename)
311 class VM:
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__
327 if state:
328 self.__dict__ = state
329 else:
330 self.process = None
331 self.serial_console = None
332 self.redirs = {}
333 self.vnc_port = 5900
334 self.monitors = []
335 self.pci_assignable = None
336 self.netdev_id = []
337 self.device_id = []
338 self.uuid = None
340 # Find a unique identifier for this VM
341 while True:
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):
345 break
347 self.spice_port = 8000
348 self.name = name
349 self.params = params
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,
355 copy_state=False):
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().
369 if name is None:
370 name = self.name
371 if params is None:
372 params = self.params.copy()
373 if root_dir is None:
374 root_dir = self.root_dir
375 if address_cache is None:
376 address_cache = self.address_cache
377 if copy_state:
378 state = self.__dict__.copy()
379 else:
380 state = None
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
404 SDL rendering)
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
412 this image
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
418 NIC (e.g. e1000)
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
428 # accordingly.
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
452 return cmd
453 else:
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
461 if format:
462 cmd += ",if=%s" % format
463 if cache:
464 cmd += ",cache=%s" % cache
465 if werror:
466 cmd += ",werror=%s" % werror
467 if serial:
468 cmd += ",serial='%s'" % serial
469 if snapshot:
470 cmd += ",snapshot=on"
471 if boot:
472 cmd += ",boot=on"
473 return cmd
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
479 else:
480 netdev_vlan_str = ",vlan=%d" % vlan
481 if has_option(help, "device"):
482 if not model:
483 model = "rtl8139"
484 elif model == "virtio":
485 model = "virtio-net-pci"
486 cmd = " -device %s" % model + netdev_vlan_str
487 if mac:
488 cmd += ",mac='%s'" % mac
489 if nic_extra_params:
490 cmd += ",%s" % nic_extra_params
491 else:
492 cmd = " -net nic" + netdev_vlan_str
493 if model:
494 cmd += ",model=%s" % model
495 if mac:
496 cmd += ",macaddr='%s'" % mac
497 if device_id:
498 cmd += ",id='%s'" % device_id
499 return cmd
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
508 else:
509 cmd = " -net %s,vlan=%d" % (mode, vlan)
510 if mode == "tap":
511 if ifname: cmd += ",ifname='%s'" % ifname
512 if script: cmd += ",script='%s'" % script
513 cmd += ",downscript='%s'" % (downscript or "no")
514 elif mode == "user":
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)
522 return cmd
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:
530 return ""
531 else:
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:
537 return ""
538 else:
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:
544 return ""
545 else:
546 return " -redir tcp:%s::%s" % (host_port, guest_port)
548 def add_vnc(help, vnc_port):
549 return " -vnc :%d" % (vnc_port - 5900)
551 def add_sdl(help):
552 if has_option(help, "sdl"):
553 return " -sdl"
554 else:
555 return ""
557 def add_nographic(help):
558 return " -nographic"
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)
569 else:
570 return ""
572 def add_qxl_vga(help, qxl, vga, qxl_dev_nr=None):
573 str = ""
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"):
580 if qxl:
581 str += " -vga qxl"
582 elif vga:
583 str += " -vga %s" % vga
584 return str
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"):
601 return " -no-hpet"
602 else:
603 return ""
605 # End of command line option wrappers
607 if name is None:
608 name = self.name
609 if params is None:
610 params = self.params
611 if root_dir is None:
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",
618 "qemu"))
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
625 qemu_cmd = ""
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
631 # Add the VM's name
632 qemu_cmd += add_name(help, name)
633 # Add monitors
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)
639 else:
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":
648 continue
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")
659 redirs = []
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)]
666 vlan = 0
667 for nic_name in params.objects("nics"):
668 nic_params = params.object_params(nic_name)
669 try:
670 netdev_id = vm.netdev_id[vlan]
671 device_id = vm.device_id[vlan]
672 except IndexError:
673 netdev_id = None
674 # Handle the '-net nic' part
675 try:
676 mac = vm.get_mac_address(vlan)
677 except VMAddressError:
678 mac = None
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")
685 if script:
686 script = kvm_utils.get_path(root_dir, script)
687 if downscript:
688 downscript = kvm_utils.get_path(root_dir, downscript)
689 if tftp:
690 tftp = kvm_utils.get_path(root_dir, tftp)
691 qemu_cmd += add_net(help, vlan, nic_params.get("nic_mode", "user"),
692 vm.get_ifname(vlan),
693 script, downscript, tftp,
694 nic_params.get("bootp"), redirs, netdev_id,
695 nic_params.get("netdev_extra_params"))
696 # Proceed to next NIC
697 vlan += 1
699 mem = params.get("mem")
700 if mem:
701 qemu_cmd += add_mem(help, mem)
703 smp = params.get("smp")
704 if 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")
710 if iso:
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")
717 if floppy:
718 floppy = kvm_utils.get_path(root_dir, floppy)
719 qemu_cmd += add_floppy(help, floppy)
721 tftp = params.get("tftp")
722 if tftp:
723 tftp = kvm_utils.get_path(root_dir, tftp)
724 qemu_cmd += add_tftp(help, tftp)
726 bootp = params.get("bootp")
727 if bootp:
728 qemu_cmd += add_bootp(help, bootp)
730 kernel = params.get("kernel")
731 if kernel:
732 kernel = kvm_utils.get_path(root_dir, kernel)
733 qemu_cmd += add_kernel(help, kernel)
735 kernel_cmdline = params.get("kernel_cmdline")
736 if kernel_cmdline:
737 qemu_cmd += add_kernel_cmdline(help, kernel_cmdline)
739 initrd = params.get("initrd")
740 if 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"))
756 qxl = ""
757 vga = ""
758 if params.get("qxl"):
759 qxl = params.get("qxl")
760 if params.get("vga"):
761 vga = params.get("vga")
762 if qxl or 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")
785 if extra_params:
786 qemu_cmd += " %s" % extra_params
788 return qemu_cmd
791 @error.context_aware
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
814 expected hash
815 @raise VMBadPATypeError: If an unsupported PCI assignment type is
816 requested
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)
822 if name is not None:
823 self.name = name
824 if params is not None:
825 self.params = params
826 if root_dir is not None:
827 self.root_dir = root_dir
828 name = self.name
829 params = self.params
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")
836 if iso:
837 iso = kvm_utils.get_path(root_dir, iso)
838 if not os.path.exists(iso):
839 raise VMImageMissingError(iso)
840 compare = False
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")
846 compare = True
847 elif cdrom_params.get("md5sum"):
848 logging.debug("Comparing expected MD5 sum with MD5 sum of "
849 "ISO file...")
850 actual_hash = utils.hash_file(iso, method="md5")
851 expected_hash = cdrom_params.get("md5sum")
852 compare = True
853 elif cdrom_params.get("sha1sum"):
854 logging.debug("Comparing expected SHA1 sum with SHA1 sum "
855 "of ISO file...")
856 actual_hash = utils.hash_file(iso, method="sha1")
857 expected_hash = cdrom_params.get("sha1sum")
858 compare = True
859 if compare:
860 if actual_hash == expected_hash:
861 logging.debug("Hashes match")
862 else:
863 raise VMHashMismatchError(actual_hash, expected_hash)
865 # Make sure the following code is not executed by more than one thread
866 # at the same time
867 lockfile = open("/tmp/kvm-autotest-vm-create.lock", "w+")
868 fcntl.lockf(lockfile, fcntl.LOCK_EX)
870 try:
871 # Handle port redirections
872 redir_names = params.objects("redirs")
873 host_ports = kvm_utils.find_free_ports(5000, 6000, len(redir_names))
874 self.redirs = {}
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
881 self.netdev_id = []
882 self.device_id = []
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()
899 f.close()
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))
908 if mac:
909 kvm_utils.set_mac_address(self.instance, vlan, mac)
910 else:
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
920 if pa_type == "vf":
921 self.pci_assignable = kvm_utils.PciAssignable(
922 type=pa_type,
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(
929 type=pa_type,
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(
935 type=pa_type,
936 driver=params.get("driver"),
937 driver_option=params.get("driver_option"),
938 names=params.get("device_names"),
939 devices_requested=pa_devices_requested)
940 else:
941 raise VMBadPATypeError(pa_type)
943 self.pa_pci_ids = self.pci_assignable.request_devs()
945 if self.pa_pci_ids:
946 logging.debug("Successfuly assigned devices: %s",
947 self.pa_pci_ids)
948 else:
949 raise VMPAError(pa_type)
951 # Make qemu command
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"' %
964 self.migration_port)
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())
975 self.destroy()
976 raise e
978 # Establish monitor connections
979 self.monitors = []
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:
985 try:
986 if monitor_params.get("monitor_type") == "qmp":
987 # Add a QMP monitor
988 monitor = kvm_monitor.QMPMonitor(
989 monitor_name,
990 self.get_monitor_filename(monitor_name))
991 else:
992 # Add a "human" monitor
993 monitor = kvm_monitor.HumanMonitor(
994 monitor_name,
995 self.get_monitor_filename(monitor_name))
996 monitor.verify_responsive()
997 break
998 except kvm_monitor.MonitorError, e:
999 logging.warn(e)
1000 time.sleep(1)
1001 else:
1002 self.destroy()
1003 raise 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())
1013 self.destroy()
1014 raise e
1016 if "alloc_mem_area" in output:
1017 e = VMHugePageError(qemu_command, self.process.get_output())
1018 self.destroy()
1019 raise e
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(),
1027 auto_close=False,
1028 output_func=kvm_utils.log_line,
1029 output_params=("serial-%s.log" % name,))
1031 finally:
1032 fcntl.lockf(lockfile, fcntl.LOCK_UN)
1033 lockfile.close()
1036 def destroy(self, gracefully=True, free_mac_addresses=True):
1038 Destroy the VM.
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
1048 will be freed.
1050 try:
1051 # Is it already dead?
1052 if self.is_dead():
1053 return
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...")
1060 try:
1061 session = self.login()
1062 except (kvm_utils.LoginError, VMError), e:
1063 logging.debug(e)
1064 else:
1065 try:
1066 # Send the shutdown command
1067 session.sendline(self.params.get("shutdown_command"))
1068 logging.debug("Shutdown command sent; waiting for VM "
1069 "to go down...")
1070 if kvm_utils.wait_for(self.is_dead, 60, 1, 1):
1071 logging.debug("VM is down")
1072 return
1073 finally:
1074 session.close()
1076 if self.monitor:
1077 # Try to destroy with a monitor command
1078 logging.debug("Trying to kill VM with monitor command...")
1079 try:
1080 self.monitor.quit()
1081 except kvm_monitor.MonitorError, e:
1082 logging.warn(e)
1083 else:
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")
1087 return
1089 # If the VM isn't dead yet...
1090 logging.debug("Cannot quit normally; sending a kill to close the "
1091 "deal...")
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")
1096 return
1098 logging.error("Process %s is a zombie!", self.process.get_pid())
1100 finally:
1101 self.monitors = []
1102 if self.pci_assignable:
1103 self.pci_assignable.release_devs()
1104 if self.process:
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()):
1111 try:
1112 os.unlink(f)
1113 except OSError:
1114 pass
1115 if hasattr(self, "migration_file"):
1116 try:
1117 os.unlink(self.migration_file)
1118 except OSError:
1119 pass
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)
1126 @property
1127 def monitor(self):
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"):
1136 return m
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
1148 if self.is_dead():
1149 raise VMDeadError(self.process.get_status(),
1150 self.process.get_output())
1151 if self.monitors:
1152 self.monitor.verify_responsive()
1155 def is_alive(self):
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())
1163 def is_dead(self):
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
1175 found.
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
1187 upon VM.create().
1189 return self.params
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
1202 params).
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
1231 requested NIC
1232 @raise VMIPAddressMissingError: If no IP address is found for the the
1233 NIC's MAC address
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)
1244 if not ip:
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)
1250 return ip
1251 else:
1252 return "localhost"
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
1264 in user mode
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":
1269 return port
1270 else:
1271 try:
1272 return self.redirs[port]
1273 except KeyError:
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")
1285 try:
1286 return re.findall("%s:.*peer=(.*)" % netid, network_info)[0]
1287 except IndexError:
1288 return None
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")
1302 else:
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
1312 requested NIC
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))
1318 if not mac:
1319 raise VMMACAddressMissingError(nic_index)
1320 return mac
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)
1332 def get_pid(self):
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.
1339 try:
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):
1344 return None
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)
1363 if self.is_dead():
1364 logging.error("Could not get shared memory info from dead VM.")
1365 return None
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
1382 guest.
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",
1399 ""))
1400 return session
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,
1420 timeout)
1421 end_time = time.time() + timeout
1422 while time.time() < end_time:
1423 try:
1424 return self.login(nic_index, internal_timeout)
1425 except (kvm_utils.LoginError, VMError), e:
1426 logging.debug(e)
1427 time.sleep(2)
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,
1434 timeout=600):
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
1443 copy.
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,
1456 timeout)
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
1470 copy.
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,
1483 verbose, timeout)
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,
1510 prompt, timeout)
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:
1526 try:
1527 return self.serial_login(internal_timeout)
1528 except kvm_utils.LoginError, e:
1529 logging.debug(e)
1530 time.sleep(2)
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):
1540 Migrate the VM.
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
1553 differ.
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)
1562 def mig_finished():
1563 o = self.monitor.info("migrate")
1564 if isinstance(o, str):
1565 return "status: active" not in o
1566 else:
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
1573 else:
1574 return o.get("status") == "completed"
1576 def mig_failed():
1577 o = self.monitor.info("migrate")
1578 if isinstance(o, str):
1579 return "status: failed" in o
1580 else:
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)
1588 else:
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()
1601 if local:
1602 error.context("creating destination VM")
1603 if stable_check:
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)
1608 error.context()
1610 try:
1611 if protocol == "tcp":
1612 if local:
1613 uri = "tcp:localhost:%d" % clone.migration_port
1614 else:
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
1621 if offline:
1622 self.monitor.cmd("stop")
1624 logging.info("Migrating to %s", uri)
1625 self.monitor.migrate(uri)
1627 if cancel_delay:
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 "
1632 "cancellation"):
1633 raise VMMigrateCancelError("Cannot cancel migration")
1634 return
1636 wait_for_migration()
1638 # Report migration status
1639 if mig_succeeded():
1640 logging.info("Migration completed successfully")
1641 elif mig_failed():
1642 raise VMMigrateFailedError("Migration failed")
1643 else:
1644 raise VMMigrateFailedError("Migration ended with unknown "
1645 "status")
1647 # Switch self <-> clone
1648 temp = self.clone(copy_state=True)
1649 self.__dict__ = clone.__dict__
1650 clone = temp
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")
1657 if local:
1658 time.sleep(1)
1659 self.verify_alive()
1661 if local and stable_check:
1662 try:
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)
1672 finally:
1673 if clean:
1674 if os.path.isfile(save1):
1675 os.remove(save1)
1676 if os.path.isfile(save2):
1677 os.remove(save2)
1679 finally:
1680 # If we're doing remote migration and it's completed successfully,
1681 # self points to a dead VM object
1682 if self.is_alive():
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
1691 timeout expires.
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
1697 after rebooting.
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()
1704 error.context()
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:
1712 m.clear_events()
1713 # Send a system_reset monitor command
1714 self.monitor.cmd("system_reset")
1715 # Look for RESET QMP events
1716 time.sleep(1)
1717 for m in qmp_monitors:
1718 if m.get_event("RESET"):
1719 logging.info("RESET QMP event received")
1720 else:
1721 raise VMRebootError("RESET QMP event not received after "
1722 "system_reset (monitor '%s')" % m.name)
1723 else:
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),
1729 120, 0, 1):
1730 raise VMRebootError("Guest refuses to go down")
1731 session.close()
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",
1747 "dot": "0x34",
1748 "slash": "0x35"}
1749 for key, value in dict.items():
1750 keystr = keystr.replace(key, value)
1751 self.monitor.sendkey(keystr)
1752 time.sleep(0.2)
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.
1762 for char in str:
1763 if char.isupper():
1764 self.send_key("shift-%s" % char.lower())
1765 else:
1766 self.send_key(char)
1769 def get_uuid(self):
1771 Catch UUID of the VM.
1773 @return: None,if not specified in config file
1775 if self.params.get("uuid") == "random":
1776 return self.uuid
1777 else:
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()
1786 try:
1787 return int(session.cmd(self.params.get("cpu_chk_cmd")))
1788 finally:
1789 session.close()
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()
1800 try:
1801 if not cmd:
1802 cmd = self.params.get("mem_chk_cmd")
1803 mem_str = session.cmd(cmd)
1804 mem = re.findall("([0-9]+)", mem_str)
1805 mem_size = 0
1806 for m in mem:
1807 mem_size += int(m)
1808 if "GB" in mem_str:
1809 mem_size *= 1024
1810 elif "MB" in mem_str:
1811 pass
1812 else:
1813 mem_size /= 1024
1814 return int(mem_size)
1815 finally:
1816 session.close()
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
1830 exec
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")