| Index: client/virt/kvm_vm.py
|
| diff --git a/client/tests/kvm/kvm_vm.py b/client/virt/kvm_vm.py
|
| old mode 100755
|
| new mode 100644
|
| similarity index 64%
|
| rename from client/tests/kvm/kvm_vm.py
|
| rename to client/virt/kvm_vm.py
|
| index f0b81528d2e69cc0384e2f6d69815d9e9e4381a3..5df87191fe12aa73e804d4cc8f2cfe1f2a18c684
|
| --- a/client/tests/kvm/kvm_vm.py
|
| +++ b/client/virt/kvm_vm.py
|
| @@ -6,334 +6,18 @@ Utility classes and functions to handle Virtual Machine creation using qemu.
|
| """
|
|
|
| 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
|
| +import virt_utils, virt_vm, kvm_monitor, aexpect
|
|
|
|
|
| -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 VMDeadKernelCrashError(VMError):
|
| - def __init__(self, kernel_crash):
|
| - VMError.__init__(self, kernel_crash)
|
| - self.kernel_crash = kernel_crash
|
| -
|
| - def __str__(self):
|
| - return ("VM is dead due to a kernel crash:\n%s" % self.kernel_crash)
|
| -
|
| -
|
| -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.
|
| -
|
| - @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)
|
| - """
|
| - 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
|
| -
|
| -
|
| -def create_image(params, root_dir):
|
| - """
|
| - Create an image using qemu_image.
|
| -
|
| - @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)
|
| - image_size -- the requested size of the image (a string
|
| - qemu-img can understand, such as '10G')
|
| - """
|
| - qemu_img_cmd = kvm_utils.get_path(root_dir, params.get("qemu_img_binary",
|
| - "qemu-img"))
|
| - qemu_img_cmd += " create"
|
| -
|
| - format = params.get("image_format", "qcow2")
|
| - qemu_img_cmd += " -f %s" % format
|
| -
|
| - image_filename = get_image_filename(params, root_dir)
|
| - qemu_img_cmd += " %s" % image_filename
|
| -
|
| - size = params.get("image_size", "10G")
|
| - qemu_img_cmd += " %s" % size
|
| -
|
| - utils.system(qemu_img_cmd)
|
| - logging.info("Image created in %r", image_filename)
|
| - return image_filename
|
| -
|
| -
|
| -def remove_image(params, root_dir):
|
| - """
|
| - Remove an image file.
|
| -
|
| - @param params: A dict
|
| - @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)
|
| - """
|
| - image_filename = get_image_filename(params, root_dir)
|
| - 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)
|
| -
|
| - cmd_result = utils.run("%s check %s" %
|
| - (qemu_img_cmd, image_filename),
|
| - ignore_status=True)
|
| - # Error check, large chances of a non-fatal problem.
|
| - # There are chances that bad data was skipped though
|
| - if cmd_result.exit_status == 1:
|
| - for e_line in cmd_result.stdout.splitlines():
|
| - logging.error("[stdout] %s", e_line)
|
| - for e_line in cmd_result.stderr.splitlines():
|
| - logging.error("[stderr] %s", e_line)
|
| - raise error.TestWarn("qemu-img check error. Some bad data in "
|
| - "the image may have gone unnoticed")
|
| - # Exit status 2 is data corruption for sure, so fail the test
|
| - elif cmd_result.exit_status == 2:
|
| - for e_line in cmd_result.stdout.splitlines():
|
| - logging.error("[stdout] %s", e_line)
|
| - for e_line in cmd_result.stderr.splitlines():
|
| - logging.error("[stderr] %s", e_line)
|
| - raise VMImageCheckError(image_filename)
|
| - # Leaked clusters, they are known to be harmless to data integrity
|
| - elif cmd_result.exit_status == 3:
|
| - raise error.TestWarn("Leaked clusters were noticed during "
|
| - "image check. No data integrity problem "
|
| - "was found though.")
|
| -
|
| - 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:
|
| +class VM(virt_vm.BaseVM):
|
| """
|
| This class handles all basic VM operations.
|
| """
|
|
|
| + MIGRATION_PROTOS = ['tcp', 'unix', 'exec']
|
| +
|
| def __init__(self, name, params, root_dir, address_cache, state=None):
|
| """
|
| Initialize the object and set a few attributes.
|
| @@ -345,6 +29,8 @@ class VM:
|
| @param address_cache: A dict that maps MAC addresses to IP addresses
|
| @param state: If provided, use this as self.__dict__
|
| """
|
| + virt_vm.BaseVM.__init__(self, name, params)
|
| +
|
| if state:
|
| self.__dict__ = state
|
| else:
|
| @@ -358,12 +44,6 @@ class VM:
|
| self.device_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.spice_port = 8000
|
| self.name = name
|
| @@ -372,6 +52,39 @@ class VM:
|
| self.address_cache = address_cache
|
|
|
|
|
| + 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
|
| + """
|
| + try:
|
| + virt_vm.BaseVM.verify_alive(self)
|
| + if self.monitors:
|
| + self.monitor.verify_responsive()
|
| + except virt_vm.VMDeadError:
|
| + raise virt_vm.VMDeadError(self.process.get_status(),
|
| + self.process.get_output())
|
| +
|
| +
|
| + def is_alive(self):
|
| + """
|
| + Return True if the VM is alive and its monitor is responsive.
|
| + """
|
| + return not self.is_dead() and (not self.monitors or
|
| + self.monitor.is_responsive())
|
| +
|
| +
|
| + def is_dead(self):
|
| + """
|
| + Return True if the qemu process is dead.
|
| + """
|
| + return not self.process or not self.process.is_alive()
|
| +
|
| +
|
| +
|
| +
|
| def clone(self, name=None, params=None, root_dir=None, address_cache=None,
|
| copy_state=False):
|
| """
|
| @@ -402,7 +115,7 @@ class VM:
|
| return VM(name, params, root_dir, address_cache, state)
|
|
|
|
|
| - def make_qemu_command(self, name=None, params=None, root_dir=None):
|
| + def __make_qemu_command(self, name=None, params=None, root_dir=None):
|
| """
|
| Generate a qemu command line. All parameters are optional. If a
|
| parameter is not supplied, the corresponding value stored in the
|
| @@ -635,7 +348,7 @@ class VM:
|
| # 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_binary = virt_utils.get_path(root_dir, params.get("qemu_binary",
|
| "qemu"))
|
| # Get the output of 'qemu -help' (log a message in case this call never
|
| # returns or causes some other kind of trouble)
|
| @@ -668,7 +381,7 @@ class VM:
|
| if image_params.get("boot_drive") == "no":
|
| continue
|
| qemu_cmd += add_drive(help,
|
| - get_image_filename(image_params, root_dir),
|
| + virt_vm.get_image_filename(image_params, root_dir),
|
| image_params.get("drive_index"),
|
| image_params.get("drive_format"),
|
| image_params.get("drive_cache"),
|
| @@ -695,7 +408,7 @@ class VM:
|
| # Handle the '-net nic' part
|
| try:
|
| mac = vm.get_mac_address(vlan)
|
| - except VMAddressError:
|
| + except virt_vm.VMAddressError:
|
| mac = None
|
| qemu_cmd += add_nic(help, vlan, nic_params.get("nic_model"), mac,
|
| device_id, netdev_id, nic_params.get("nic_extra_params"))
|
| @@ -704,11 +417,11 @@ class VM:
|
| downscript = nic_params.get("nic_downscript")
|
| tftp = nic_params.get("tftp")
|
| if script:
|
| - script = kvm_utils.get_path(root_dir, script)
|
| + script = virt_utils.get_path(root_dir, script)
|
| if downscript:
|
| - downscript = kvm_utils.get_path(root_dir, downscript)
|
| + downscript = virt_utils.get_path(root_dir, downscript)
|
| if tftp:
|
| - tftp = kvm_utils.get_path(root_dir, tftp)
|
| + tftp = virt_utils.get_path(root_dir, tftp)
|
| qemu_cmd += add_net(help, vlan, nic_params.get("nic_mode", "user"),
|
| vm.get_ifname(vlan),
|
| script, downscript, tftp,
|
| @@ -729,19 +442,19 @@ class VM:
|
| 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),
|
| + qemu_cmd += add_cdrom(help, virt_utils.get_path(root_dir, iso),
|
| cdrom_params.get("drive_index"))
|
|
|
| # We may want to add {floppy_otps} parameter for -fda
|
| # {fat:floppy:}/path/. However vvfat is not usually recommended.
|
| floppy = params.get("floppy")
|
| if floppy:
|
| - floppy = kvm_utils.get_path(root_dir, floppy)
|
| + floppy = virt_utils.get_path(root_dir, floppy)
|
| qemu_cmd += add_floppy(help, floppy)
|
|
|
| tftp = params.get("tftp")
|
| if tftp:
|
| - tftp = kvm_utils.get_path(root_dir, tftp)
|
| + tftp = virt_utils.get_path(root_dir, tftp)
|
| qemu_cmd += add_tftp(help, tftp)
|
|
|
| bootp = params.get("bootp")
|
| @@ -750,7 +463,7 @@ class VM:
|
|
|
| kernel = params.get("kernel")
|
| if kernel:
|
| - kernel = kvm_utils.get_path(root_dir, kernel)
|
| + kernel = virt_utils.get_path(root_dir, kernel)
|
| qemu_cmd += add_kernel(help, kernel)
|
|
|
| kernel_cmdline = params.get("kernel_cmdline")
|
| @@ -759,7 +472,7 @@ class VM:
|
|
|
| initrd = params.get("initrd")
|
| if initrd:
|
| - initrd = kvm_utils.get_path(root_dir, initrd)
|
| + initrd = virt_utils.get_path(root_dir, initrd)
|
| qemu_cmd += add_initrd(help, initrd)
|
|
|
| for host_port, guest_port in redirs:
|
| @@ -855,9 +568,9 @@ class VM:
|
| cdrom_params = params.object_params(cdrom)
|
| iso = cdrom_params.get("cdrom")
|
| if iso:
|
| - iso = kvm_utils.get_path(root_dir, iso)
|
| + iso = virt_utils.get_path(root_dir, iso)
|
| if not os.path.exists(iso):
|
| - raise VMImageMissingError(iso)
|
| + raise virt_vm.VMImageMissingError(iso)
|
| compare = False
|
| if cdrom_params.get("md5sum_1m"):
|
| logging.debug("Comparing expected MD5 sum with MD5 sum of "
|
| @@ -881,7 +594,8 @@ class VM:
|
| if actual_hash == expected_hash:
|
| logging.debug("Hashes match")
|
| else:
|
| - raise VMHashMismatchError(actual_hash, expected_hash)
|
| + raise virt_vm.VMHashMismatchError(actual_hash,
|
| + expected_hash)
|
|
|
| # Make sure the following code is not executed by more than one thread
|
| # at the same time
|
| @@ -891,7 +605,7 @@ class VM:
|
| try:
|
| # Handle port redirections
|
| redir_names = params.objects("redirs")
|
| - host_ports = kvm_utils.find_free_ports(5000, 6000, len(redir_names))
|
| + host_ports = virt_utils.find_free_ports(5000, 6000, len(redir_names))
|
| self.redirs = {}
|
| for i in range(len(redir_names)):
|
| redir_params = params.object_params(redir_names[i])
|
| @@ -902,16 +616,16 @@ class VM:
|
| self.netdev_id = []
|
| self.device_id = []
|
| for nic in params.objects("nics"):
|
| - self.netdev_id.append(kvm_utils.generate_random_id())
|
| - self.device_id.append(kvm_utils.generate_random_id())
|
| + self.netdev_id.append(virt_utils.generate_random_id())
|
| + self.device_id.append(virt_utils.generate_random_id())
|
|
|
| # Find available VNC port, if needed
|
| if params.get("display") == "vnc":
|
| - self.vnc_port = kvm_utils.find_free_port(5900, 6100)
|
| + self.vnc_port = virt_utils.find_free_port(5900, 6100)
|
|
|
| - # Find available spice port
|
| + # Find available spice port, if needed
|
| if params.get("spice"):
|
| - self.spice_port = kvm_utils.find_free_port(8000, 8100)
|
| + self.spice_port = virt_utils.find_free_port(8000, 8100)
|
|
|
| # Find random UUID if specified 'uuid = random' in config file
|
| if params.get("uuid") == "random":
|
| @@ -927,9 +641,9 @@ class VM:
|
| mac = (nic_params.get("nic_mac") or
|
| mac_source and mac_source.get_mac_address(vlan))
|
| if mac:
|
| - kvm_utils.set_mac_address(self.instance, vlan, mac)
|
| + virt_utils.set_mac_address(self.instance, vlan, mac)
|
| else:
|
| - kvm_utils.generate_mac_address(self.instance, vlan)
|
| + virt_utils.generate_mac_address(self.instance, vlan)
|
|
|
| # Assign a PCI assignable device
|
| self.pci_assignable = None
|
| @@ -939,27 +653,27 @@ class VM:
|
|
|
| # Virtual Functions (VF) assignable devices
|
| if pa_type == "vf":
|
| - self.pci_assignable = kvm_utils.PciAssignable(
|
| + self.pci_assignable = virt_utils.PciAssignable(
|
| type=pa_type,
|
| driver=params.get("driver"),
|
| driver_option=params.get("driver_option"),
|
| devices_requested=pa_devices_requested)
|
| # Physical NIC (PF) assignable devices
|
| elif pa_type == "pf":
|
| - self.pci_assignable = kvm_utils.PciAssignable(
|
| + self.pci_assignable = virt_utils.PciAssignable(
|
| type=pa_type,
|
| names=params.get("device_names"),
|
| devices_requested=pa_devices_requested)
|
| # Working with both VF and PF
|
| elif pa_type == "mixed":
|
| - self.pci_assignable = kvm_utils.PciAssignable(
|
| + self.pci_assignable = virt_utils.PciAssignable(
|
| type=pa_type,
|
| driver=params.get("driver"),
|
| driver_option=params.get("driver_option"),
|
| names=params.get("device_names"),
|
| devices_requested=pa_devices_requested)
|
| else:
|
| - raise VMBadPATypeError(pa_type)
|
| + raise virt_vm.VMBadPATypeError(pa_type)
|
|
|
| self.pa_pci_ids = self.pci_assignable.request_devs()
|
|
|
| @@ -967,32 +681,32 @@ class VM:
|
| logging.debug("Successfuly assigned devices: %s",
|
| self.pa_pci_ids)
|
| else:
|
| - raise VMPAError(pa_type)
|
| + raise virt_vm.VMPAError(pa_type)
|
|
|
| # Make qemu command
|
| - qemu_command = self.make_qemu_command()
|
| + qemu_command = self.__make_qemu_command()
|
|
|
| # Add migration parameters if required
|
| if migration_mode == "tcp":
|
| - self.migration_port = kvm_utils.find_free_port(5200, 6000)
|
| + self.migration_port = virt_utils.find_free_port(5200, 6000)
|
| qemu_command += " -incoming tcp:0:%d" % self.migration_port
|
| elif migration_mode == "unix":
|
| self.migration_file = "/tmp/migration-unix-%s" % self.instance
|
| qemu_command += " -incoming unix:%s" % self.migration_file
|
| elif migration_mode == "exec":
|
| - self.migration_port = kvm_utils.find_free_port(5200, 6000)
|
| + self.migration_port = virt_utils.find_free_port(5200, 6000)
|
| qemu_command += (' -incoming "exec:nc -l %s"' %
|
| self.migration_port)
|
|
|
| logging.info("Running qemu command:\n%s", qemu_command)
|
| - self.process = kvm_subprocess.run_bg(qemu_command, None,
|
| + self.process = aexpect.run_bg(qemu_command, None,
|
| logging.info, "(qemu) ")
|
|
|
| # Make sure the process was started successfully
|
| if not self.process.is_alive():
|
| - e = VMCreateError(qemu_command,
|
| - self.process.get_status(),
|
| - self.process.get_output())
|
| + e = virt_vm.VMCreateError(qemu_command,
|
| + self.process.get_status(),
|
| + self.process.get_output())
|
| self.destroy()
|
| raise e
|
|
|
| @@ -1030,12 +744,12 @@ class VM:
|
| output = self.process.get_output()
|
|
|
| if re.search("Could not initialize KVM", output, re.IGNORECASE):
|
| - e = VMKVMInitError(qemu_command, self.process.get_output())
|
| + e = virt_vm.VMKVMInitError(qemu_command, self.process.get_output())
|
| self.destroy()
|
| raise e
|
|
|
| if "alloc_mem_area" in output:
|
| - e = VMHugePageError(qemu_command, self.process.get_output())
|
| + e = virt_vm.VMHugePageError(qemu_command, self.process.get_output())
|
| self.destroy()
|
| raise e
|
|
|
| @@ -1043,10 +757,10 @@ class VM:
|
|
|
| # Establish a session with the serial console -- requires a version
|
| # of netcat that supports -U
|
| - self.serial_console = kvm_subprocess.ShellSession(
|
| + self.serial_console = aexpect.ShellSession(
|
| "nc -U %s" % self.get_serial_console_filename(),
|
| auto_close=False,
|
| - output_func=kvm_utils.log_line,
|
| + output_func=virt_utils.log_line,
|
| output_params=("serial-%s.log" % name,))
|
|
|
| finally:
|
| @@ -1080,7 +794,7 @@ class VM:
|
| logging.debug("Trying to shutdown VM with shell command...")
|
| try:
|
| session = self.login()
|
| - except (kvm_utils.LoginError, VMError), e:
|
| + except (virt_utils.LoginError, virt_vm.VMError), e:
|
| logging.debug(e)
|
| else:
|
| try:
|
| @@ -1088,7 +802,7 @@ class VM:
|
| 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):
|
| + if virt_utils.wait_for(self.is_dead, 60, 1, 1):
|
| logging.debug("VM is down")
|
| return
|
| finally:
|
| @@ -1103,16 +817,16 @@ class VM:
|
| logging.warn(e)
|
| else:
|
| # Wait for the VM to be really dead
|
| - if kvm_utils.wait_for(self.is_dead, 5, 0.5, 0.5):
|
| + if virt_utils.wait_for(self.is_dead, 5, 0.5, 0.5):
|
| logging.debug("VM is down")
|
| return
|
|
|
| # If the VM isn't dead yet...
|
| logging.debug("Cannot quit normally; sending a kill to close the "
|
| "deal...")
|
| - kvm_utils.kill_process_tree(self.process.get_pid(), 9)
|
| + virt_utils.kill_process_tree(self.process.get_pid(), 9)
|
| # Wait for the VM to be really dead
|
| - if kvm_utils.wait_for(self.is_dead, 5, 0.5, 0.5):
|
| + if virt_utils.wait_for(self.is_dead, 5, 0.5, 0.5):
|
| logging.debug("VM is down")
|
| return
|
|
|
| @@ -1159,57 +873,6 @@ 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.
|
| - """
|
| - return not self.is_dead() and (not self.monitors or
|
| - self.monitor.is_responsive())
|
| -
|
| -
|
| - def is_dead(self):
|
| - """
|
| - Return True if the qemu process is dead.
|
| - """
|
| - return not self.process or not self.process.is_alive()
|
| -
|
| -
|
| - def verify_kernel_crash(self):
|
| - """
|
| - Find kernel crash message on the VM serial console.
|
| -
|
| - @raise: VMDeadKernelCrashError, in case a kernel crash message was
|
| - found.
|
| - """
|
| - data = self.serial_console.get_output()
|
| - match = re.search(r"BUG:.*---\[ end trace .* \]---", data,
|
| - re.DOTALL|re.MULTILINE)
|
| - if match is not None:
|
| - raise VMDeadKernelCrashError(match.group(0))
|
| -
|
| -
|
| - def get_params(self):
|
| - """
|
| - Return the VM's params dict. Most modified params take effect only
|
| - upon VM.create().
|
| - """
|
| - return self.params
|
| -
|
| -
|
| def get_monitor_filename(self, monitor_name):
|
| """
|
| Return the filename corresponding to a given monitor name.
|
| @@ -1226,20 +889,6 @@ class VM:
|
| self.params.objects("monitors")]
|
|
|
|
|
| - def get_serial_console_filename(self):
|
| - """
|
| - Return the serial console filename.
|
| - """
|
| - return "/tmp/serial-%s" % self.instance
|
| -
|
| -
|
| - def get_testlog_filename(self):
|
| - """
|
| - Return the testlog filename.
|
| - """
|
| - return "/tmp/testlog-%s" % self.instance
|
| -
|
| -
|
| def get_address(self, index=0):
|
| """
|
| Return the address of a NIC of the guest, in host space.
|
| @@ -1263,11 +912,11 @@ class VM:
|
| # Get the IP address from the cache
|
| ip = self.address_cache.get(mac)
|
| if not ip:
|
| - raise VMIPAddressMissingError(mac)
|
| + raise virt_vm.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):
|
| - raise VMAddressVerificationError(mac, ip)
|
| + if not virt_utils.verify_ip_address_ownership(ip, macs):
|
| + raise virt_vm.VMAddressVerificationError(mac, ip)
|
| return ip
|
| else:
|
| return "localhost"
|
| @@ -1292,7 +941,7 @@ class VM:
|
| try:
|
| return self.redirs[port]
|
| except KeyError:
|
| - raise VMPortNotRedirectedError(port)
|
| + raise virt_vm.VMPortNotRedirectedError(port)
|
|
|
|
|
| def get_peer(self, netid):
|
| @@ -1335,9 +984,9 @@ class VM:
|
| nic_name = self.params.objects("nics")[nic_index]
|
| nic_params = self.params.object_params(nic_name)
|
| mac = (nic_params.get("nic_mac") or
|
| - kvm_utils.get_mac_address(self.instance, nic_index))
|
| + virt_utils.get_mac_address(self.instance, nic_index))
|
| if not mac:
|
| - raise VMMACAddressMissingError(nic_index)
|
| + raise virt_vm.VMMACAddressMissingError(nic_index)
|
| return mac
|
|
|
|
|
| @@ -1347,7 +996,7 @@ class VM:
|
|
|
| @param nic_index: Index of the NIC
|
| """
|
| - kvm_utils.free_mac_address(self.instance, nic_index)
|
| + virt_utils.free_mac_address(self.instance, nic_index)
|
|
|
|
|
| def get_pid(self):
|
| @@ -1392,168 +1041,6 @@ class VM:
|
|
|
|
|
| @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
|
| - password prompt or a shell prompt) -- fail.
|
| -
|
| - @param nic_index: The index of the NIC to connect to.
|
| - @param timeout: Time (seconds) before giving up logging into the
|
| - guest.
|
| - @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", "[\#\$]")
|
| - linesep = eval("'%s'" % self.params.get("shell_linesep", r"\n"))
|
| - client = self.params.get("shell_client")
|
| - address = self.get_address(nic_index)
|
| - port = self.get_port(int(self.params.get("shell_port")))
|
| - log_filename = ("session-%s-%s.log" %
|
| - (self.name, kvm_utils.generate_random_string(4)))
|
| - session = kvm_utils.remote_login(client, address, port, username,
|
| - password, prompt, linesep,
|
| - log_filename, timeout)
|
| - session.set_status_test_command(self.params.get("status_test_command",
|
| - ""))
|
| - return session
|
| -
|
| -
|
| - 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 remote host(guest).
|
| -
|
| - @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")))
|
| - 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)
|
| -
|
| -
|
| - @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 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")))
|
| - 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.
|
| - If timeout expires while waiting for output from the guest (e.g. a
|
| - password prompt or a shell prompt) -- fail.
|
| -
|
| - @param timeout: Time (seconds) before giving up logging into the guest.
|
| - @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", "")
|
| -
|
| - self.serial_console.set_linesep(linesep)
|
| - self.serial_console.set_status_test_command(status_test_command)
|
| -
|
| - # Try to get a login prompt
|
| - self.serial_console.sendline()
|
| -
|
| - 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):
|
| @@ -1565,7 +1052,7 @@ class VM:
|
| 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 protocol: Migration protocol (as defined in MIGRATION_PROTOS)
|
| @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.
|
| @@ -1578,6 +1065,9 @@ class VM:
|
| @param dest_host: Destination host (defaults to 'localhost').
|
| @param remote_port: Port to use for remote migration.
|
| """
|
| + if protocol not in self.MIGRATION_PROTOS:
|
| + raise virt_vm.VMMigrateProtoUnsupportedError
|
| +
|
| error.base_context("migrating '%s'" % self.name)
|
|
|
| def mig_finished():
|
| @@ -1611,9 +1101,9 @@ class VM:
|
| o.get("status") == "canceled")
|
|
|
| def wait_for_migration():
|
| - if not kvm_utils.wait_for(mig_finished, timeout, 2, 2,
|
| + if not virt_utils.wait_for(mig_finished, timeout, 2, 2,
|
| "Waiting for migration to complete"):
|
| - raise VMMigrateTimeoutError("Timeout expired while waiting "
|
| + raise virt_vm.VMMigrateTimeoutError("Timeout expired while waiting "
|
| "for migration to finish")
|
|
|
| local = dest_host == "localhost"
|
| @@ -1648,10 +1138,10 @@ class VM:
|
| if cancel_delay:
|
| time.sleep(cancel_delay)
|
| self.monitor.cmd("migrate_cancel")
|
| - if not kvm_utils.wait_for(mig_cancelled, 60, 2, 2,
|
| + if not virt_utils.wait_for(mig_cancelled, 60, 2, 2,
|
| "Waiting for migration "
|
| "cancellation"):
|
| - raise VMMigrateCancelError("Cannot cancel migration")
|
| + raise virt_vm.VMMigrateCancelError("Cannot cancel migration")
|
| return
|
|
|
| wait_for_migration()
|
| @@ -1660,10 +1150,10 @@ class VM:
|
| if mig_succeeded():
|
| logging.info("Migration completed successfully")
|
| elif mig_failed():
|
| - raise VMMigrateFailedError("Migration failed")
|
| + raise virt_vm.VMMigrateFailedError("Migration failed")
|
| else:
|
| - raise VMMigrateFailedError("Migration ended with unknown "
|
| - "status")
|
| + raise virt_vm.VMMigrateFailedError("Migration ended with "
|
| + "unknown status")
|
|
|
| # Switch self <-> clone
|
| temp = self.clone(copy_state=True)
|
| @@ -1678,7 +1168,6 @@ class VM:
|
| if local:
|
| time.sleep(1)
|
| self.verify_alive()
|
| - self.verify_kernel_crash()
|
|
|
| if local and stable_check:
|
| try:
|
| @@ -1690,7 +1179,8 @@ class VM:
|
| md5_save1 = utils.hash_file(save1)
|
| md5_save2 = utils.hash_file(save2)
|
| if md5_save1 != md5_save2:
|
| - raise VMMigrateStateMismatchError(md5_save1, md5_save2)
|
| + raise virt_vm.VMMigrateStateMismatchError(md5_save1,
|
| + md5_save2)
|
| finally:
|
| if clean:
|
| if os.path.isfile(save1):
|
| @@ -1740,16 +1230,17 @@ class VM:
|
| 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)
|
| + raise virt_vm.VMRebootError("RESET QMP event not received "
|
| + "after system_reset "
|
| + "(monitor '%s')" % m.name)
|
| else:
|
| - raise VMRebootError("Unknown reboot method: %s" % method)
|
| + raise virt_vm.VMRebootError("Unknown reboot method: %s" % method)
|
|
|
| error.context("waiting for guest to go down", logging.info)
|
| - if not kvm_utils.wait_for(lambda:
|
| + if not virt_utils.wait_for(lambda:
|
| not session.is_responsive(timeout=30),
|
| 120, 0, 1):
|
| - raise VMRebootError("Guest refuses to go down")
|
| + raise virt_vm.VMRebootError("Guest refuses to go down")
|
| session.close()
|
|
|
| error.context("logging in after reboot", logging.info)
|
| @@ -1774,76 +1265,13 @@ class VM:
|
| time.sleep(0.2)
|
|
|
|
|
| - def send_string(self, str):
|
| - """
|
| - Send a string to the VM.
|
| -
|
| - @param str: String, that must consist of alphanumeric characters only.
|
| - Capital letters are allowed.
|
| - """
|
| - for char in str:
|
| - if char.isupper():
|
| - self.send_key("shift-%s" % char.lower())
|
| - else:
|
| - self.send_key(char)
|
| -
|
| -
|
| - def get_uuid(self):
|
| - """
|
| - Catch UUID of the VM.
|
| -
|
| - @return: None,if not specified in config file
|
| - """
|
| - if self.params.get("uuid") == "random":
|
| - return self.uuid
|
| - else:
|
| - return self.params.get("uuid", None)
|
| -
|
| -
|
| - def get_cpu_count(self):
|
| - """
|
| - Get the cpu count of the VM.
|
| - """
|
| - session = self.login()
|
| - try:
|
| - return int(session.cmd(self.params.get("cpu_chk_cmd")))
|
| - finally:
|
| - session.close()
|
| -
|
| -
|
| - def get_memory_size(self, cmd=None):
|
| - """
|
| - Get bootup memory size of the VM.
|
| -
|
| - @param check_cmd: Command used to check memory. If not provided,
|
| - self.params.get("mem_chk_cmd") will be used.
|
| - """
|
| - session = self.login()
|
| + # should this really be expected from VMs of all hypervisor types?
|
| + def screendump(self, filename):
|
| try:
|
| - if not cmd:
|
| - cmd = self.params.get("mem_chk_cmd")
|
| - mem_str = session.cmd(cmd)
|
| - mem = re.findall("([0-9]+)", mem_str)
|
| - mem_size = 0
|
| - for m in mem:
|
| - mem_size += int(m)
|
| - if "GB" in mem_str:
|
| - mem_size *= 1024
|
| - elif "MB" in mem_str:
|
| - pass
|
| - else:
|
| - mem_size /= 1024
|
| - return int(mem_size)
|
| - finally:
|
| - session.close()
|
| -
|
| -
|
| - def get_current_memory_size(self):
|
| - """
|
| - Get current memory size of the VM, rather than bootup memory.
|
| - """
|
| - cmd = self.params.get("mem_chk_cur_cmd")
|
| - return self.get_memory_size(cmd)
|
| + if self.monitor:
|
| + self.monitor.screendump(filename=filename)
|
| + except kvm_monitor.MonitorError, e:
|
| + logging.warn(e)
|
|
|
|
|
| def save_to_file(self, path):
|
| @@ -1858,3 +1286,12 @@ class VM:
|
| # Restore the speed and downtime of migration
|
| self.monitor.cmd("migrate_set_speed %d" % (32<<20))
|
| self.monitor.cmd("migrate_set_downtime 0.03")
|
| +
|
| +
|
| + def needs_restart(self, name, params, basedir):
|
| + """
|
| + Verifies whether the current qemu commandline matches the requested
|
| + one, based on the test parameters.
|
| + """
|
| + return (self.__make_qemu_command() !=
|
| + self.__make_qemu_command(name, params, basedir))
|
|
|