import logging
import time
import glob
import os
import re
import socket
from autotest.client import utils
from autotest.client.shared import error
import utils_misc
import utils_net
import remote
import traceback
import ppm_utils
import data_dir
import copy
[docs]class VMError(Exception):
def __init__(self, *args):
Exception.__init__(self, *args)
[docs]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))
[docs]class VMStartError(VMError):
def __init__(self, name, reason=None):
VMError.__init__(self, name, reason)
self.name = name
self.reason = reason
def __str__(self):
msg = "VM '%s' failed to start" % self.name
if self.reason is not None:
msg += ": %s" % self.reason
return msg
[docs]class VMConfigMissingError(VMError):
def __init__(self, name, config):
VMError.__init__(self, name, config)
self.name = name
self.config = config
def __str__(self):
return "Missing config '%s' for VM %s" % (self.config, self.name)
[docs]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))
[docs]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
[docs]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
[docs]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
[docs]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)
[docs]class VMPostCreateError(VMError):
def __init__(self, cmd, output):
VMError.__init__(self, cmd, output)
self.cmd = cmd
self.output = output
[docs]class VMHugePageError(VMPostCreateError):
def __str__(self):
return ("Cannot allocate hugepage memory (command: %r, "
"output: %r)" % (self.cmd, self.output))
[docs]class VMKVMInitError(VMPostCreateError):
def __str__(self):
return ("Cannot initialize KVM (command: %r, output: %r)" %
(self.cmd, self.output))
[docs]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)
[docs]class VMDeadKernelCrashError(VMError):
def __init__(self, kernel_crash):
VMError.__init__(self, kernel_crash)
logging.debug(kernel_crash)
def __str__(self):
return ("VM is dead due to a kernel crash, "
"see debug/serial log for details")
[docs]class VMInvalidInstructionCode(VMError):
def __init__(self, invalid_code):
VMError.__init__(self, invalid_code)
self.invalid_code = invalid_code
def __str__(self):
error = ""
for invalid_code in self.invalid_code:
error += "%s" % (invalid_code)
return ("Invalid instruction was executed on VM:\n%s" % error)
[docs]class VMAddressError(VMError):
pass
[docs]class VMInterfaceIndexError(VMError):
pass
[docs]class VMPortNotRedirectedError(VMAddressError):
def __init__(self, port, virtnet_nic=None):
VMAddressError.__init__(self, port)
self.port = port
self.virtnet_nic = virtnet_nic
def __str__(self):
msg = "Don't know how to connect to guest port %s" % self.port
if self.virtnet_nic is None:
return msg
else:
nic = self.virtnet_nic
msg += (" with networking type '%s', to destination '%s', for nic "
"'%s' with mac '%s' and ip '%s'." % (nic.nettype, nic.netdst,
nic.nic_name, nic.mac, nic.ip))
return msg
[docs]class VMAddressVerificationError(VMAddressError):
def __init__(self, mac, ip):
VMAddressError.__init__(self, mac, ip)
self.mac = mac
self.ip = ip
def __str__(self):
return ("Could not verify DHCP lease: "
"%s --> %s" % (self.mac, self.ip))
[docs]class VMMACAddressMissingError(VMAddressError):
def __init__(self, nic_index):
VMAddressError.__init__(self, nic_index)
self.nic_index = nic_index
def __str__(self):
return "No MAC defined for NIC #%s" % self.nic_index
[docs]class VMIPAddressMissingError(VMAddressError):
def __init__(self, mac, ip_version="ipv4"):
VMAddressError.__init__(self, mac)
self.mac = mac
self.ip_version = ip_version
def __str__(self):
return "No %s DHCP lease for MAC %s" % (self.ip_version, self.mac)
[docs]class VMUnknownNetTypeError(VMError):
def __init__(self, vmname, nicname, nettype):
super(VMUnknownNetTypeError, self).__init__()
self.vmname = vmname
self.nicname = nicname
self.nettype = nettype
def __str__(self):
return "Unknown nettype '%s' requested for NIC %s on VM %s" % (
self.nettype, self.nicname, self.vmname)
[docs]class VMAddNetDevError(VMError):
pass
[docs]class VMDelNetDevError(VMError):
pass
[docs]class VMAddNicError(VMError):
pass
[docs]class VMDelNicError(VMError):
pass
[docs]class VMMigrateError(VMError):
pass
[docs]class VMMigrateTimeoutError(VMMigrateError):
pass
[docs]class VMMigrateCancelError(VMMigrateError):
pass
[docs]class VMMigrateFailedError(VMMigrateError):
pass
[docs]class VMMigrateProtoUnknownError(error.TestNAError):
def __init__(self, protocol):
self.protocol = protocol
def __str__(self):
return ("Virt Test doesn't know migration protocol '%s'. "
"You would have to add it to the list of known protocols" %
self.protocol)
[docs]class VMMigrateStateMismatchError(VMMigrateError):
def __init__(self):
VMMigrateError.__init__(self)
def __str__(self):
return ("Mismatch of VM state before and after migration")
[docs]class VMRebootError(VMError):
pass
[docs]class VMStatusError(VMError):
pass
[docs]class VMRemoveError(VMError):
pass
[docs]class VMDeviceError(VMError):
pass
[docs]class VMDeviceNotSupportedError(VMDeviceError):
def __init__(self, name, device):
VMDeviceError.__init__(self, name, device)
self.name = name
self.device = device
def __str__(self):
return ("Device '%s' is not supported for vm '%s' on this Host." %
(self.device, self.name))
[docs]class VMPCIDeviceError(VMDeviceError):
pass
[docs]class VMPCISlotInUseError(VMPCIDeviceError):
def __init__(self, name, slot):
VMPCIDeviceError.__init__(self, name, slot)
self.name = name
self.slot = slot
def __str__(self):
return ("PCI slot '0x%s' is already in use on vm '%s'. Please assign"
" another slot in config file." % (self.slot, self.name))
[docs]class VMPCIOutOfRangeError(VMPCIDeviceError):
def __init__(self, name, max_dev_num):
VMPCIDeviceError.__init__(self, name, max_dev_num)
self.name = name
self.max_dev_num = max_dev_num
def __str__(self):
return ("Too many PCI devices added on vm '%s', max supported '%s'" %
(self.name, str(self.max_dev_num)))
[docs]class VMUSBError(VMError):
pass
[docs]class VMUSBControllerError(VMUSBError):
pass
[docs]class VMUSBControllerMissingError(VMUSBControllerError):
def __init__(self, name, controller_type):
VMUSBControllerError.__init__(self, name, controller_type)
self.name = name
self.controller_type = controller_type
def __str__(self):
return ("Could not find '%s' USB Controller on vm '%s'. Please "
"check config files." % (self.controller_type, self.name))
[docs]class VMUSBControllerPortFullError(VMUSBControllerError):
def __init__(self, name, usb_dev_dict):
VMUSBControllerError.__init__(self, name, usb_dev_dict)
self.name = name
self.usb_dev_dict = usb_dev_dict
def __str__(self):
output = ""
try:
for ctl, dev_list in self.usb_dev_dict.iteritems():
output += "%s: %s\n" % (ctl, dev_list)
except Exception:
pass
return ("No available USB port left on VM %s.\n"
"USB devices map is: \n%s" % (self.name, output))
[docs]class VMUSBPortInUseError(VMUSBError):
def __init__(self, vm_name, controller, port):
VMUSBError.__init__(self, vm_name, controller, port)
self.vm_name = vm_name
self.controller = controller
self.port = port
def __str__(self):
return ("USB port '%d' of controller '%s' is already in use on vm"
" '%s'. Please assign another port in config file." %
(self.port, self.controller, self.vm_name))
[docs]class VMScreenInactiveError(VMError):
def __init__(self, vm, inactive_time):
VMError.__init__(self)
self.vm = vm
self.inactive_time = inactive_time
def __str__(self):
msg = ("%s screen is inactive for %d s (%d min)" %
(self.vm.name, self.inactive_time, self.inactive_time / 60))
return msg
[docs]class CpuInfo(object):
"""
A class for VM's cpu information.
"""
def __init__(self, model=None, vendor=None, flags=None, family=None,
smp=0, maxcpus=0, sockets=0, cores=0, threads=0):
"""
:param model: CPU Model of VM (use 'qemu -cpu ?' for list)
:param vendor: CPU Vendor of VM
:param flags: CPU Flags of VM
:param flags: CPU Family of VM
:param smp: set the number of CPUs to 'n' [default=1]
:param maxcpus: maximum number of total cpus, including
offline CPUs for hotplug, etc
:param cores: number of CPU cores on one socket
:param threads: number of threads on one CPU core
:param sockets: number of discrete sockets in the system
"""
self.model = model
self.vendor = vendor
self.flags = flags
self.family = family
self.smp = smp
self.maxcpus = maxcpus
self.sockets = sockets
self.cores = cores
self.threads = threads
[docs]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 preceded 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', ]
#
# Timeout definition. This is being kept inside the base class so that
# sub classes can change the default just for themselves
#
LOGIN_TIMEOUT = 10
LOGIN_WAIT_TIMEOUT = 240
COPY_FILES_TIMEOUT = 600
MIGRATE_TIMEOUT = 3600
REBOOT_TIMEOUT = 240
def __init__(self, name, params):
self.name = name
self.params = params
# Create instance if not already set
if not hasattr(self, 'instance'):
self._generate_unique_id()
# Don't overwrite existing state, update from params
if hasattr(self, 'virtnet'):
# Direct reference to self.virtnet makes pylint complain
# note: virtnet.__init__() supports being called anytime
getattr(self, 'virtnet').__init__(self.params,
self.name,
self.instance)
else: # Create new
self.virtnet = utils_net.VirtNet(self.params,
self.name,
self.instance)
if not hasattr(self, 'cpuinfo'):
self.cpuinfo = CpuInfo()
def _generate_unique_id(self):
"""
Generate a unique identifier for this VM
"""
while True:
self.instance = (time.strftime("%Y%m%d-%H%M%S-") +
utils_misc.generate_random_string(8))
if not glob.glob("/tmp/*%s" % self.instance):
break
[docs] def update_vm_id(self):
"""
Update vm identifier, we need do that when force reboot vm, since vm
virnet params may be changed.
"""
self._generate_unique_id()
@staticmethod
[docs] def lookup_vm_class(vm_type, target):
if vm_type == 'qemu':
import qemu_vm
return qemu_vm.VM
if vm_type == 'libvirt':
import libvirt_vm
return libvirt_vm.VM
if vm_type == 'v2v':
if target == 'libvirt' or target is None:
import libvirt_vm
return libvirt_vm.VM
if target == 'ovirt':
import ovirt
return ovirt.VMManager
#
# Public API - could be reimplemented with virt specific code
#
[docs] def needs_restart(self, name, params, basedir):
"""
Verifies whether the current virt_install commandline matches the
requested one, based on the test parameters.
"""
if not self.is_alive():
return True
try:
need_restart = (self.make_create_command() !=
self.make_create_command(name, params, basedir))
except Exception:
logging.error(traceback.format_exc())
need_restart = True
if need_restart:
logging.debug(
"VM params in env don't match requested, restarting.")
return True
else:
# Command-line encoded state doesn't include all params
# TODO: Check more than just networking
other_virtnet = utils_net.VirtNet(params, name, self.instance)
if self.virtnet != other_virtnet:
logging.debug("VM params in env match, but network differs, "
"restarting")
logging.debug("\t" + str(self.virtnet))
logging.debug("\t!=")
logging.debug("\t" + str(other_virtnet))
return True
else:
logging.debug(
"VM params in env do match requested, continuing.")
return False
[docs] 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
[docs] 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
"""
try:
mac = self.virtnet[nic_index].mac
return mac
except KeyError:
raise VMMACAddressMissingError(nic_index)
[docs] def get_address(self, index=0):
"""
Return the IP address of a NIC or guest (in host space).
:param index: Name or index of the NIC whose address is requested.
:return: 'localhost': Port redirection is in use
:return: IP address of NIC if valid in arp cache.
: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)
"""
nic = self.virtnet[index]
self.ip_version = self.params.get("ip_version", "ipv4").lower()
# TODO: Determine port redirection in use w/o checking nettype
if nic.nettype not in ['bridge', 'macvtap']:
hostname = socket.gethostname()
return socket.gethostbyname(hostname)
if not nic.has_key('mac'):
if self.params.get('vm_type') in ['libvirt', 'v2v']:
# Look it up from xml
nic.mac = self.get_virsh_mac_address(index)
# else TODO: Look up mac from existing qemu-kvm process
if not nic.has_key('mac'):
raise VMMACAddressMissingError(index)
if self.ip_version == "ipv4":
# Get the IP address from arp cache
arp_ip = self.address_cache.get(nic.mac.lower())
# Make sure IP is assigned to one or more macs for this guest
macs = self.virtnet.mac_list()
if not arp_ip:
arp_ip = self.address_cache.get(nic.mac.upper())
if (not arp_ip or
not utils_net.verify_ip_address_ownership(arp_ip, macs) or
os.geteuid() != 0):
# For non-root, tcpdump won't work for finding IP address,
# or IP missed in address_cache, try to find it from arp table.
ip_map = utils_net.parse_arp()
arp_ip = ip_map.get(nic.mac.lower())
if not arp_ip:
raise VMIPAddressMissingError(nic.mac)
nic_params = self.params.object_params(nic.nic_name)
pci_assignable = nic_params.get("pci_assignable") != "no"
if not utils_net.verify_ip_address_ownership(arp_ip, macs):
# SR-IOV/Macvtap cards may not be in same subnet with the cards
# used by host by default, so arp checks won't work. Therefore,
# do not raise VMAddressVerificationError when SR-IOV is used.
if pci_assignable or nic.nettype == "macvtap":
nic_backend = pci_assignable and "SR-IOV" or "macvtap"
msg = "Could not verify DHCP lease: %s-> %s." % (nic.mac,
arp_ip)
msg += " Maybe %s is not in the same subnet " % arp_ip
msg += "as the host (%s in use)" % nic_backend
logging.error(msg)
else:
raise VMAddressVerificationError(nic.mac, arp_ip)
logging.debug('Found/Verified IP %s for VM %s NIC %s',
arp_ip, self.name, str(index))
# Update address_cache since ip/mac releation ship verify pass
self.address_cache[nic.mac.lower()] = arp_ip
return arp_ip
elif self.ip_version == "ipv6":
# Try to get and return IPV6 address
if self.params.get('using_linklocal') == "yes":
ipv6_addr = utils_net.ipv6_from_mac_addr(nic.mac)
# Using global address
else:
mac_key = "%s_6" % nic.mac
ipv6_addr = self.address_cache.get(mac_key.lower())
if not ipv6_addr:
raise VMIPAddressMissingError(nic.mac)
# Check whether the ipv6 address is reachable
utils_net.refresh_neigh_table(nic.netdst, ipv6_addr)
if not utils_misc.wait_for(lambda: utils_net.neigh_reachable(
ipv6_addr, nic.netdst),
30, 0, 1, "Wait neighbour reachable"):
raise VMAddressVerificationError(nic.mac, ipv6_addr)
return ipv6_addr
[docs] def fill_addrs(self, addrs):
"""
Fill VM's nic address to the virtnet structure based on VM's address
structure addrs.
:param addrs: Dict of interfaces and address
::
{"if_name":{"mac":['addrs',],
"ipv4":['addrs',],
"ipv6":['addrs',]},
...}
"""
for virtnet in self.virtnet:
for iface_name, iface in addrs.iteritems():
if virtnet.mac in iface["mac"]:
virtnet.ip = {"ipv4": iface["ipv4"],
"ipv6": iface["ipv6"]}
virtnet.g_nic_name = iface_name
[docs] def get_port(self, port, nic_index=0):
"""
Return the port in host space corresponding to port in guest space.
:param port: Port number in host space.
:param nic_index: Index of the NIC.
:return: If port redirection is used, return the host port redirected
to guest port port. Otherwise return port.
:raise VMPortNotRedirectedError: If an unredirected port is requested
in user mode
"""
nic_nettype = self.virtnet[nic_index].nettype
if nic_nettype in ["bridge", "macvtap"]:
return port
else:
try:
return self.redirs[port]
except KeyError:
raise VMPortNotRedirectedError(port, self.virtnet[nic_index])
[docs] def free_mac_address(self, nic_index_or_name=0):
"""
Free a NIC's MAC address.
:param nic_index: Index of the NIC
"""
self.virtnet.free_mac_address(nic_index_or_name)
@error.context_aware
def wait_for_get_address(self, nic_index_or_name, timeout=30,
internal_timeout=1, ip_version='ipv4'):
"""
Wait for a nic to acquire an IP address, then return it.
For ipv6 linklocal address, we can generate it by nic mac,
so we can ignore this case
"""
# Don't let VMIPAddressMissingError/VMAddressVerificationError through
def _get_address():
try:
return self.get_address(nic_index_or_name)
except (VMIPAddressMissingError, VMAddressVerificationError):
return False
if not utils_misc.wait_for(_get_address, timeout, internal_timeout):
raise VMIPAddressMissingError(self.virtnet[nic_index_or_name].mac)
return self.get_address(nic_index_or_name)
# Adding/setup networking devices methods split between 'add_*' for
# setting up virtnet, and 'activate_' for performing actions based
# on settings.
[docs] def add_nic(self, **params):
"""
Add new or setup existing NIC with optional model type and mac address
:param params: Dict with additional NIC parameters to set.
:return: Dict with new NIC's info.
"""
if not params.has_key('nic_name'):
params['nic_name'] = utils_misc.generate_random_id()
nic_name = params['nic_name']
if nic_name in self.virtnet.nic_name_list():
self.virtnet[nic_name].update(**params)
else:
self.virtnet.append(params)
nic = self.virtnet[nic_name]
if not nic.has_key('mac'): # generate random mac
logging.debug("Generating random mac address for nic")
self.virtnet.generate_mac_address(nic_name)
# mac of '' or invaid format results in not setting a mac
if nic.has_key('ip') and nic.has_key('mac'):
if not self.address_cache.has_key(nic.mac):
logging.debug("(address cache) Adding static "
"cache entry: %s ---> %s" % (nic.mac, nic.ip))
else:
logging.debug("(address cache) Updating static "
"cache entry from: %s ---> %s"
" to: %s ---> %s" % (nic.mac,
self.address_cache[nic.mac], nic.mac, nic.ip))
self.address_cache[nic.mac] = nic.ip
return nic
[docs] def del_nic(self, nic_index_or_name):
"""
Remove the nic specified by name, or index number
"""
nic = self.virtnet[nic_index_or_name]
nic_mac = nic.mac.lower()
self.free_mac_address(nic_index_or_name)
try:
del self.virtnet[nic_index_or_name]
del self.address_cache[nic_mac]
except IndexError:
pass # continue to not exist
except KeyError:
pass # continue to not exist
[docs] def verify_kernel_crash(self):
"""
Find kernel crash message on the VM serial console.
:raise: VMDeadKernelCrashError, in case a kernel crash message was
found.
"""
panic_re = [r"BUG:.*---\[ end trace .* \]---"]
panic_re.append(r"----------\[ cut here.* BUG .*\[ end trace .* \]---")
panic_re.append(r"general protection fault:.* RSP.*>")
panic_re = "|".join(panic_re)
if self.serial_console is not None:
data = self.serial_console.get_output()
match = re.search(panic_re, data, re.DOTALL | re.MULTILINE | re.I)
if match is not None:
raise VMDeadKernelCrashError(match.group(0))
[docs] def verify_bsod(self, scrdump_file):
# For windows guest
if (os.path.exists(scrdump_file) and
self.params.get("check_guest_bsod", "no") == 'yes' and
ppm_utils.Image is not None):
ref_img_path = self.params.get("bsod_reference_img", "")
bsod_base_dir = os.path.join(data_dir.get_root_dir(),
"shared", "deps",
"bsod_img")
ref_img = utils_misc.get_path(bsod_base_dir, ref_img_path)
if ppm_utils.have_similar_img(scrdump_file, ref_img):
err_msg = "Windows Guest appears to have suffered a BSOD,"
err_msg += " please check %s against %s." % (scrdump_file, ref_img)
raise VMDeadKernelCrashError(err_msg)
[docs] def verify_illegal_instruction(self):
"""
Find illegal instruction code on VM serial console output.
:raise: VMInvalidInstructionCode, in case a wrong instruction code.
"""
if self.serial_console is not None:
data = self.serial_console.get_output()
match = re.findall(r".*trap invalid opcode.*\n", data,
re.MULTILINE)
if match:
raise VMInvalidInstructionCode(match)
[docs] def get_params(self):
"""
Return the VM's params dict. Most modified params take effect only
upon VM.create().
"""
return self.params
[docs] def get_testlog_filename(self):
"""
Return the testlog filename.
"""
return "/tmp/testlog-%s" % self.instance
[docs] def get_virtio_port_filename(self, port_name):
"""
Return the filename corresponding to a givven monitor name.
"""
return "/tmp/virtio_port-%s-%s" % (port_name, self.instance)
[docs] def get_virtio_port_filenames(self):
"""
Return a list of all virtio port filenames (as specified in the VM's
params).
"""
return [self.get_virtio_port_filename(v) for v in
self.params.objects("virtio_ports")]
@error.context_aware
def login(self, nic_index=0, timeout=LOGIN_TIMEOUT,
username=None, password=None):
"""
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)
if not username:
username = self.params.get("username", "")
if not password:
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")
neigh_attach_if = ""
address = self.get_address(nic_index)
if address and address.lower().startswith("fe80"):
neigh_attach_if = utils_net.get_neigh_attch_interface(address)
port = self.get_port(int(self.params.get("shell_port")))
log_filename = ("session-%s-%s.log" %
(self.name, utils_misc.generate_random_string(4)))
session = remote.remote_login(client, address, port, username,
password, prompt, linesep,
log_filename, timeout,
interface=neigh_attach_if)
session.set_status_test_command(self.params.get("status_test_command",
""))
self.remote_sessions.append(session)
return session
[docs] def remote_login(self, nic_index=0, timeout=LOGIN_TIMEOUT,
username=None, password=None):
"""
Alias for login() for backward compatibility.
"""
return self.login(nic_index, timeout, username, password)
@error.context_aware
def commander(self, nic_index=0, timeout=LOGIN_TIMEOUT,
username=None, password=None, commander_path=None):
"""
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.
:param commaner_path: Path where will be commader placed.
:return: A ShellSession object.
"""
if commander_path is None:
commander_path = "/tmp"
error.context("logging into '%s'" % self.name)
if not username:
username = self.params.get("username", "")
if not password:
password = self.params.get("password", "")
prompt = "^\s*#"
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 = None
import remote_commander as rc
path = os.path.dirname(rc.__file__)
f_path = " ".join((os.path.join(path, _) for _ in
("remote_runner.py", "remote_interface.py",
"messenger.py")))
self.copy_files_to(f_path, commander_path)
# start remote commander
cmd = remote.remote_commander(client, address, port, username,
password, prompt, linesep, log_filename,
timeout, commander_path)
self.remote_sessions.append(cmd)
return cmd
[docs] def remote_commander(self, nic_index=0, timeout=LOGIN_TIMEOUT,
username=None, password=None):
"""
Alias for commander() for backward compatibility.
"""
return self.commander(nic_index, timeout, username, password)
[docs] def wait_for_login(self, nic_index=0, timeout=LOGIN_WAIT_TIMEOUT,
internal_timeout=LOGIN_TIMEOUT,
serial=False, restart_network=False,
username=None, password=None):
"""
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().
:param serial: Whether to use a serial connection when remote login
(ssh, rss) failed.
:param restart_network: Whether to try to restart guest's network
when remote login (ssh, rss) failed.
:return: A ShellSession object.
"""
error_messages = []
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,
username, password)
except (remote.LoginError, VMError), e:
self.verify_alive()
e = str(e)
if e not in error_messages:
logging.debug(e)
error_messages.append(e)
time.sleep(2)
# Timeout expired
logging.info("Try to get guest network status.")
s_session = self.wait_for_serial_login(30, internal_timeout,
username=username,
password=password)
if s_session:
output = s_session.cmd_output("ipconfig || ifconfig", timeout=60)
txt = "Guest network status:\n %s" % output
logging.debug(txt)
out = s_session.cmd_output("ip route || route print", timeout=60)
txt = "Guest route table:\n %s" % out
logging.debug(txt)
if serial:
return s_session
if restart_network:
os_type = self.params.get("os_type")
utils_net.restart_guest_network(s_session, os_type=os_type)
# Try one more time after restarting guest network.
session = self.login(nic_index, internal_timeout, username,
password)
s_session.close()
return session
else:
# Close serial session
s_session.close()
# TODO Need to clean up existed serial console,
# otherwise, following serial login will fail. Any other solution?
if self.serial_console:
self.cleanup_serial_console()
# In the case of address is changed, update arp cache
utils_net.update_mac_ip_address(self, self.params)
# Try one more time but don't catch exceptions
return self.login(nic_index, internal_timeout, username, password)
@error.context_aware
def copy_files_to(self, host_path, guest_path, nic_index=0, limit="",
verbose=False, timeout=COPY_FILES_TIMEOUT,
username=None, password=None):
"""
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 limit: Speed limit of file transfer.
: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)
if not username:
username = self.params.get("username", "")
if not password:
password = self.params.get("password", "")
client = self.params.get("file_transfer_client")
address = self.get_address(nic_index)
neigh_attach_if = ""
if address.lower().startswith("fe80"):
neigh_attach_if = utils_net.get_neigh_attch_interface(address)
port = self.get_port(int(self.params.get("file_transfer_port")))
log_filename = ("transfer-%s-to-%s-%s.log" %
(self.name, address,
utils_misc.generate_random_string(4)))
remote.copy_files_to(address, client, username, password, port,
host_path, guest_path, limit, log_filename,
verbose, timeout, interface=neigh_attach_if)
utils_misc.close_log_file(log_filename)
@error.context_aware
def copy_files_from(self, guest_path, host_path, nic_index=0, limit="",
verbose=False, timeout=COPY_FILES_TIMEOUT,
username=None, password=None):
"""
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 limit: Speed limit of file transfer.
: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)
if not username:
username = self.params.get("username", "")
if not password:
password = self.params.get("password", "")
client = self.params.get("file_transfer_client")
address = self.get_address(nic_index)
neigh_attach_if = ""
if address.lower().startswith("fe80"):
neigh_attach_if = utils_net.get_neigh_attch_interface(address)
port = self.get_port(int(self.params.get("file_transfer_port")))
log_filename = ("transfer-%s-from-%s-%s.log" %
(self.name, address,
utils_misc.generate_random_string(4)))
remote.copy_files_from(address, client, username, password, port,
guest_path, host_path, limit, log_filename,
verbose, timeout, interface=neigh_attach_if)
utils_misc.close_log_file(log_filename)
[docs] def create_serial_console(self):
"""
Establish a session with the serial console.
Let's consider the first serial port as serial console.
Note: requires a version of netcat that supports -U
"""
raise NotImplementedError
[docs] def cleanup_serial_console(self):
"""
Close serial console and associated log file
"""
raise NotImplementedError
[docs] def create_virtio_console(self):
"""
Establish a session with the virtio console.
"""
raise NotImplementedError
@error.context_aware
def serial_login(self, timeout=LOGIN_TIMEOUT,
username=None, password=None, virtio=False):
"""
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.
:param virtio: is a console virtio console.
:return: ShellSession object on success and None on failure.
"""
console_type = virtio and "virtio_console" or "serial_console"
error.context("logging into '%s' via %s" % (self.name, console_type))
if not username:
username = self.params.get("username", "")
if not password:
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", "")
console = getattr(self, console_type)
# Some times need recreate the console.
if not (console and os.path.exists(console.inpipe_filename)):
create_console = getattr(self, "create_%s" % console_type)
create_console()
console = getattr(self, console_type)
console.set_linesep(linesep)
console.set_status_test_command(status_test_command)
# Try to get a login prompt
console.sendline()
remote.handle_prompts(console, username, password, prompt, timeout)
return console
[docs] def wait_for_serial_login(self, timeout=LOGIN_WAIT_TIMEOUT,
internal_timeout=LOGIN_TIMEOUT,
restart_network=False,
username=None, password=None, virtio=False):
"""
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().
:param restart_network: Whether try to restart guest's network.
:params virtio: is a virtio console.
:return: A ShellSession object.
"""
error_messages = []
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:
session = self.serial_login(internal_timeout, username,
password, virtio=virtio)
if restart_network:
os_type = self.params.get("os_type")
try:
logging.debug("Attempting to restart guest network")
utils_net.restart_guest_network(session,
os_type=os_type)
except Exception:
pass
return session
except remote.LoginError, e:
self.verify_alive()
e = str(e)
if e not in error_messages:
logging.debug(e)
error_messages.append(e)
time.sleep(2)
session = self.serial_login(internal_timeout, username,
password, virtio=virtio)
return session
[docs] 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)
[docs] def send_string(self, sr):
"""
Send a string to the VM.
:param sr: String, that must consist of alphanumeric characters only.
Capital letters are allowed.
"""
for char in sr:
if char.isupper():
self.send_key("shift-%s" % char.lower())
else:
self.send_key(char)
[docs] def get_cpu_count(self):
"""
Get the cpu count of the VM.
"""
session = self.wait_for_login()
cmd = self.params.get("cpu_chk_cmd")
try:
out = session.cmd_output_safe(cmd)
return int(re.search("\d+", out, re.M).group())
finally:
session.close()
[docs] def get_memory_size(self, cmd=None, timeout=60, re_str=None):
"""
Get bootup memory size of the VM.
:param cmd: Command used to check memory. If not provided,
self.params.get("mem_chk_cmd") will be used.
:param timeout: timeout for cmd
:param re_str: pattern to get memory size from the command
output. If not provided,
self.params.get("mem_chk_re_str") will be
used.
"""
session = self.login()
if re_str is None:
re_str = self.params.get("mem_chk_re_str", "([0-9]+)")
try:
if not cmd:
cmd = self.params.get("mem_chk_cmd")
mem_str = session.cmd_output(cmd, timeout=timeout)
mem = re.findall(re_str, 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()
[docs] 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
#
[docs] def is_alive(self):
"""
Return True if the VM is alive and the management interface is responsive.
"""
raise NotImplementedError
[docs] def is_dead(self):
"""
Return True if the VM is dead.
"""
raise NotImplementedError
[docs] def is_paused(self):
"""
Return True if the VM is paused
"""
raise NotImplementedError
[docs] def activate_nic(self, nic_index_or_name):
"""
Activate an inactive network device
:param nic_index_or_name: name or index number for existing NIC
"""
raise NotImplementedError
[docs] def deactivate_nic(self, nic_index_or_name):
"""
Deactivate an active network device
:param nic_index_or_name: name or index number for existing NIC
"""
raise NotImplementedError
[docs] def verify_userspace_crash(self):
"""
Verify if the userspace component of the virtualization backend crashed.
"""
pass
[docs] def clone(self, name, **params):
"""
Return a clone of the VM object with optionally modified parameters.
This method should be implemented by
"""
raise NotImplementedError
[docs] 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
[docs] def migrate(self, timeout=MIGRATE_TIMEOUT, 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).
:param 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
[docs] def reboot(self, session=None, method="shell", nic_index=0,
timeout=REBOOT_TIMEOUT, serial=False):
"""
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).
:param serial: Serial port login or not (default is False).
:return: A new shell session object.
"""
raise NotImplementedError
# should this really be expected from VMs of all hypervisor types?
[docs] 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
[docs] def save_to_file(self, path):
"""
State of paused VM recorded to path and VM shutdown on success
Throws a VMStatusError if before/after state is incorrect.
:param path: file where VM state recorded
"""
raise NotImplementedError
[docs] def restore_from_file(self, path):
"""
A shutdown or paused VM is resumed from path, & possibly set running
Throws a VMStatusError if before/after restore state is incorrect
:param path: path to file vm state was saved to
"""
raise NotImplementedError
[docs] def savevm(self, tag_name):
"""
Save the virtual machine as the tag 'tag_name'
:param tag_name: tag of the virtual machine that saved
"""
raise NotImplementedError
[docs] def loadvm(self, tag_name):
"""
Load the virtual machine tagged 'tag_name'.
:param tag_name: tag of the virtual machine that saved
"""
raise NotImplementedError
[docs] def pause(self):
"""
Stop the VM operation.
"""
raise NotImplementedError
[docs] def resume(self):
"""
Resume the VM operation in case it's stopped.
"""
raise NotImplementedError