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)) |