834 lines
28 KiB
Python
834 lines
28 KiB
Python
import os, logging, time, glob, re
|
|
from autotest_lib.client.common_lib import error
|
|
from autotest_lib.client.bin import utils
|
|
import virt_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, reason='', detail=''):
|
|
VMError.__init__(self)
|
|
self.reason = reason
|
|
self.detail = detail
|
|
|
|
def __str__(self):
|
|
msg = "VM is dead"
|
|
if self.reason:
|
|
msg += " reason: %s" % self.reason
|
|
if self.detail:
|
|
msg += " detail: %r" % self.detail
|
|
return (msg)
|
|
|
|
|
|
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 VMMigrateProtoUnsupportedError(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
|
|
|
|
class VMStatusError(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 = virt_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_cluster_size (optional) -- the cluster size for the image
|
|
image_size -- the requested size of the image (a string
|
|
qemu-img can understand, such as '10G')
|
|
"""
|
|
qemu_img_cmd = virt_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_cluster_size = params.get("image_cluster_size", None)
|
|
if image_cluster_size is not None:
|
|
qemu_img_cmd += " -o cluster_size=%s" % image_cluster_size
|
|
|
|
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)
|
|
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 the appropriate tools for each virt backend.
|
|
|
|
@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.
|
|
"""
|
|
vm_type = params.get("vm_type")
|
|
if vm_type == 'kvm':
|
|
image_filename = get_image_filename(params, root_dir)
|
|
logging.debug("Checking image file %s", image_filename)
|
|
qemu_img_cmd = virt_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 BaseVM(object):
|
|
"""
|
|
Base class for all hypervisor specific VM subclasses.
|
|
|
|
This class should not be used directly, that is, do not attempt to
|
|
instantiate and use this class. Instead, one should implement a subclass
|
|
that implements, at the very least, all methods defined right after the
|
|
the comment blocks that are marked with:
|
|
|
|
"Public API - *must* be reimplemented with virt specific code"
|
|
|
|
and
|
|
|
|
"Protected API - *must* be reimplemented with virt specific classes"
|
|
|
|
The current proposal regarding methods naming convention is:
|
|
|
|
- Public API methods: named in the usual way, consumed by tests
|
|
- Protected API methods: name begins with a single underline, to be
|
|
consumed only by BaseVM and subclasses
|
|
- Private API methods: name begins with double underline, to be consumed
|
|
only by the VM subclass itself (usually implements virt specific
|
|
functionality: example: __make_qemu_command())
|
|
|
|
So called "protected" methods are intended to be used only by VM classes,
|
|
and not be consumed by tests. Theses should respect a naming convention
|
|
and always be preceeded by a single underline.
|
|
|
|
Currently most (if not all) methods are public and appears to be consumed
|
|
by tests. It is a ongoing task to determine whether methods should be
|
|
"public" or "protected".
|
|
"""
|
|
|
|
#
|
|
# Assuming that all low-level hypervisor have at least migration via tcp
|
|
# (true for xen & kvm). Also true for libvirt (using xen and kvm drivers)
|
|
#
|
|
MIGRATION_PROTOS = ['tcp', ]
|
|
|
|
def __init__(self, name, params):
|
|
self.name = name
|
|
self.params = params
|
|
|
|
#
|
|
# Assuming all low-level hypervisors will have a serial (like) console
|
|
# connection to the guest. libvirt also supports serial (like) consoles
|
|
# (virDomainOpenConsole). subclasses should set this to an object that
|
|
# is or behaves like aexpect.ShellSession.
|
|
#
|
|
self.serial_console = None
|
|
|
|
self._generate_unique_id()
|
|
|
|
|
|
def _generate_unique_id(self):
|
|
"""
|
|
Generate a unique identifier for this VM
|
|
"""
|
|
while True:
|
|
self.instance = (time.strftime("%Y%m%d-%H%M%S-") +
|
|
virt_utils.generate_random_string(4))
|
|
if not glob.glob("/tmp/*%s" % self.instance):
|
|
break
|
|
|
|
|
|
#
|
|
# Public API - could be reimplemented with virt specific code
|
|
#
|
|
def verify_alive(self):
|
|
"""
|
|
Make sure the VM is alive and that the main monitor is responsive.
|
|
|
|
Can be subclassed to provide better information on why the VM is
|
|
not alive (reason, detail)
|
|
|
|
@raise VMDeadError: If the VM is dead
|
|
@raise: Various monitor exceptions if the monitor is unresponsive
|
|
"""
|
|
if self.is_dead():
|
|
raise VMDeadError
|
|
|
|
|
|
def get_mac_address(self, nic_index=0):
|
|
"""
|
|
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
|
|
"""
|
|
nic_name = self.params.objects("nics")[nic_index]
|
|
nic_params = self.params.object_params(nic_name)
|
|
mac = (nic_params.get("nic_mac") or
|
|
virt_utils.get_mac_address(self.instance, nic_index))
|
|
if not mac:
|
|
raise VMMACAddressMissingError(nic_index)
|
|
return mac
|
|
|
|
|
|
def verify_kernel_crash(self):
|
|
"""
|
|
Find kernel crash message on the VM serial console.
|
|
|
|
@raise: VMDeadKernelCrashError, in case a kernel crash message was
|
|
found.
|
|
"""
|
|
if self.serial_console is not None:
|
|
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_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
|
|
|
|
|
|
@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, virt_utils.generate_random_string(4)))
|
|
session = virt_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 (virt_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,
|
|
virt_utils.generate_random_string(4)))
|
|
virt_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,
|
|
virt_utils.generate_random_string(4)))
|
|
virt_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()
|
|
|
|
virt_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 virt_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)
|
|
|
|
|
|
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 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_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()
|
|
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)
|
|
|
|
|
|
#
|
|
# Public API - *must* be reimplemented with virt specific code
|
|
#
|
|
def is_alive(self):
|
|
"""
|
|
Return True if the VM is alive and the management interface is responsive.
|
|
"""
|
|
raise NotImplementedError
|
|
|
|
|
|
def is_dead(self):
|
|
"""
|
|
Return True if the the VM is dead.
|
|
"""
|
|
raise NotImplementedError
|
|
|
|
|
|
def get_address(self, index=0):
|
|
"""
|
|
Return the IP address of a NIC of the guest
|
|
|
|
@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)
|
|
"""
|
|
raise NotImplementedError
|
|
|
|
|
|
def clone(self, name, **params):
|
|
"""
|
|
Return a clone of the VM object with optionally modified parameters.
|
|
|
|
This method should be implemented by
|
|
"""
|
|
raise NotImplementedError
|
|
|
|
|
|
def destroy(self, gracefully=True, free_mac_addresses=True):
|
|
"""
|
|
Destroy the VM.
|
|
|
|
If gracefully is True, first attempt to shutdown the VM with a shell
|
|
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: 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.
|
|
"""
|
|
raise NotImplementedError
|
|
|
|
|
|
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.
|
|
"""
|
|
raise NotImplementedError
|
|
|
|
|
|
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.
|
|
"""
|
|
raise NotImplementedError
|
|
|
|
|
|
# should this really be expected from VMs of all hypervisor types?
|
|
def send_key(self, keystr):
|
|
"""
|
|
Send a key event to the VM.
|
|
|
|
@param: keystr: A key event string (e.g. "ctrl-alt-delete")
|
|
"""
|
|
raise NotImplementedError
|
|
|
|
|
|
def save_to_file(self, path):
|
|
"""
|
|
Save the state of virtual machine to a file through migrate to
|
|
exec
|
|
"""
|
|
raise NotImplementedError
|
|
|
|
|
|
def needs_restart(self, name, params, basedir):
|
|
"""
|
|
Based on virt preprocessing information, decide whether the VM needs
|
|
a restart.
|
|
"""
|
|
raise NotImplementedError
|