| Index: client/tests/kvm/kvm_vm.py
|
| diff --git a/client/tests/kvm/kvm_vm.py b/client/tests/kvm/kvm_vm.py
|
| index a860437f6ea41e92d8f069b8bdc6e3a61d45e7f6..969558b92b8f52ecba07db4e6faf4adb59904ecf 100755
|
| --- a/client/tests/kvm/kvm_vm.py
|
| +++ b/client/tests/kvm/kvm_vm.py
|
| @@ -5,12 +5,179 @@ Utility classes and functions to handle Virtual Machine creation using qemu.
|
| @copyright: 2008-2009 Red Hat Inc.
|
| """
|
|
|
| -import time, socket, os, logging, fcntl, re, commands, shelve, glob
|
| -import kvm_utils, kvm_subprocess, kvm_monitor, rss_file_transfer
|
| +import time, os, logging, fcntl, re, commands, glob
|
| +import kvm_utils, kvm_subprocess, kvm_monitor
|
| from autotest_lib.client.common_lib import error
|
| from autotest_lib.client.bin import utils
|
|
|
|
|
| +class VMError(Exception):
|
| + pass
|
| +
|
| +
|
| +class VMCreateError(VMError):
|
| + def __init__(self, cmd, status, output):
|
| + VMError.__init__(self, cmd, status, output)
|
| + self.cmd = cmd
|
| + self.status = status
|
| + self.output = output
|
| +
|
| + def __str__(self):
|
| + return ("VM creation command failed: %r (status: %s, "
|
| + "output: %r)" % (self.cmd, self.status, self.output))
|
| +
|
| +
|
| +class VMHashMismatchError(VMError):
|
| + def __init__(self, actual, expected):
|
| + VMError.__init__(self, actual, expected)
|
| + self.actual_hash = actual
|
| + self.expected_hash = expected
|
| +
|
| + def __str__(self):
|
| + return ("CD image hash (%s) differs from expected one (%s)" %
|
| + (self.actual_hash, self.expected_hash))
|
| +
|
| +
|
| +class VMImageMissingError(VMError):
|
| + def __init__(self, filename):
|
| + VMError.__init__(self, filename)
|
| + self.filename = filename
|
| +
|
| + def __str__(self):
|
| + return "CD image file not found: %r" % self.filename
|
| +
|
| +
|
| +class VMImageCheckError(VMError):
|
| + def __init__(self, filename):
|
| + VMError.__init__(self, filename)
|
| + self.filename = filename
|
| +
|
| + def __str__(self):
|
| + return "Errors found on image: %r" % self.filename
|
| +
|
| +
|
| +class VMBadPATypeError(VMError):
|
| + def __init__(self, pa_type):
|
| + VMError.__init__(self, pa_type)
|
| + self.pa_type = pa_type
|
| +
|
| + def __str__(self):
|
| + return "Unsupported PCI assignable type: %r" % self.pa_type
|
| +
|
| +
|
| +class VMPAError(VMError):
|
| + def __init__(self, pa_type):
|
| + VMError.__init__(self, pa_type)
|
| + self.pa_type = pa_type
|
| +
|
| + def __str__(self):
|
| + return ("No PCI assignable devices could be assigned "
|
| + "(pci_assignable=%r)" % self.pa_type)
|
| +
|
| +
|
| +class VMPostCreateError(VMError):
|
| + def __init__(self, cmd, output):
|
| + VMError.__init__(self, cmd, output)
|
| + self.cmd = cmd
|
| + self.output = output
|
| +
|
| +
|
| +class VMHugePageError(VMPostCreateError):
|
| + def __str__(self):
|
| + return ("Cannot allocate hugepage memory (command: %r, "
|
| + "output: %r)" % (self.cmd, self.output))
|
| +
|
| +
|
| +class VMKVMInitError(VMPostCreateError):
|
| + def __str__(self):
|
| + return ("Cannot initialize KVM (command: %r, output: %r)" %
|
| + (self.cmd, self.output))
|
| +
|
| +
|
| +class VMDeadError(VMError):
|
| + def __init__(self, status, output):
|
| + VMError.__init__(self, status, output)
|
| + self.status = status
|
| + self.output = output
|
| +
|
| + def __str__(self):
|
| + return ("VM process is dead (status: %s, output: %r)" %
|
| + (self.status, self.output))
|
| +
|
| +
|
| +class VMAddressError(VMError):
|
| + pass
|
| +
|
| +
|
| +class VMPortNotRedirectedError(VMAddressError):
|
| + def __init__(self, port):
|
| + VMAddressError.__init__(self, port)
|
| + self.port = port
|
| +
|
| + def __str__(self):
|
| + return "Port not redirected: %s" % self.port
|
| +
|
| +
|
| +class VMAddressVerificationError(VMAddressError):
|
| + def __init__(self, mac, ip):
|
| + VMAddressError.__init__(self, mac, ip)
|
| + self.mac = mac
|
| + self.ip = ip
|
| +
|
| + def __str__(self):
|
| + return ("Cannot verify MAC-IP address mapping using arping: "
|
| + "%s ---> %s" % (self.mac, self.ip))
|
| +
|
| +
|
| +class VMMACAddressMissingError(VMAddressError):
|
| + def __init__(self, nic_index):
|
| + VMAddressError.__init__(self, nic_index)
|
| + self.nic_index = nic_index
|
| +
|
| + def __str__(self):
|
| + return "No MAC address defined for NIC #%s" % self.nic_index
|
| +
|
| +
|
| +class VMIPAddressMissingError(VMAddressError):
|
| + def __init__(self, mac):
|
| + VMAddressError.__init__(self, mac)
|
| + self.mac = mac
|
| +
|
| + def __str__(self):
|
| + return "Cannot find IP address for MAC address %s" % self.mac
|
| +
|
| +
|
| +class VMMigrateError(VMError):
|
| + pass
|
| +
|
| +
|
| +class VMMigrateTimeoutError(VMMigrateError):
|
| + pass
|
| +
|
| +
|
| +class VMMigrateCancelError(VMMigrateError):
|
| + pass
|
| +
|
| +
|
| +class VMMigrateFailedError(VMMigrateError):
|
| + pass
|
| +
|
| +
|
| +class VMMigrateStateMismatchError(VMMigrateError):
|
| + def __init__(self, src_hash, dst_hash):
|
| + VMMigrateError.__init__(self, src_hash, dst_hash)
|
| + self.src_hash = src_hash
|
| + self.dst_hash = dst_hash
|
| +
|
| + def __str__(self):
|
| + return ("Mismatch of VM state before and after migration (%s != %s)" %
|
| + (self.src_hash, self.dst_hash))
|
| +
|
| +
|
| +class VMRebootError(VMError):
|
| + pass
|
| +
|
| +
|
| def get_image_filename(params, root_dir):
|
| """
|
| Generate an image path from params and root_dir.
|
| @@ -24,6 +191,8 @@ def get_image_filename(params, root_dir):
|
| """
|
| image_name = params.get("image_name", "image")
|
| image_format = params.get("image_format", "qcow2")
|
| + if params.get("image_raw_device") == "yes":
|
| + return image_name
|
| image_filename = "%s.%s" % (image_name, image_format)
|
| image_filename = kvm_utils.get_path(root_dir, image_filename)
|
| return image_filename
|
| @@ -55,19 +224,8 @@ def create_image(params, root_dir):
|
| size = params.get("image_size", "10G")
|
| qemu_img_cmd += " %s" % size
|
|
|
| - try:
|
| - utils.system(qemu_img_cmd)
|
| - except error.CmdError, e:
|
| - logging.error("Could not create image; qemu-img command failed:\n%s",
|
| - str(e))
|
| - return None
|
| -
|
| - if not os.path.exists(image_filename):
|
| - logging.error("Image could not be created for some reason; "
|
| - "qemu-img command:\n%s" % qemu_img_cmd)
|
| - return None
|
| -
|
| - logging.info("Image created in %s" % image_filename)
|
| + utils.system(qemu_img_cmd)
|
| + logging.info("Image created in %r", image_filename)
|
| return image_filename
|
|
|
|
|
| @@ -83,19 +241,70 @@ def remove_image(params, root_dir):
|
| image_format -- the format of the image (qcow2, raw etc)
|
| """
|
| image_filename = get_image_filename(params, root_dir)
|
| - logging.debug("Removing image file %s..." % image_filename)
|
| + logging.debug("Removing image file %s...", image_filename)
|
| if os.path.exists(image_filename):
|
| os.unlink(image_filename)
|
| else:
|
| logging.debug("Image file %s not found")
|
|
|
|
|
| +def check_image(params, root_dir):
|
| + """
|
| + Check an image using qemu-img.
|
| +
|
| + @param params: Dictionary containing the test parameters.
|
| + @param root_dir: Base directory for relative filenames.
|
| +
|
| + @note: params should contain:
|
| + image_name -- the name of the image file, without extension
|
| + image_format -- the format of the image (qcow2, raw etc)
|
| +
|
| + @raise VMImageCheckError: In case qemu-img check fails on the image.
|
| + """
|
| + image_filename = get_image_filename(params, root_dir)
|
| + logging.debug("Checking image file %s...", image_filename)
|
| + qemu_img_cmd = kvm_utils.get_path(root_dir,
|
| + params.get("qemu_img_binary", "qemu-img"))
|
| + image_is_qcow2 = params.get("image_format") == 'qcow2'
|
| + if os.path.exists(image_filename) and image_is_qcow2:
|
| + # Verifying if qemu-img supports 'check'
|
| + q_result = utils.run(qemu_img_cmd, ignore_status=True)
|
| + q_output = q_result.stdout
|
| + check_img = True
|
| + if not "check" in q_output:
|
| + logging.error("qemu-img does not support 'check', "
|
| + "skipping check...")
|
| + check_img = False
|
| + if not "info" in q_output:
|
| + logging.error("qemu-img does not support 'info', "
|
| + "skipping check...")
|
| + check_img = False
|
| + if check_img:
|
| + try:
|
| + utils.system("%s info %s" % (qemu_img_cmd, image_filename))
|
| + except error.CmdError:
|
| + logging.error("Error getting info from image %s",
|
| + image_filename)
|
| + try:
|
| + utils.system("%s check %s" % (qemu_img_cmd, image_filename))
|
| + except error.CmdError:
|
| + raise VMImageCheckError(image_filename)
|
| +
|
| + else:
|
| + if not os.path.exists(image_filename):
|
| + logging.debug("Image file %s not found, skipping check...",
|
| + image_filename)
|
| + elif not image_is_qcow2:
|
| + logging.debug("Image file %s not qcow2, skipping check...",
|
| + image_filename)
|
| +
|
| +
|
| class VM:
|
| """
|
| This class handles all basic VM operations.
|
| """
|
|
|
| - def __init__(self, name, params, root_dir, address_cache):
|
| + def __init__(self, name, params, root_dir, address_cache, state=None):
|
| """
|
| Initialize the object and set a few attributes.
|
|
|
| @@ -104,30 +313,35 @@ class VM:
|
| (see method make_qemu_command for a full description)
|
| @param root_dir: Base directory for relative filenames
|
| @param address_cache: A dict that maps MAC addresses to IP addresses
|
| + @param state: If provided, use this as self.__dict__
|
| """
|
| - self.process = None
|
| - self.serial_console = None
|
| - self.redirs = {}
|
| - self.vnc_port = 5900
|
| - self.monitors = []
|
| - self.pci_assignable = None
|
| - self.netdev_id = []
|
| - self.uuid = None
|
| + if state:
|
| + self.__dict__ = state
|
| + else:
|
| + self.process = None
|
| + self.serial_console = None
|
| + self.redirs = {}
|
| + self.vnc_port = 5900
|
| + self.monitors = []
|
| + self.pci_assignable = None
|
| + self.netdev_id = []
|
| + self.uuid = None
|
| +
|
| + # Find a unique identifier for this VM
|
| + while True:
|
| + self.instance = (time.strftime("%Y%m%d-%H%M%S-") +
|
| + kvm_utils.generate_random_string(4))
|
| + if not glob.glob("/tmp/*%s" % self.instance):
|
| + break
|
|
|
| self.name = name
|
| self.params = params
|
| self.root_dir = root_dir
|
| self.address_cache = address_cache
|
|
|
| - # Find a unique identifier for this VM
|
| - while True:
|
| - self.instance = (time.strftime("%Y%m%d-%H%M%S-") +
|
| - kvm_utils.generate_random_string(4))
|
| - if not glob.glob("/tmp/*%s" % self.instance):
|
| - break
|
|
|
| -
|
| - def clone(self, name=None, params=None, root_dir=None, address_cache=None):
|
| + def clone(self, name=None, params=None, root_dir=None, address_cache=None,
|
| + copy_state=False):
|
| """
|
| Return a clone of the VM object with optionally modified parameters.
|
| The clone is initially not alive and needs to be started using create().
|
| @@ -138,6 +352,8 @@ class VM:
|
| @param params: Optional new VM creation parameters
|
| @param root_dir: Optional new base directory for relative filenames
|
| @param address_cache: A dict that maps MAC addresses to IP addresses
|
| + @param copy_state: If True, copy the original VM's state to the clone.
|
| + Mainly useful for make_qemu_command().
|
| """
|
| if name is None:
|
| name = self.name
|
| @@ -147,7 +363,11 @@ class VM:
|
| root_dir = self.root_dir
|
| if address_cache is None:
|
| address_cache = self.address_cache
|
| - return VM(name, params, root_dir, address_cache)
|
| + if copy_state:
|
| + state = self.__dict__.copy()
|
| + else:
|
| + state = None
|
| + return VM(name, params, root_dir, address_cache, state)
|
|
|
|
|
| def make_qemu_command(self, name=None, params=None, root_dir=None):
|
| @@ -225,36 +445,40 @@ class VM:
|
| def add_drive(help, filename, index=None, format=None, cache=None,
|
| werror=None, serial=None, snapshot=False, boot=False):
|
| cmd = " -drive file='%s'" % filename
|
| - if index is not None: cmd += ",index=%s" % index
|
| - if format: cmd += ",if=%s" % format
|
| - if cache: cmd += ",cache=%s" % cache
|
| - if werror: cmd += ",werror=%s" % werror
|
| - if serial: cmd += ",serial='%s'" % serial
|
| - if snapshot: cmd += ",snapshot=on"
|
| - if boot: cmd += ",boot=on"
|
| + if index is not None:
|
| + cmd += ",index=%s" % index
|
| + if format:
|
| + cmd += ",if=%s" % format
|
| + if cache:
|
| + cmd += ",cache=%s" % cache
|
| + if werror:
|
| + cmd += ",werror=%s" % werror
|
| + if serial:
|
| + cmd += ",serial='%s'" % serial
|
| + if snapshot:
|
| + cmd += ",snapshot=on"
|
| + if boot:
|
| + cmd += ",boot=on"
|
| return cmd
|
|
|
| def add_nic(help, vlan, model=None, mac=None, netdev_id=None,
|
| nic_extra_params=None):
|
| + if has_option(help, "netdev"):
|
| + netdev_vlan_str = ",netdev=%s" % netdev_id
|
| + else:
|
| + netdev_vlan_str = ",vlan=%d" % vlan
|
| if has_option(help, "device"):
|
| - if model == "virtio":
|
| - model="virtio-net-pci"
|
| if not model:
|
| - model= "rtl8139"
|
| - cmd = " -device %s" % model
|
| + model = "rtl8139"
|
| + elif model == "virtio":
|
| + model = "virtio-net-pci"
|
| + cmd = " -device %s" % model + netdev_vlan_str
|
| if mac:
|
| - cmd += ",mac=%s" % mac
|
| - if has_option(help, "netdev"):
|
| - cmd += ",netdev=%s" % netdev_id
|
| - else:
|
| - cmd += "vlan=%d," % vlan
|
| + cmd += ",mac='%s'" % mac
|
| if nic_extra_params:
|
| cmd += ",%s" % nic_extra_params
|
| else:
|
| - if has_option(help, "netdev"):
|
| - cmd = " -net nic,netdev=%s" % netdev_id
|
| - else:
|
| - cmd = " -net nic,vlan=%d" % vlan
|
| + cmd = " -net nic" + netdev_vlan_str
|
| if model:
|
| cmd += ",model=%s" % model
|
| if mac:
|
| @@ -263,11 +487,11 @@ class VM:
|
|
|
| def add_net(help, vlan, mode, ifname=None, script=None,
|
| downscript=None, tftp=None, bootfile=None, hostfwd=[],
|
| - netdev_id=None, vhost=False):
|
| + netdev_id=None, netdev_extra_params=None):
|
| if has_option(help, "netdev"):
|
| cmd = " -netdev %s,id=%s" % (mode, netdev_id)
|
| - if vhost:
|
| - cmd +=",vhost=on"
|
| + if netdev_extra_params:
|
| + cmd += ",%s" % netdev_extra_params
|
| else:
|
| cmd = " -net %s,vlan=%d" % (mode, vlan)
|
| if mode == "tap":
|
| @@ -347,9 +571,15 @@ class VM:
|
|
|
| # End of command line option wrappers
|
|
|
| - if name is None: name = self.name
|
| - if params is None: params = self.params
|
| - if root_dir is None: root_dir = self.root_dir
|
| + if name is None:
|
| + name = self.name
|
| + if params is None:
|
| + params = self.params
|
| + if root_dir is None:
|
| + root_dir = self.root_dir
|
| +
|
| + # Clone this VM using the new params
|
| + vm = self.clone(name, params, root_dir, copy_state=True)
|
|
|
| qemu_binary = kvm_utils.get_path(root_dir, params.get("qemu_binary",
|
| "qemu"))
|
| @@ -368,19 +598,19 @@ class VM:
|
| # Add the VM's name
|
| qemu_cmd += add_name(help, name)
|
| # Add monitors
|
| - for monitor_name in kvm_utils.get_sub_dict_names(params, "monitors"):
|
| - monitor_params = kvm_utils.get_sub_dict(params, monitor_name)
|
| - monitor_filename = self.get_monitor_filename(monitor_name)
|
| + for monitor_name in params.objects("monitors"):
|
| + monitor_params = params.object_params(monitor_name)
|
| + monitor_filename = vm.get_monitor_filename(monitor_name)
|
| if monitor_params.get("monitor_type") == "qmp":
|
| qemu_cmd += add_qmp_monitor(help, monitor_filename)
|
| else:
|
| qemu_cmd += add_human_monitor(help, monitor_filename)
|
|
|
| # Add serial console redirection
|
| - qemu_cmd += add_serial(help, self.get_serial_console_filename())
|
| + qemu_cmd += add_serial(help, vm.get_serial_console_filename())
|
|
|
| - for image_name in kvm_utils.get_sub_dict_names(params, "images"):
|
| - image_params = kvm_utils.get_sub_dict(params, image_name)
|
| + for image_name in params.objects("images"):
|
| + image_params = params.object_params(image_name)
|
| if image_params.get("boot_drive") == "no":
|
| continue
|
| qemu_cmd += add_drive(help,
|
| @@ -394,20 +624,23 @@ class VM:
|
| image_params.get("image_boot") == "yes")
|
|
|
| redirs = []
|
| - for redir_name in kvm_utils.get_sub_dict_names(params, "redirs"):
|
| - redir_params = kvm_utils.get_sub_dict(params, redir_name)
|
| + for redir_name in params.objects("redirs"):
|
| + redir_params = params.object_params(redir_name)
|
| guest_port = int(redir_params.get("guest_port"))
|
| - host_port = self.redirs.get(guest_port)
|
| + host_port = vm.redirs.get(guest_port)
|
| redirs += [(host_port, guest_port)]
|
|
|
| vlan = 0
|
| - for nic_name in kvm_utils.get_sub_dict_names(params, "nics"):
|
| - nic_params = kvm_utils.get_sub_dict(params, nic_name)
|
| + for nic_name in params.objects("nics"):
|
| + nic_params = params.object_params(nic_name)
|
| + try:
|
| + netdev_id = vm.netdev_id[vlan]
|
| + except IndexError:
|
| + netdev_id = None
|
| # Handle the '-net nic' part
|
| - mac = self.get_mac_address(vlan)
|
| + mac = vm.get_mac_address(vlan)
|
| qemu_cmd += add_nic(help, vlan, nic_params.get("nic_model"), mac,
|
| - self.netdev_id[vlan],
|
| - nic_params.get("nic_extra_params"))
|
| + netdev_id, nic_params.get("nic_extra_params"))
|
| # Handle the '-net tap' or '-net user' part
|
| script = nic_params.get("nic_script")
|
| downscript = nic_params.get("nic_downscript")
|
| @@ -419,11 +652,10 @@ class VM:
|
| if tftp:
|
| tftp = kvm_utils.get_path(root_dir, tftp)
|
| qemu_cmd += add_net(help, vlan, nic_params.get("nic_mode", "user"),
|
| - self.get_ifname(vlan),
|
| + vm.get_ifname(vlan),
|
| script, downscript, tftp,
|
| - nic_params.get("bootp"), redirs,
|
| - self.netdev_id[vlan],
|
| - nic_params.get("vhost")=="yes")
|
| + nic_params.get("bootp"), redirs, netdev_id,
|
| + nic_params.get("netdev_extra_params"))
|
| # Proceed to next NIC
|
| vlan += 1
|
|
|
| @@ -435,9 +667,8 @@ class VM:
|
| if smp:
|
| qemu_cmd += add_smp(help, smp)
|
|
|
| - cdroms = kvm_utils.get_sub_dict_names(params, "cdroms")
|
| - for cdrom in cdroms:
|
| - cdrom_params = kvm_utils.get_sub_dict(params, cdrom)
|
| + for cdrom in params.objects("cdroms"):
|
| + cdrom_params = params.object_params(cdrom)
|
| iso = cdrom_params.get("cdrom")
|
| if iso:
|
| qemu_cmd += add_cdrom(help, kvm_utils.get_path(root_dir, iso),
|
| @@ -477,27 +708,27 @@ class VM:
|
| qemu_cmd += add_tcp_redir(help, host_port, guest_port)
|
|
|
| if params.get("display") == "vnc":
|
| - qemu_cmd += add_vnc(help, self.vnc_port)
|
| + qemu_cmd += add_vnc(help, vm.vnc_port)
|
| elif params.get("display") == "sdl":
|
| qemu_cmd += add_sdl(help)
|
| elif params.get("display") == "nographic":
|
| qemu_cmd += add_nographic(help)
|
|
|
| if params.get("uuid") == "random":
|
| - qemu_cmd += add_uuid(help, self.uuid)
|
| + qemu_cmd += add_uuid(help, vm.uuid)
|
| elif params.get("uuid"):
|
| qemu_cmd += add_uuid(help, params.get("uuid"))
|
|
|
| if params.get("testdev") == "yes":
|
| - qemu_cmd += add_testdev(help, self.get_testlog_filename())
|
| + qemu_cmd += add_testdev(help, vm.get_testlog_filename())
|
|
|
| if params.get("disable_hpet") == "yes":
|
| qemu_cmd += add_no_hpet(help)
|
|
|
| # If the PCI assignment step went OK, add each one of the PCI assigned
|
| # devices to the qemu command line.
|
| - if self.pci_assignable:
|
| - for pci_id in self.pa_pci_ids:
|
| + if vm.pci_assignable:
|
| + for pci_id in vm.pa_pci_ids:
|
| qemu_cmd += add_pcidevice(help, pci_id)
|
|
|
| extra_params = params.get("extra_params")
|
| @@ -507,8 +738,9 @@ class VM:
|
| return qemu_cmd
|
|
|
|
|
| + @error.context_aware
|
| def create(self, name=None, params=None, root_dir=None, timeout=5.0,
|
| - migration_mode=None, migration_exec_cmd=None, mac_source=None):
|
| + migration_mode=None, mac_source=None):
|
| """
|
| Start the VM by running a qemu command.
|
| All parameters are optional. If name, params or root_dir are not
|
| @@ -523,8 +755,19 @@ class VM:
|
| (e.g. 'gzip -c -d filename') if migration_mode is 'exec'
|
| @param mac_source: A VM object from which to copy MAC addresses. If not
|
| specified, new addresses will be generated.
|
| +
|
| + @raise VMCreateError: If qemu terminates unexpectedly
|
| + @raise VMKVMInitError: If KVM initialization fails
|
| + @raise VMHugePageError: If hugepage initialization fails
|
| + @raise VMImageMissingError: If a CD image is missing
|
| + @raise VMHashMismatchError: If a CD image hash has doesn't match the
|
| + expected hash
|
| + @raise VMBadPATypeError: If an unsupported PCI assignment type is
|
| + requested
|
| + @raise VMPAError: If no PCI assignable devices could be assigned
|
| """
|
| - self.destroy()
|
| + error.context("creating '%s'" % self.name)
|
| + self.destroy(free_mac_addresses=False)
|
|
|
| if name is not None:
|
| self.name = name
|
| @@ -536,38 +779,38 @@ class VM:
|
| params = self.params
|
| root_dir = self.root_dir
|
|
|
| - # Verify the md5sum of the ISO image
|
| - iso = params.get("cdrom")
|
| - if iso:
|
| - iso = kvm_utils.get_path(root_dir, iso)
|
| - if not os.path.exists(iso):
|
| - logging.error("ISO file not found: %s" % iso)
|
| - return False
|
| - compare = False
|
| - if params.get("md5sum_1m"):
|
| - logging.debug("Comparing expected MD5 sum with MD5 sum of "
|
| - "first MB of ISO file...")
|
| - actual_hash = utils.hash_file(iso, 1048576, method="md5")
|
| - expected_hash = params.get("md5sum_1m")
|
| - compare = True
|
| - elif params.get("md5sum"):
|
| - logging.debug("Comparing expected MD5 sum with MD5 sum of ISO "
|
| - "file...")
|
| - actual_hash = utils.hash_file(iso, method="md5")
|
| - expected_hash = params.get("md5sum")
|
| - compare = True
|
| - elif params.get("sha1sum"):
|
| - logging.debug("Comparing expected SHA1 sum with SHA1 sum of "
|
| - "ISO file...")
|
| - actual_hash = utils.hash_file(iso, method="sha1")
|
| - expected_hash = params.get("sha1sum")
|
| - compare = True
|
| - if compare:
|
| - if actual_hash == expected_hash:
|
| - logging.debug("Hashes match")
|
| - else:
|
| - logging.error("Actual hash differs from expected one")
|
| - return False
|
| + # Verify the md5sum of the ISO images
|
| + for cdrom in params.objects("cdroms"):
|
| + cdrom_params = params.object_params(cdrom)
|
| + iso = cdrom_params.get("cdrom")
|
| + if iso:
|
| + iso = kvm_utils.get_path(root_dir, iso)
|
| + if not os.path.exists(iso):
|
| + raise VMImageMissingError(iso)
|
| + compare = False
|
| + if cdrom_params.get("md5sum_1m"):
|
| + logging.debug("Comparing expected MD5 sum with MD5 sum of "
|
| + "first MB of ISO file...")
|
| + actual_hash = utils.hash_file(iso, 1048576, method="md5")
|
| + expected_hash = cdrom_params.get("md5sum_1m")
|
| + compare = True
|
| + elif cdrom_params.get("md5sum"):
|
| + logging.debug("Comparing expected MD5 sum with MD5 sum of "
|
| + "ISO file...")
|
| + actual_hash = utils.hash_file(iso, method="md5")
|
| + expected_hash = cdrom_params.get("md5sum")
|
| + compare = True
|
| + elif cdrom_params.get("sha1sum"):
|
| + logging.debug("Comparing expected SHA1 sum with SHA1 sum "
|
| + "of ISO file...")
|
| + actual_hash = utils.hash_file(iso, method="sha1")
|
| + expected_hash = cdrom_params.get("sha1sum")
|
| + compare = True
|
| + if compare:
|
| + if actual_hash == expected_hash:
|
| + logging.debug("Hashes match")
|
| + else:
|
| + raise VMHashMismatchError(actual_hash, expected_hash)
|
|
|
| # Make sure the following code is not executed by more than one thread
|
| # at the same time
|
| @@ -576,15 +819,17 @@ class VM:
|
|
|
| try:
|
| # Handle port redirections
|
| - redir_names = kvm_utils.get_sub_dict_names(params, "redirs")
|
| + redir_names = params.objects("redirs")
|
| host_ports = kvm_utils.find_free_ports(5000, 6000, len(redir_names))
|
| self.redirs = {}
|
| for i in range(len(redir_names)):
|
| - redir_params = kvm_utils.get_sub_dict(params, redir_names[i])
|
| + redir_params = params.object_params(redir_names[i])
|
| guest_port = int(redir_params.get("guest_port"))
|
| self.redirs[guest_port] = host_ports[i]
|
|
|
| - for nic in kvm_utils.get_sub_dict_names(params, "nics"):
|
| + # Generate netdev IDs for all NICs
|
| + self.netdev_id = []
|
| + for nic in params.objects("nics"):
|
| self.netdev_id.append(kvm_utils.generate_random_id())
|
|
|
| # Find available VNC port, if needed
|
| @@ -598,18 +843,24 @@ class VM:
|
| f.close()
|
|
|
| # Generate or copy MAC addresses for all NICs
|
| - num_nics = len(kvm_utils.get_sub_dict_names(params, "nics"))
|
| + num_nics = len(params.objects("nics"))
|
| for vlan in range(num_nics):
|
| - mac = mac_source and mac_source.get_mac_address(vlan)
|
| - if mac:
|
| + nic_name = params.objects("nics")[vlan]
|
| + nic_params = params.object_params(nic_name)
|
| + if nic_params.get("nic_mac", None):
|
| + mac = nic_params.get("nic_mac")
|
| kvm_utils.set_mac_address(self.instance, vlan, mac)
|
| else:
|
| - kvm_utils.generate_mac_address(self.instance, vlan)
|
| + mac = mac_source and mac_source.get_mac_address(vlan)
|
| + if mac:
|
| + kvm_utils.set_mac_address(self.instance, vlan, mac)
|
| + else:
|
| + kvm_utils.generate_mac_address(self.instance, vlan)
|
|
|
| # Assign a PCI assignable device
|
| self.pci_assignable = None
|
| pa_type = params.get("pci_assignable")
|
| - if pa_type in ["vf", "pf", "mixed"]:
|
| + if pa_type and pa_type != "no":
|
| pa_devices_requested = params.get("devices_requested")
|
|
|
| # Virtual Functions (VF) assignable devices
|
| @@ -633,6 +884,8 @@ class VM:
|
| driver_option=params.get("driver_option"),
|
| names=params.get("device_names"),
|
| devices_requested=pa_devices_requested)
|
| + else:
|
| + raise VMBadPATypeError(pa_type)
|
|
|
| self.pa_pci_ids = self.pci_assignable.request_devs()
|
|
|
| @@ -640,14 +893,7 @@ class VM:
|
| logging.debug("Successfuly assigned devices: %s",
|
| self.pa_pci_ids)
|
| else:
|
| - logging.error("No PCI assignable devices were assigned "
|
| - "and 'pci_assignable' is defined to %s "
|
| - "on your config file. Aborting VM creation.",
|
| - pa_type)
|
| - return False
|
| -
|
| - elif pa_type and pa_type != "no":
|
| - logging.warn("Unsupported pci_assignable type: %s", pa_type)
|
| + raise VMPAError(pa_type)
|
|
|
| # Make qemu command
|
| qemu_command = self.make_qemu_command()
|
| @@ -660,27 +906,26 @@ class VM:
|
| self.migration_file = "/tmp/migration-unix-%s" % self.instance
|
| qemu_command += " -incoming unix:%s" % self.migration_file
|
| elif migration_mode == "exec":
|
| - qemu_command += ' -incoming "exec:%s"' % migration_exec_cmd
|
| + self.migration_port = kvm_utils.find_free_port(5200, 6000)
|
| + qemu_command += (' -incoming "exec:nc -l %s"' %
|
| + self.migration_port)
|
|
|
| - logging.debug("Running qemu command:\n%s", qemu_command)
|
| + logging.info("Running qemu command:\n%s", qemu_command)
|
| self.process = kvm_subprocess.run_bg(qemu_command, None,
|
| - logging.debug, "(qemu) ")
|
| + logging.info, "(qemu) ")
|
|
|
| # Make sure the process was started successfully
|
| if not self.process.is_alive():
|
| - logging.error("VM could not be created; "
|
| - "qemu command failed:\n%s" % qemu_command)
|
| - logging.error("Status: %s" % self.process.get_status())
|
| - logging.error("Output:" + kvm_utils.format_str_for_message(
|
| - self.process.get_output()))
|
| + e = VMCreateError(qemu_command,
|
| + self.process.get_status(),
|
| + self.process.get_output())
|
| self.destroy()
|
| - return False
|
| + raise e
|
|
|
| # Establish monitor connections
|
| self.monitors = []
|
| - for monitor_name in kvm_utils.get_sub_dict_names(params,
|
| - "monitors"):
|
| - monitor_params = kvm_utils.get_sub_dict(params, monitor_name)
|
| + for monitor_name in params.objects("monitors"):
|
| + monitor_params = params.object_params(monitor_name)
|
| # Wait for monitor connection to succeed
|
| end_time = time.time() + timeout
|
| while time.time() < end_time:
|
| @@ -695,17 +940,14 @@ class VM:
|
| monitor = kvm_monitor.HumanMonitor(
|
| monitor_name,
|
| self.get_monitor_filename(monitor_name))
|
| + monitor.verify_responsive()
|
| + break
|
| except kvm_monitor.MonitorError, e:
|
| logging.warn(e)
|
| - else:
|
| - if monitor.is_responsive():
|
| - break
|
| - time.sleep(1)
|
| + time.sleep(1)
|
| else:
|
| - logging.error("Could not connect to monitor '%s'" %
|
| - monitor_name)
|
| self.destroy()
|
| - return False
|
| + raise e
|
| # Add this monitor to the list
|
| self.monitors += [monitor]
|
|
|
| @@ -714,39 +956,31 @@ class VM:
|
| output = self.process.get_output()
|
|
|
| if re.search("Could not initialize KVM", output, re.IGNORECASE):
|
| - logging.error("Could not initialize KVM; "
|
| - "qemu command:\n%s" % qemu_command)
|
| - logging.error("Output:" + kvm_utils.format_str_for_message(
|
| - self.process.get_output()))
|
| + e = VMKVMInitError(qemu_command, self.process.get_output())
|
| self.destroy()
|
| - return False
|
| + raise e
|
|
|
| if "alloc_mem_area" in output:
|
| - logging.error("Could not allocate hugepage memory; "
|
| - "qemu command:\n%s" % qemu_command)
|
| - logging.error("Output:" + kvm_utils.format_str_for_message(
|
| - self.process.get_output()))
|
| + e = VMHugePageError(qemu_command, self.process.get_output())
|
| self.destroy()
|
| - return False
|
| + raise e
|
|
|
| logging.debug("VM appears to be alive with PID %s", self.get_pid())
|
|
|
| # Establish a session with the serial console -- requires a version
|
| # of netcat that supports -U
|
| - self.serial_console = kvm_subprocess.kvm_shell_session(
|
| + self.serial_console = kvm_subprocess.ShellSession(
|
| "nc -U %s" % self.get_serial_console_filename(),
|
| auto_close=False,
|
| output_func=kvm_utils.log_line,
|
| output_params=("serial-%s.log" % name,))
|
|
|
| - return True
|
| -
|
| finally:
|
| fcntl.lockf(lockfile, fcntl.LOCK_UN)
|
| lockfile.close()
|
|
|
|
|
| - def destroy(self, gracefully=True):
|
| + def destroy(self, gracefully=True, free_mac_addresses=True):
|
| """
|
| Destroy the VM.
|
|
|
| @@ -754,14 +988,15 @@ class VM:
|
| command. Then, attempt to destroy the VM via the monitor with a 'quit'
|
| command. If that fails, send SIGKILL to the qemu process.
|
|
|
| - @param gracefully: Whether an attempt will be made to end the VM
|
| + @param gracefully: If True, an attempt will be made to end the VM
|
| using a shell command before trying to end the qemu process
|
| with a 'quit' or a kill signal.
|
| + @param free_mac_addresses: If True, the MAC addresses used by the VM
|
| + will be freed.
|
| """
|
| try:
|
| # Is it already dead?
|
| if self.is_dead():
|
| - logging.debug("VM is already down")
|
| return
|
|
|
| logging.debug("Destroying VM with PID %s...", self.get_pid())
|
| @@ -769,15 +1004,18 @@ class VM:
|
| if gracefully and self.params.get("shutdown_command"):
|
| # Try to destroy with shell command
|
| logging.debug("Trying to shutdown VM with shell command...")
|
| - session = self.remote_login()
|
| - if session:
|
| + try:
|
| + session = self.login()
|
| + except (kvm_utils.LoginError, VMError), e:
|
| + logging.debug(e)
|
| + else:
|
| try:
|
| # Send the shutdown command
|
| session.sendline(self.params.get("shutdown_command"))
|
| logging.debug("Shutdown command sent; waiting for VM "
|
| "to go down...")
|
| if kvm_utils.wait_for(self.is_dead, 60, 1, 1):
|
| - logging.debug("VM is down, freeing mac address.")
|
| + logging.debug("VM is down")
|
| return
|
| finally:
|
| session.close()
|
| @@ -804,7 +1042,7 @@ class VM:
|
| logging.debug("VM is down")
|
| return
|
|
|
| - logging.error("Process %s is a zombie!" % self.process.get_pid())
|
| + logging.error("Process %s is a zombie!", self.process.get_pid())
|
|
|
| finally:
|
| self.monitors = []
|
| @@ -826,9 +1064,10 @@ class VM:
|
| os.unlink(self.migration_file)
|
| except OSError:
|
| pass
|
| - num_nics = len(kvm_utils.get_sub_dict_names(self.params, "nics"))
|
| - for vlan in range(num_nics):
|
| - self.free_mac_address(vlan)
|
| + if free_mac_addresses:
|
| + num_nics = len(self.params.objects("nics"))
|
| + for vlan in range(num_nics):
|
| + self.free_mac_address(vlan)
|
|
|
|
|
| @property
|
| @@ -846,15 +1085,26 @@ class VM:
|
| return self.monitors[0]
|
|
|
|
|
| + def verify_alive(self):
|
| + """
|
| + Make sure the VM is alive and that the main monitor is responsive.
|
| +
|
| + @raise VMDeadError: If the VM is dead
|
| + @raise: Various monitor exceptions if the monitor is unresponsive
|
| + """
|
| + if self.is_dead():
|
| + raise VMDeadError(self.process.get_status(),
|
| + self.process.get_output())
|
| + if self.monitors:
|
| + self.monitor.verify_responsive()
|
| +
|
| +
|
| def is_alive(self):
|
| """
|
| Return True if the VM is alive and its monitor is responsive.
|
| """
|
| - # Check if the process is running
|
| - if self.is_dead():
|
| - return False
|
| - # Try sending a monitor command
|
| - return bool(self.monitor) and self.monitor.is_responsive()
|
| + return not self.is_dead() and (not self.monitors or
|
| + self.monitor.is_responsive())
|
|
|
|
|
| def is_dead(self):
|
| @@ -885,7 +1135,7 @@ class VM:
|
| params).
|
| """
|
| return [self.get_monitor_filename(m) for m in
|
| - kvm_utils.get_sub_dict_names(self.params, "monitors")]
|
| + self.params.objects("monitors")]
|
|
|
|
|
| def get_serial_console_filename(self):
|
| @@ -910,28 +1160,26 @@ class VM:
|
| address of its own). Otherwise return the NIC's IP address.
|
|
|
| @param index: Index of the NIC whose address is requested.
|
| + @raise VMMACAddressMissingError: If no MAC address is defined for the
|
| + requested NIC
|
| + @raise VMIPAddressMissingError: If no IP address is found for the the
|
| + NIC's MAC address
|
| + @raise VMAddressVerificationError: If the MAC-IP address mapping cannot
|
| + be verified (using arping)
|
| """
|
| - nics = kvm_utils.get_sub_dict_names(self.params, "nics")
|
| + nics = self.params.objects("nics")
|
| nic_name = nics[index]
|
| - nic_params = kvm_utils.get_sub_dict(self.params, nic_name)
|
| + nic_params = self.params.object_params(nic_name)
|
| if nic_params.get("nic_mode") == "tap":
|
| - mac = self.get_mac_address(index)
|
| - if not mac:
|
| - logging.debug("MAC address unavailable")
|
| - return None
|
| - mac = mac.lower()
|
| + mac = self.get_mac_address(index).lower()
|
| # Get the IP address from the cache
|
| ip = self.address_cache.get(mac)
|
| if not ip:
|
| - logging.debug("Could not find IP address for MAC address: %s" %
|
| - mac)
|
| - return None
|
| + raise VMIPAddressMissingError(mac)
|
| # Make sure the IP address is assigned to this guest
|
| macs = [self.get_mac_address(i) for i in range(len(nics))]
|
| if not kvm_utils.verify_ip_address_ownership(ip, macs):
|
| - logging.debug("Could not verify MAC-IP address mapping: "
|
| - "%s ---> %s" % (mac, ip))
|
| - return None
|
| + raise VMAddressVerificationError(mac, ip)
|
| return ip
|
| else:
|
| return "localhost"
|
| @@ -945,16 +1193,18 @@ class VM:
|
| @param nic_index: Index of the NIC.
|
| @return: If port redirection is used, return the host port redirected
|
| to guest port port. Otherwise return port.
|
| + @raise VMPortNotRedirectedError: If an unredirected port is requested
|
| + in user mode
|
| """
|
| - nic_name = kvm_utils.get_sub_dict_names(self.params, "nics")[nic_index]
|
| - nic_params = kvm_utils.get_sub_dict(self.params, nic_name)
|
| + nic_name = self.params.objects("nics")[nic_index]
|
| + nic_params = self.params.object_params(nic_name)
|
| if nic_params.get("nic_mode") == "tap":
|
| return port
|
| else:
|
| - if not self.redirs.has_key(port):
|
| - logging.warn("Warning: guest port %s requested but not "
|
| - "redirected" % port)
|
| - return self.redirs.get(port)
|
| + try:
|
| + return self.redirs[port]
|
| + except KeyError:
|
| + raise VMPortNotRedirectedError(port)
|
|
|
|
|
| def get_ifname(self, nic_index=0):
|
| @@ -963,9 +1213,9 @@ class VM:
|
|
|
| @param nic_index: Index of the NIC
|
| """
|
| - nics = kvm_utils.get_sub_dict_names(self.params, "nics")
|
| + nics = self.params.objects("nics")
|
| nic_name = nics[nic_index]
|
| - nic_params = kvm_utils.get_sub_dict(self.params, nic_name)
|
| + nic_params = self.params.object_params(nic_name)
|
| if nic_params.get("nic_ifname"):
|
| return nic_params.get("nic_ifname")
|
| else:
|
| @@ -977,8 +1227,13 @@ class VM:
|
| Return the MAC address of a NIC.
|
|
|
| @param nic_index: Index of the NIC
|
| + @raise VMMACAddressMissingError: If no MAC address is defined for the
|
| + requested NIC
|
| """
|
| - return kvm_utils.get_mac_address(self.instance, nic_index)
|
| + mac = kvm_utils.get_mac_address(self.instance, nic_index)
|
| + if not mac:
|
| + raise VMMACAddressMissingError(nic_index)
|
| + return mac
|
|
|
|
|
| def free_mac_address(self, nic_index=0):
|
| @@ -1031,7 +1286,8 @@ class VM:
|
| return shm * 4.0 / 1024
|
|
|
|
|
| - def remote_login(self, nic_index=0, timeout=10):
|
| + @error.context_aware
|
| + def login(self, nic_index=0, timeout=10):
|
| """
|
| Log into the guest via SSH/Telnet/Netcat.
|
| If timeout expires while waiting for output from the guest (e.g. a
|
| @@ -1040,8 +1296,9 @@ class VM:
|
| @param nic_index: The index of the NIC to connect to.
|
| @param timeout: Time (seconds) before giving up logging into the
|
| guest.
|
| - @return: kvm_spawn object on success and None on failure.
|
| + @return: A ShellSession object.
|
| """
|
| + error.context("logging into '%s'" % self.name)
|
| username = self.params.get("username", "")
|
| password = self.params.get("password", "")
|
| prompt = self.params.get("shell_prompt", "[\#\$]")
|
| @@ -1051,87 +1308,98 @@ class VM:
|
| port = self.get_port(int(self.params.get("shell_port")))
|
| log_filename = ("session-%s-%s.log" %
|
| (self.name, kvm_utils.generate_random_string(4)))
|
| -
|
| - if not address or not port:
|
| - logging.debug("IP address or port unavailable")
|
| - return None
|
| -
|
| session = kvm_utils.remote_login(client, address, port, username,
|
| password, prompt, linesep,
|
| log_filename, timeout)
|
| -
|
| - if session:
|
| - session.set_status_test_command(self.params.get("status_test_"
|
| - "command", ""))
|
| + session.set_status_test_command(self.params.get("status_test_command",
|
| + ""))
|
| return session
|
|
|
|
|
| - def copy_files_to(self, local_path, remote_path, nic_index=0, timeout=600):
|
| + def remote_login(self, nic_index=0, timeout=10):
|
| + """
|
| + Alias for login() for backward compatibility.
|
| + """
|
| + return self.login(nic_index, timeout)
|
| +
|
| +
|
| + def wait_for_login(self, nic_index=0, timeout=240, internal_timeout=10):
|
| + """
|
| + Make multiple attempts to log into the guest via SSH/Telnet/Netcat.
|
| +
|
| + @param nic_index: The index of the NIC to connect to.
|
| + @param timeout: Time (seconds) to keep trying to log in.
|
| + @param internal_timeout: Timeout to pass to login().
|
| + @return: A ShellSession object.
|
| + """
|
| + logging.debug("Attempting to log into '%s' (timeout %ds)", self.name,
|
| + timeout)
|
| + end_time = time.time() + timeout
|
| + while time.time() < end_time:
|
| + try:
|
| + return self.login(nic_index, internal_timeout)
|
| + except (kvm_utils.LoginError, VMError), e:
|
| + logging.debug(e)
|
| + time.sleep(2)
|
| + # Timeout expired; try one more time but don't catch exceptions
|
| + return self.login(nic_index, internal_timeout)
|
| +
|
| +
|
| + @error.context_aware
|
| + def copy_files_to(self, host_path, guest_path, nic_index=0, verbose=False,
|
| + timeout=600):
|
| """
|
| - Transfer files to the guest.
|
| + Transfer files to the remote host(guest).
|
|
|
| - @param local_path: Host path
|
| - @param remote_path: Guest path
|
| + @param host_path: Host path
|
| + @param guest_path: Guest path
|
| @param nic_index: The index of the NIC to connect to.
|
| + @param verbose: If True, log some stats using logging.debug (RSS only)
|
| @param timeout: Time (seconds) before giving up on doing the remote
|
| copy.
|
| """
|
| + error.context("sending file(s) to '%s'" % self.name)
|
| username = self.params.get("username", "")
|
| password = self.params.get("password", "")
|
| client = self.params.get("file_transfer_client")
|
| address = self.get_address(nic_index)
|
| port = self.get_port(int(self.params.get("file_transfer_port")))
|
| -
|
| - if not address or not port:
|
| - logging.debug("IP address or port unavailable")
|
| - return None
|
| -
|
| - if client == "scp":
|
| - log_filename = ("scp-%s-%s.log" %
|
| - (self.name, kvm_utils.generate_random_string(4)))
|
| - return kvm_utils.scp_to_remote(address, port, username, password,
|
| - local_path, remote_path,
|
| - log_filename, timeout)
|
| - elif client == "rss":
|
| - c = rss_file_transfer.FileUploadClient(address, port)
|
| - c.upload(local_path, remote_path, timeout)
|
| - c.close()
|
| - return True
|
| + log_filename = ("transfer-%s-to-%s-%s.log" %
|
| + (self.name, address,
|
| + kvm_utils.generate_random_string(4)))
|
| + kvm_utils.copy_files_to(address, client, username, password, port,
|
| + host_path, guest_path, log_filename, verbose,
|
| + timeout)
|
|
|
|
|
| - def copy_files_from(self, remote_path, local_path, nic_index=0, timeout=600):
|
| + @error.context_aware
|
| + def copy_files_from(self, guest_path, host_path, nic_index=0,
|
| + verbose=False, timeout=600):
|
| """
|
| Transfer files from the guest.
|
|
|
| - @param local_path: Guest path
|
| - @param remote_path: Host path
|
| + @param host_path: Guest path
|
| + @param guest_path: Host path
|
| @param nic_index: The index of the NIC to connect to.
|
| + @param verbose: If True, log some stats using logging.debug (RSS only)
|
| @param timeout: Time (seconds) before giving up on doing the remote
|
| copy.
|
| """
|
| + error.context("receiving file(s) from '%s'" % self.name)
|
| username = self.params.get("username", "")
|
| password = self.params.get("password", "")
|
| client = self.params.get("file_transfer_client")
|
| address = self.get_address(nic_index)
|
| port = self.get_port(int(self.params.get("file_transfer_port")))
|
| -
|
| - if not address or not port:
|
| - logging.debug("IP address or port unavailable")
|
| - return None
|
| -
|
| - if client == "scp":
|
| - log_filename = ("scp-%s-%s.log" %
|
| - (self.name, kvm_utils.generate_random_string(4)))
|
| - return kvm_utils.scp_from_remote(address, port, username, password,
|
| - remote_path, local_path,
|
| - log_filename, timeout)
|
| - elif client == "rss":
|
| - c = rss_file_transfer.FileDownloadClient(address, port)
|
| - c.download(remote_path, local_path, timeout)
|
| - c.close()
|
| - return True
|
| + log_filename = ("transfer-%s-from-%s-%s.log" %
|
| + (self.name, address,
|
| + kvm_utils.generate_random_string(4)))
|
| + kvm_utils.copy_files_from(address, client, username, password, port,
|
| + guest_path, host_path, log_filename,
|
| + verbose, timeout)
|
|
|
|
|
| + @error.context_aware
|
| def serial_login(self, timeout=10):
|
| """
|
| Log into the guest via the serial console.
|
| @@ -1139,26 +1407,247 @@ class VM:
|
| password prompt or a shell prompt) -- fail.
|
|
|
| @param timeout: Time (seconds) before giving up logging into the guest.
|
| - @return: kvm_spawn object on success and None on failure.
|
| + @return: ShellSession object on success and None on failure.
|
| """
|
| + error.context("logging into '%s' via serial console" % self.name)
|
| username = self.params.get("username", "")
|
| password = self.params.get("password", "")
|
| prompt = self.params.get("shell_prompt", "[\#\$]")
|
| linesep = eval("'%s'" % self.params.get("shell_linesep", r"\n"))
|
| status_test_command = self.params.get("status_test_command", "")
|
|
|
| - if self.serial_console:
|
| - self.serial_console.set_linesep(linesep)
|
| - self.serial_console.set_status_test_command(status_test_command)
|
| - else:
|
| - return None
|
| + self.serial_console.set_linesep(linesep)
|
| + self.serial_console.set_status_test_command(status_test_command)
|
|
|
| - # Make sure we get a login prompt
|
| + # Try to get a login prompt
|
| self.serial_console.sendline()
|
|
|
| - if kvm_utils._remote_login(self.serial_console, username, password,
|
| - prompt, timeout):
|
| - return self.serial_console
|
| + kvm_utils._remote_login(self.serial_console, username, password,
|
| + prompt, timeout)
|
| + return self.serial_console
|
| +
|
| +
|
| + def wait_for_serial_login(self, timeout=240, internal_timeout=10):
|
| + """
|
| + Make multiple attempts to log into the guest via serial console.
|
| +
|
| + @param timeout: Time (seconds) to keep trying to log in.
|
| + @param internal_timeout: Timeout to pass to serial_login().
|
| + @return: A ShellSession object.
|
| + """
|
| + logging.debug("Attempting to log into '%s' via serial console "
|
| + "(timeout %ds)", self.name, timeout)
|
| + end_time = time.time() + timeout
|
| + while time.time() < end_time:
|
| + try:
|
| + return self.serial_login(internal_timeout)
|
| + except kvm_utils.LoginError, e:
|
| + logging.debug(e)
|
| + time.sleep(2)
|
| + # Timeout expired; try one more time but don't catch exceptions
|
| + return self.serial_login(internal_timeout)
|
| +
|
| +
|
| + @error.context_aware
|
| + def migrate(self, timeout=3600, protocol="tcp", cancel_delay=None,
|
| + offline=False, stable_check=False, clean=True,
|
| + save_path="/tmp", dest_host="localhost", remote_port=None):
|
| + """
|
| + Migrate the VM.
|
| +
|
| + If the migration is local, the VM object's state is switched with that
|
| + of the destination VM. Otherwise, the state is switched with that of
|
| + a dead VM (returned by self.clone()).
|
| +
|
| + @param timeout: Time to wait for migration to complete.
|
| + @param protocol: Migration protocol ('tcp', 'unix' or 'exec').
|
| + @param cancel_delay: If provided, specifies a time duration after which
|
| + migration will be canceled. Used for testing migrate_cancel.
|
| + @param offline: If True, pause the source VM before migration.
|
| + @param stable_check: If True, compare the VM's state after migration to
|
| + its state before migration and raise an exception if they
|
| + differ.
|
| + @param clean: If True, delete the saved state files (relevant only if
|
| + stable_check is also True).
|
| + @save_path: The path for state files.
|
| + @param dest_host: Destination host (defaults to 'localhost').
|
| + @param remote_port: Port to use for remote migration.
|
| + """
|
| + error.base_context("migrating '%s'" % self.name)
|
| +
|
| + def mig_finished():
|
| + o = self.monitor.info("migrate")
|
| + if isinstance(o, str):
|
| + return "status: active" not in o
|
| + else:
|
| + return o.get("status") != "active"
|
| +
|
| + def mig_succeeded():
|
| + o = self.monitor.info("migrate")
|
| + if isinstance(o, str):
|
| + return "status: completed" in o
|
| + else:
|
| + return o.get("status") == "completed"
|
| +
|
| + def mig_failed():
|
| + o = self.monitor.info("migrate")
|
| + if isinstance(o, str):
|
| + return "status: failed" in o
|
| + else:
|
| + return o.get("status") == "failed"
|
| +
|
| + def mig_cancelled():
|
| + o = self.monitor.info("migrate")
|
| + if isinstance(o, str):
|
| + return ("Migration status: cancelled" in o or
|
| + "Migration status: canceled" in o)
|
| + else:
|
| + return (o.get("status") == "cancelled" or
|
| + o.get("status") == "canceled")
|
| +
|
| + def wait_for_migration():
|
| + if not kvm_utils.wait_for(mig_finished, timeout, 2, 2,
|
| + "Waiting for migration to complete"):
|
| + raise VMMigrateTimeoutError("Timeout expired while waiting "
|
| + "for migration to finish")
|
| +
|
| + local = dest_host == "localhost"
|
| +
|
| + clone = self.clone()
|
| + if local:
|
| + error.context("creating destination VM")
|
| + if stable_check:
|
| + # Pause the dest vm after creation
|
| + extra_params = clone.params.get("extra_params", "") + " -S"
|
| + clone.params["extra_params"] = extra_params
|
| + clone.create(migration_mode=protocol, mac_source=self)
|
| + error.context()
|
| +
|
| + try:
|
| + if protocol == "tcp":
|
| + if local:
|
| + uri = "tcp:localhost:%d" % clone.migration_port
|
| + else:
|
| + uri = "tcp:%s:%d" % (dest_host, remote_port)
|
| + elif protocol == "unix":
|
| + uri = "unix:%s" % clone.migration_file
|
| + elif protocol == "exec":
|
| + uri = '"exec:nc localhost %s"' % clone.migration_port
|
| +
|
| + if offline:
|
| + self.monitor.cmd("stop")
|
| +
|
| + logging.info("Migrating to %s", uri)
|
| + self.monitor.migrate(uri)
|
| +
|
| + if cancel_delay:
|
| + time.sleep(cancel_delay)
|
| + self.monitor.cmd("migrate_cancel")
|
| + if not kvm_utils.wait_for(mig_cancelled, 60, 2, 2,
|
| + "Waiting for migration "
|
| + "cancellation"):
|
| + raise VMMigrateCancelError("Cannot cancel migration")
|
| + return
|
| +
|
| + wait_for_migration()
|
| +
|
| + # Report migration status
|
| + if mig_succeeded():
|
| + logging.info("Migration completed successfully")
|
| + elif mig_failed():
|
| + raise VMMigrateFailedError("Migration failed")
|
| + else:
|
| + raise VMMigrateFailedError("Migration ended with unknown "
|
| + "status")
|
| +
|
| + # Switch self <-> clone
|
| + temp = self.clone(copy_state=True)
|
| + self.__dict__ = clone.__dict__
|
| + clone = temp
|
| +
|
| + # From now on, clone is the source VM that will soon be destroyed
|
| + # and self is the destination VM that will remain alive. If this
|
| + # is remote migration, self is a dead VM object.
|
| +
|
| + error.context("after migration")
|
| + if local:
|
| + time.sleep(1)
|
| + self.verify_alive()
|
| +
|
| + if local and stable_check:
|
| + try:
|
| + save1 = os.path.join(save_path, "src-" + clone.instance)
|
| + save2 = os.path.join(save_path, "dst-" + self.instance)
|
| + clone.save_to_file(save1)
|
| + self.save_to_file(save2)
|
| + # Fail if we see deltas
|
| + md5_save1 = utils.hash_file(save1)
|
| + md5_save2 = utils.hash_file(save2)
|
| + if md5_save1 != md5_save2:
|
| + raise VMMigrateStateMismatchError(md5_save1, md5_save2)
|
| + finally:
|
| + if clean:
|
| + if os.path.isfile(save1):
|
| + os.remove(save1)
|
| + if os.path.isfile(save2):
|
| + os.remove(save2)
|
| +
|
| + finally:
|
| + # If we're doing remote migration and it's completed successfully,
|
| + # self points to a dead VM object
|
| + if self.is_alive():
|
| + self.monitor.cmd("cont")
|
| + clone.destroy(gracefully=False)
|
| +
|
| +
|
| + @error.context_aware
|
| + def reboot(self, session=None, method="shell", nic_index=0, timeout=240):
|
| + """
|
| + Reboot the VM and wait for it to come back up by trying to log in until
|
| + timeout expires.
|
| +
|
| + @param session: A shell session object or None.
|
| + @param method: Reboot method. Can be "shell" (send a shell reboot
|
| + command) or "system_reset" (send a system_reset monitor command).
|
| + @param nic_index: Index of NIC to access in the VM, when logging in
|
| + after rebooting.
|
| + @param timeout: Time to wait for login to succeed (after rebooting).
|
| + @return: A new shell session object.
|
| + """
|
| + error.base_context("rebooting '%s'" % self.name, logging.info)
|
| + error.context("before reboot")
|
| + session = session or self.login()
|
| + error.context()
|
| +
|
| + if method == "shell":
|
| + session.sendline(self.params.get("reboot_command"))
|
| + elif method == "system_reset":
|
| + # Clear the event list of all QMP monitors
|
| + qmp_monitors = [m for m in self.monitors if m.protocol == "qmp"]
|
| + for m in qmp_monitors:
|
| + m.clear_events()
|
| + # Send a system_reset monitor command
|
| + self.monitor.cmd("system_reset")
|
| + # Look for RESET QMP events
|
| + time.sleep(1)
|
| + for m in qmp_monitors:
|
| + if m.get_event("RESET"):
|
| + logging.info("RESET QMP event received")
|
| + else:
|
| + raise VMRebootError("RESET QMP event not received after "
|
| + "system_reset (monitor '%s')" % m.name)
|
| + else:
|
| + raise VMRebootError("Unknown reboot method: %s" % method)
|
| +
|
| + error.context("waiting for guest to go down", logging.info)
|
| + if not kvm_utils.wait_for(lambda:
|
| + not session.is_responsive(timeout=30),
|
| + 120, 0, 1):
|
| + raise VMRebootError("Guest refuses to go down")
|
| + session.close()
|
| +
|
| + error.context("logging in after reboot", logging.info)
|
| + return self.wait_for_login(nic_index, timeout=timeout)
|
|
|
|
|
| def send_key(self, keystr):
|
| @@ -1209,15 +1698,9 @@ class VM:
|
| """
|
| Get the cpu count of the VM.
|
| """
|
| - session = self.remote_login()
|
| - if not session:
|
| - return None
|
| + session = self.login()
|
| try:
|
| - cmd = self.params.get("cpu_chk_cmd")
|
| - s, count = session.get_command_status_output(cmd)
|
| - if s == 0:
|
| - return int(count)
|
| - return None
|
| + return int(session.cmd(self.params.get("cpu_chk_cmd")))
|
| finally:
|
| session.close()
|
|
|
| @@ -1229,15 +1712,11 @@ class VM:
|
| @param check_cmd: Command used to check memory. If not provided,
|
| self.params.get("mem_chk_cmd") will be used.
|
| """
|
| - session = self.remote_login()
|
| - if not session:
|
| - return None
|
| + session = self.login()
|
| try:
|
| if not cmd:
|
| cmd = self.params.get("mem_chk_cmd")
|
| - s, mem_str = session.get_command_status_output(cmd)
|
| - if s != 0:
|
| - return None
|
| + mem_str = session.cmd(cmd)
|
| mem = re.findall("([0-9]+)", mem_str)
|
| mem_size = 0
|
| for m in mem:
|
| @@ -1259,3 +1738,17 @@ class VM:
|
| """
|
| cmd = self.params.get("mem_chk_cur_cmd")
|
| return self.get_memory_size(cmd)
|
| +
|
| +
|
| + def save_to_file(self, path):
|
| + """
|
| + Save the state of virtual machine to a file through migrate to
|
| + exec
|
| + """
|
| + # Make sure we only get one iteration
|
| + self.monitor.cmd("migrate_set_speed 1000g")
|
| + self.monitor.cmd("migrate_set_downtime 100000000")
|
| + self.monitor.migrate('"exec:cat>%s"' % path)
|
| + # Restore the speed and downtime of migration
|
| + self.monitor.cmd("migrate_set_speed %d" % (32<<20))
|
| + self.monitor.cmd("migrate_set_downtime 0.03")
|
|
|