"""
Utility classes and functions to handle Virtual Machine creation using qemu.
:copyright: 2008-2009, 2014 Red Hat Inc.
"""
import time
import os
import logging
import fcntl
import re
import commands
from autotest.client.shared import error
from autotest.client import utils
from virttest.qemu_devices import qdevices, qcontainer
import utils_misc
import virt_vm
import test_setup
import qemu_monitor
import aexpect
import qemu_virtio_port
import remote
import data_dir
import utils_net
import arch
import storage
[docs]class QemuSegFaultError(virt_vm.VMError):
def __init__(self, crash_message):
virt_vm.VMError.__init__(self, crash_message)
self.crash_message = crash_message
def __str__(self):
return ("Qemu crashed: %s" % self.crash_message)
[docs]class VMMigrateProtoUnsupportedError(virt_vm.VMMigrateProtoUnknownError):
"""
When QEMU tells us it doesn't know about a given migration protocol.
This usually happens when we're testing older QEMU. It makes sense to
skip the test in this situation.
"""
def __init__(self, protocol, output):
self.protocol = protocol
self.output = output
def __str__(self):
return ("QEMU reports it doesn't know migration protocol '%s'. "
"QEMU output: %s" % (self.protocol, self.output))
[docs]class KVMInternalError(virt_vm.VMError):
pass
[docs]class ImageUnbootableError(virt_vm.VMError):
def __init__(self, name):
virt_vm.VMError.__init__(self, name)
self.name = name
def __str__(self):
return ("VM '%s' can't bootup from image,"
" check your boot disk image file." % self.name)
[docs]def clean_tmp_files():
if os.path.isfile(CREATE_LOCK_FILENAME):
os.unlink(CREATE_LOCK_FILENAME)
CREATE_LOCK_FILENAME = os.path.join('/tmp', 'virt-test-vm-create.lock')
[docs]class VM(virt_vm.BaseVM):
"""
This class handles all basic VM operations.
"""
MIGRATION_PROTOS = ['rdma', 'x-rdma', 'tcp', 'unix', 'exec', 'fd']
# By default we inherit all timeouts from the base VM class except...
CLOSE_SESSION_TIMEOUT = 30
def __init__(self, name, params, root_dir, address_cache, state=None):
"""
Initialize the object and set a few attributes.
:param name: The name of the object
:param params: A dict containing VM params
(see method make_qemu_command for a full description)
:param root_dir: Base directory for relative filenames
:param address_cache: A dict that maps MAC addresses to IP addresses
:param state: If provided, use this as self.__dict__
"""
if state:
self.__dict__ = state
else:
self.process = None
self.serial_ports = []
self.serial_console = None
self.virtio_console = None
self.redirs = {}
self.spice_options = {}
self.vnc_port = 5900
self.monitors = []
self.virtio_ports = [] # virtio_console / virtio_serialport
self.pci_assignable = None
self.uuid = None
self.vcpu_threads = []
self.vhost_threads = []
self.devices = None
self.logs = {}
self.remote_sessions = []
self.logsessions = {}
self.name = name
self.params = params
self.root_dir = root_dir
self.ip_version = self.params.get("ip_version", "ipv4")
self.address_cache = address_cache
self.index_in_use = {}
# This usb_dev_dict member stores usb controller and device info,
# It's dict, each key is an id of usb controller,
# and key's value is a list, contains usb devices' ids which
# attach to this controller.
# A filled usb_dev_dict may look like:
# { "usb1" : ["stg1", "stg2", "stg3", "stg4", "stg5", "stg6"],
# "usb2" : ["stg7", "stg8"],
# ...
# }
# This structure can used in usb hotplug/unplug test.
self.usb_dev_dict = {}
self.driver_type = 'qemu'
self.params['driver_type_' + self.name] = self.driver_type
# virtnet init depends on vm_type/driver_type being set w/in params
super(VM, self).__init__(name, params)
# un-overwrite instance attribute, virtnet db lookups depend on this
if state:
self.instance = state['instance']
self.qemu_command = ''
self.start_time = 0.0
self.start_monotonic_time = 0.0
self.last_boot_index = 0
self.last_driver_index = 0
[docs] 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
"""
self.verify_disk_image_bootable()
self.verify_userspace_crash()
self.verify_kernel_crash()
self.verify_illegal_instruction()
self.verify_kvm_internal_error()
try:
virt_vm.BaseVM.verify_alive(self)
if self.monitor:
self.monitor.verify_responsive()
except virt_vm.VMDeadError:
raise virt_vm.VMDeadError(self.process.get_status(),
self.process.get_output())
[docs] def is_alive(self):
"""
Return True if the VM is alive and its monitor is responsive.
"""
return not self.is_dead() and (not self.catch_monitor or
self.catch_monitor.is_responsive())
[docs] def is_dead(self):
"""
Return True if the qemu process is dead.
"""
return not self.process or not self.process.is_alive()
[docs] def is_paused(self):
"""
Return True if the qemu process is paused ('stop'ed)
"""
if self.is_dead():
return False
try:
self.verify_status("paused")
return True
except virt_vm.VMStatusError:
return False
[docs] def verify_status(self, status):
"""
Check VM status
:param status: Optional VM status, 'running' or 'paused'
:raise VMStatusError: If the VM status is not same as parameter
"""
if not self.monitor.verify_status(status):
raise virt_vm.VMStatusError('Unexpected VM status: "%s"' %
self.monitor.get_status())
[docs] def verify_userspace_crash(self):
"""
Verify if the userspace component (qemu) crashed.
"""
if "(core dumped)" in self.process.get_output():
for line in self.process.get_output().splitlines():
if "(core dumped)" in line:
raise QemuSegFaultError(line)
[docs] def verify_kvm_internal_error(self):
"""
Verify KVM internal error.
"""
if "KVM internal error." in self.process.get_output():
out = self.process.get_output()
out = out[out.find("KVM internal error."):]
raise KVMInternalError(out)
[docs] def verify_disk_image_bootable(self):
if self.params.get("image_verify_bootable") == "yes":
pattern = self.params.get("image_unbootable_pattern")
if not pattern:
raise virt_vm.VMConfigMissingError(self.name,
"image_unbootable_pattern")
try:
seabios_log = self.logsessions['seabios'].get_output()
if re.search(pattern, seabios_log, re.S):
logging.error("Can't boot guest from image.")
# Set 'shutdown_command' to None to force autotest
# shuts down guest with monitor.
self.params["shutdown_command"] = None
raise ImageUnbootableError(self.name)
except KeyError:
pass
[docs] def clone(self, name=None, params=None, root_dir=None, address_cache=None,
copy_state=False):
"""
Return a clone of the VM object with optionally modified parameters.
The clone is initially not alive and needs to be started using create().
Any parameters not passed to this function are copied from the source
VM.
:param name: Optional new VM name
:param params: Optional new VM creation parameters
:param root_dir: Optional new base directory for relative filenames
:param address_cache: A dict that maps MAC addresses to IP addresses
:param copy_state: If True, copy the original VM's state to the clone.
Mainly useful for make_qemu_command().
"""
if name is None:
name = self.name
if params is None:
params = self.params.copy()
if root_dir is None:
root_dir = self.root_dir
if address_cache is None:
address_cache = self.address_cache
if copy_state:
state = self.__dict__.copy()
else:
state = None
return VM(name, params, root_dir, address_cache, state)
[docs] def get_serial_console_filename(self, name=None):
"""
Return the serial console filename.
:param name: The serial port name.
"""
if name:
return "/tmp/serial-%s-%s" % (name, self.instance)
return "/tmp/serial-%s" % self.instance
[docs] def get_serial_console_filenames(self):
"""
Return a list of all serial console filenames
(as specified in the VM's params).
"""
return [self.get_serial_console_filename(_) for _ in
self.params.objects("serials")]
[docs] def get_virtio_port_filenames(self):
"""
Get socket file of virtio ports
"""
return [_.hostfile for _ in self.virtio_ports]
[docs] def cleanup_serial_console(self):
"""
Close serial console and associated log file
"""
for console_type in ["virtio_console", "serial_console"]:
if hasattr(self, console_type):
console = getattr(self, console_type)
if console:
console.close()
console = None
if hasattr(self, "migration_file"):
try:
os.unlink(self.migration_file)
except OSError:
pass
[docs] def make_create_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
class attributes is used.
:param name: The name of the object
:param params: A dict containing VM params
:param root_dir: Base directory for relative filenames
:note: The params dict should contain:
mem -- memory size in MBs
cdrom -- ISO filename to use with the qemu -cdrom parameter
extra_params -- a string to append to the qemu command
shell_port -- port of the remote shell daemon on the guest
(SSH, Telnet or the home-made Remote Shell Server)
shell_client -- client program to use for connecting to the
remote shell daemon on the guest (ssh, telnet or nc)
x11_display -- if specified, the DISPLAY environment variable
will be be set to this value for the qemu process (useful for
SDL rendering)
images -- a list of image object names, separated by spaces
nics -- a list of NIC object names, separated by spaces
For each image in images:
drive_format -- string to pass as 'if' parameter for this
image (e.g. ide, scsi)
image_snapshot -- if yes, pass 'snapshot=on' to qemu for
this image
image_boot -- if yes, pass 'boot=on' to qemu for this image
In addition, all parameters required by get_image_filename.
For each NIC in nics:
nic_model -- string to pass as 'model' parameter for this
NIC (e.g. e1000)
"""
# Helper function for command line option wrappers
def _add_option(option, value, option_type=None, first=False):
"""
Add option to qemu parameters.
"""
if first:
fmt = " %s=%s"
else:
fmt = ",%s=%s"
if option_type is bool:
# Decode value for bool parameter (supports True, False, None)
if value in ['yes', 'on', True]:
return fmt % (option, "on")
elif value in ['no', 'off', False]:
return fmt % (option, "off")
elif value and isinstance(value, bool):
return fmt % (option, "on")
elif value and isinstance(value, str):
# "EMPTY_STRING" and "NULL_STRING" is used for testing illegal
# foramt of option.
# "EMPTY_STRING": set option as a empty string "".
# "NO_EQUAL_STRING": set option as a option string only,
# even without "=".
# (In most case, qemu-kvm should recognize it as "<null>")
if value == "NO_EQUAL_STRING":
return ",%s" % option
if value == "EMPTY_STRING":
value = '""'
return fmt % (option, str(value))
return ""
# Wrappers for all supported qemu command line parameters.
# This is meant to allow support for multiple qemu versions.
# Each of these functions receives the output of 'qemu -help'
# as a parameter, and should add the requested command line
# option accordingly.
def add_name(devices, name):
return " -name '%s'" % name
def process_sandbox(devices, action):
if action == "add":
if devices.has_option("sandbox"):
return " -sandbox on "
elif action == "rem":
if devices.has_option("sandbox"):
return " -sandbox off "
def add_human_monitor(devices, monitor_name, filename):
if not devices.has_option("chardev"):
return " -monitor unix:'%s',server,nowait" % filename
monitor_id = "hmp_id_%s" % monitor_name
cmd = " -chardev socket"
cmd += _add_option("id", monitor_id)
cmd += _add_option("path", filename)
cmd += _add_option("server", "NO_EQUAL_STRING")
cmd += _add_option("nowait", "NO_EQUAL_STRING")
cmd += " -mon chardev=%s" % monitor_id
cmd += _add_option("mode", "readline")
return cmd
def add_qmp_monitor(devices, monitor_name, filename):
if not devices.has_option("qmp"):
logging.warn("Fallback to human monitor since qmp is"
" unsupported")
return add_human_monitor(devices, monitor_name, filename)
if not devices.has_option("chardev"):
return " -qmp unix:'%s',server,nowait" % filename
monitor_id = "qmp_id_%s" % monitor_name
cmd = " -chardev socket"
cmd += _add_option("id", monitor_id)
cmd += _add_option("path", filename)
cmd += _add_option("server", "NO_EQUAL_STRING")
cmd += _add_option("nowait", "NO_EQUAL_STRING")
cmd += " -mon chardev=%s" % monitor_id
cmd += _add_option("mode", "control")
return cmd
def add_serial(devices, name, filename):
if arch.ARCH == 'aarch64' or not devices.has_option("chardev"):
return " -serial unix:'%s',server,nowait" % filename
serial_id = "serial_id_%s" % name
cmd = " -chardev socket"
cmd += _add_option("id", serial_id)
cmd += _add_option("path", filename)
cmd += _add_option("server", "NO_EQUAL_STRING")
cmd += _add_option("nowait", "NO_EQUAL_STRING")
if '86' in arch.ARCH:
cmd += " -device isa-serial"
elif 'ppc' in arch.ARCH:
cmd += " -device spapr-vty"
# Workaround for console issue, details:
# lists.gnu.org/archive/html/qemu-ppc/2013-10/msg00129.html
cmd += _add_option("reg", "0x30000000")
cmd += _add_option("chardev", serial_id)
return cmd
def add_virtio_port(devices, name, bus, filename, porttype, chardev,
name_prefix=None, index=None, extra_params=""):
"""
Appends virtio_serialport or virtio_console device to cmdline.
:param help: qemu -h output
:param name: Name of the port
:param bus: Which virtio-serial-pci device use
:param filename: Path to chardev filename
:param porttype: Type of the port (*serialport, console)
:param chardev: Which chardev to use (*socket, spicevmc)
:param name_prefix: Custom name prefix (port index is appended)
:param index: Index of the current virtio_port
:param extra_params: Space sepparated chardev params
"""
cmd = ''
# host chardev
if chardev == "spicevmc": # SPICE
cmd += " -chardev spicevmc,id=dev%s,name=%s" % (name, name)
else: # SOCKET
cmd = (" -chardev socket,id=dev%s,path=%s,server,nowait"
% (name, filename))
# virtport device
if porttype in ("console", "virtio_console"):
cmd += " -device virtconsole"
else:
cmd += " -device virtserialport"
if name_prefix: # used by spiceagent (com.redhat.spice.*)
port_name = "%s%d" % (name_prefix, index)
else:
port_name = name
cmd += ",chardev=dev%s,name=%s,id=%s" % (name, port_name, name)
cmd += _add_option("bus", bus)
# Space sepparated chardev params
_params = ""
for parm in extra_params.split():
_params += ',' + parm
cmd += _params
return cmd
def add_log_seabios(devices):
if not devices.has_device("isa-debugcon"):
return ""
default_id = "seabioslog_id_%s" % self.instance
filename = "/tmp/seabios-%s" % self.instance
self.logs["seabios"] = filename
cmd = " -chardev socket"
cmd += _add_option("id", default_id)
cmd += _add_option("path", filename)
cmd += _add_option("server", "NO_EQUAL_STRING")
cmd += _add_option("nowait", "NO_EQUAL_STRING")
cmd += " -device isa-debugcon"
cmd += _add_option("chardev", default_id)
cmd += _add_option("iobase", "0x402")
return cmd
def add_log_anaconda(devices, pci_bus='pci.0'):
chardev_id = "anacondalog_chardev_%s" % self.instance
vioser_id = "anacondalog_vioser_%s" % self.instance
filename = "/tmp/anaconda-%s" % self.instance
self.logs["anaconda"] = filename
dev = qdevices.QCustomDevice('chardev', backend='backend')
dev.set_param('backend', 'socket')
dev.set_param('id', chardev_id)
dev.set_param("path", filename)
dev.set_param("server", 'NO_EQUAL_STRING')
dev.set_param("nowait", 'NO_EQUAL_STRING')
devices.insert(dev)
if arch.ARCH == 'aarch64':
dev = QDevice('virtio-serial-device')
else:
dev = QDevice('virtio-serial-pci', parent_bus=pci_bus)
dev.set_param("id", vioser_id)
devices.insert(dev)
dev = QDevice('virtserialport')
dev.set_param("bus", "%s.0" % vioser_id)
dev.set_param("chardev", chardev_id)
dev.set_param("name", "org.fedoraproject.anaconda.log.0")
devices.insert(dev)
def add_mem(devices, mem):
return " -m %s" % mem
def add_smp(devices):
smp_str = " -smp %d" % self.cpuinfo.smp
smp_pattern = "smp .*n\[,maxcpus=cpus\].*"
if devices.has_option(smp_pattern):
smp_str += ",maxcpus=%d" % self.cpuinfo.maxcpus
smp_str += ",cores=%d" % self.cpuinfo.cores
smp_str += ",threads=%d" % self.cpuinfo.threads
smp_str += ",sockets=%d" % self.cpuinfo.sockets
return smp_str
def add_nic(devices, vlan, model=None, mac=None, device_id=None,
netdev_id=None, nic_extra_params=None, pci_addr=None,
bootindex=None, queues=1, vectors=None, pci_bus='pci.0',
ctrl_mac_addr=None):
if model == 'none':
return
if devices.has_option("device"):
if not model:
model = "rtl8139"
elif model == "virtio":
model = "virtio-net-pci"
dev = QDevice(model)
if ctrl_mac_addr and ctrl_mac_addr in ["on", "off"]:
dev.set_param('ctrl_mac_addr', ctrl_mac_addr)
dev.set_param('mac', mac, dynamic=True)
# only pci domain=0,bus=0,function=0 is supported for now.
#
# libvirt gains the pci_slot, free_pci_addr here,
# value by parsing the xml file, i.e. counting all the
# pci devices and store the number.
if model == 'virtio-net-device':
dev.parent_bus = {'type': 'virtio-bus'}
elif model != 'spapr-vlan':
dev.parent_bus = pci_bus
dev.set_param('addr', pci_addr)
if nic_extra_params:
nic_extra_params = (_.split('=', 1) for _ in
nic_extra_params.split(',') if _)
for key, val in nic_extra_params:
dev.set_param(key, val)
dev.set_param("bootindex", bootindex)
else:
dev = qdevices.QCustomDevice('net', backend='type')
dev.set_param('type', 'nic')
dev.set_param('model', model)
dev.set_param('macaddr', mac, 'NEED_QUOTE', True)
dev.set_param('id', device_id, 'NEED_QUOTE')
if "virtio" in model:
if int(queues) > 1:
dev.set_param('mq', 'on')
if vectors:
dev.set_param('vectors', vectors)
if devices.has_option("netdev"):
dev.set_param('netdev', netdev_id)
else:
dev.set_param('vlan', vlan)
devices.insert(dev)
def add_net(devices, vlan, nettype, ifname=None, tftp=None,
bootfile=None, hostfwd=[], netdev_id=None,
netdev_extra_params=None, tapfds=None, script=None,
downscript=None, vhost=None, queues=None, vhostfds=None,
add_queues=None, helper=None, add_tapfd=None,
add_vhostfd=None, vhostforce=None):
mode = None
if nettype in ['bridge', 'network', 'macvtap']:
mode = 'tap'
elif nettype == 'user':
mode = 'user'
else:
logging.warning("Unknown/unsupported nettype %s" % nettype)
return ''
if devices.has_option("netdev"):
cmd = " -netdev %s,id=%s" % (mode, netdev_id)
cmd_nd = cmd
if vhost:
if vhost in ["on", "off"]:
cmd += ",vhost=%s" % vhost
elif vhost == "vhost=on": # Keeps compatibility with old.
cmd += ",%s" % vhost
cmd_nd = cmd
if vhostfds:
if (int(queues) > 1 and
'vhostfds=' in devices.get_help_text()):
cmd += ",vhostfds=%(vhostfds)s"
cmd_nd += ",vhostfds=DYN"
else:
txt = ""
if int(queues) > 1:
txt = "qemu do not support vhost multiqueue,"
txt += " Fall back to single queue."
if 'vhostfd=' in devices.get_help_text():
cmd += ",vhostfd=%(vhostfd)s"
cmd_nd += ",vhostfd=DYN"
else:
txt += " qemu do not support vhostfd."
if txt:
logging.warn(txt)
# For negative test
if add_vhostfd:
cmd += ",vhostfd=%(vhostfd)s"
cmd_nd += ",vhostfd=%(vhostfd)s"
if vhostforce in ["on", "off"]:
cmd += ",vhostforce=%s" % vhostforce
cmd_nd = cmd
if netdev_extra_params:
cmd += "%s" % netdev_extra_params
cmd_nd += "%s" % netdev_extra_params
else:
cmd = " -net %s,vlan=%d" % (mode, vlan)
cmd_nd = cmd
if mode == "tap":
if script:
cmd += ",script='%s'" % script
cmd += ",downscript='%s'" % (downscript or "no")
cmd_nd = cmd
if ifname:
cmd += ",ifname='%s'" % ifname
cmd_nd = cmd
elif tapfds:
if (int(queues) > 1 and
',fds=' in devices.get_help_text()):
cmd += ",fds=%(tapfds)s"
cmd_nd += ",fds=DYN"
else:
cmd += ",fd=%(tapfd)s"
cmd_nd += ",fd=DYN"
# For negative test
if add_tapfd:
cmd += ",fd=%(tapfd)s"
cmd_nd += ",fd=%(tapfd)s"
elif mode == "user":
if tftp and "[,tftp=" in devices.get_help_text():
cmd += ",tftp='%s'" % tftp
cmd_nd = cmd
if bootfile and "[,bootfile=" in devices.get_help_text():
cmd += ",bootfile='%s'" % bootfile
cmd_nd = cmd
if "[,hostfwd=" in devices.get_help_text():
for i in xrange(len(hostfwd)):
cmd += (",hostfwd=tcp::%%(host_port%d)s"
"-:%%(guest_port%d)s" % (i, i))
cmd_nd += ",hostfwd=tcp::DYN-:%%(guest_port)ds"
if add_queues and queues:
cmd += ",queues=%s" % queues
cmd_nd += ",queues=%s" % queues
if helper:
cmd += ",helper=%s" % helper
cmd_nd += ",helper=%s" % helper
return cmd, cmd_nd
def add_floppy(devices, filename, index):
cmd_list = [" -fda '%s'", " -fdb '%s'"]
return cmd_list[index] % filename
def add_tftp(devices, filename):
# If the new syntax is supported, don't add -tftp
if "[,tftp=" in devices.get_help_text():
return ""
else:
return " -tftp '%s'" % filename
def add_bootp(devices, filename):
# If the new syntax is supported, don't add -bootp
if "[,bootfile=" in devices.get_help_text():
return ""
else:
return " -bootp '%s'" % filename
def add_tcp_redir(devices, host_port, guest_port):
# If the new syntax is supported, don't add -redir
if "[,hostfwd=" in devices.get_help_text():
return ""
else:
return " -redir tcp:%s::%s" % (host_port, guest_port)
def add_vnc(devices, vnc_port, vnc_password='no', extra_params=None):
vnc_cmd = " -vnc :%d" % (vnc_port - 5900)
if vnc_password == "yes":
vnc_cmd += ",password"
if extra_params:
vnc_cmd += ",%s" % extra_params
return vnc_cmd
def add_sdl(devices):
if devices.has_option("sdl"):
return " -sdl"
else:
return ""
def add_nographic(devices):
return " -nographic"
def add_uuid(devices, uuid):
return " -uuid '%s'" % uuid
def add_qemu_option(devices, name, optsinfo):
"""
Add qemu option, such as '-msg timestamp=on|off'
:param devices: qcontainer object
:param name: string type option name
:param optsinfo: list like [(key, val, vtype)]
"""
if devices.has_option(name):
options = []
for info in optsinfo:
key, val = info[:2]
if key and val:
options.append("%s=%%(%s)s" % (key, key))
else:
options += filter(None, info[:2])
options = ",".join(options)
cmdline = "-%s %s" % (name, options)
device = qdevices.QStringDevice(name, cmdline=cmdline)
for info in optsinfo:
key, val, vtype = info
if key and val:
device.set_param(key, val, vtype, False)
devices.insert(device)
else:
logging.warn("option '-%s' not supportted" % name)
def add_pcidevice(devices, host, params, device_driver="pci-assign",
pci_bus='pci.0'):
if devices.has_device(device_driver):
dev = QDevice(device_driver, parent_bus=pci_bus)
else:
dev = qdevices.QCustomDevice('pcidevice', parent_bus=pci_bus)
help_cmd = "%s -device %s,\\? 2>&1" % (qemu_binary, device_driver)
pcidevice_help = utils.system_output(help_cmd)
dev.set_param('host', host)
dev.set_param('id', 'id_%s' % host.replace(":", "."))
fail_param = []
for param in params.get("pci-assign_params", "").split():
value = params.get(param)
if value:
if param in pcidevice_help:
dev.set_param(param, value)
else:
fail_param.append(param)
if fail_param:
msg = ("parameter %s is not support in device pci-assign."
" It only support following parameter:\n %s" %
(", ".join(fail_param), pcidevice_help))
logging.warn(msg)
devices.insert(dev)
def add_virtio_rng(devices, rng_params, parent_bus="pci.0"):
"""
Add virtio-rng device.
:param devices: qcontainer object to contain devices.
:param rng_params: dict include virtio_rng device params.
:param parent_bus: parent bus for virtio-rng-pci.
"""
def set_dev_params(dev, dev_params,
dev_backend, backend_type):
"""
Set QCustomDevice properties by user params dict.
"""
for pro, val in dev_params.iteritems():
suffix = "_%s" % backend_type
if pro.endswith(suffix):
idx = len(suffix)
dev.set_param(pro[:-idx], val)
if dev_backend:
dev.set_param("backend", dev_backend)
dev_id = utils_misc.generate_random_string(8)
dev_id = "%s-%s" % (backend_type, dev_id)
dev.set_param("id", dev_id)
dev_type = "virtio-rng-pci"
if devices.has_device(dev_type):
rng_pci = QDevice(dev_type, parent_bus=parent_bus)
set_dev_params(rng_pci, rng_params, None, dev_type)
rng_dev = qdevices.QCustomDevice(dev_type="object",
backend="backend")
backend = rng_params["backend"]
backend_type = rng_params["backend_type"]
set_dev_params(rng_dev, rng_params, backend, backend_type)
if backend_type == "chardev":
backend = rng_params["chardev_backend"]
backend_type = rng_params["%s_type" % backend]
char_dev = qdevices.QCustomDevice(dev_type="chardev",
backend="backend")
set_dev_params(char_dev, rng_params,
backend, backend_type)
rng_dev.set_param("chardev", char_dev.get_qid())
devices.insert(char_dev)
devices.insert(rng_dev)
rng_pci.set_param("rng", rng_dev.get_qid())
devices.insert(rng_pci)
def add_spice_rhel5(devices, spice_params, port_range=(3100, 3199)):
"""
processes spice parameters on rhel5 host.
:param spice_options - dict with spice keys/values
:param port_range - tuple with port range, default: (3000, 3199)
"""
if devices.has_option("spice"):
cmd = " -spice"
else:
return ""
spice_help = ""
if devices.has_option("spice-help"):
spice_help = commands.getoutput("%s -device \\?" % qemu_binary)
s_port = str(utils_misc.find_free_port(*port_range))
self.spice_options['spice_port'] = s_port
cmd += " port=%s" % s_port
for param in spice_params.split():
value = params.get(param)
if value:
if bool(re.search(param, spice_help, re.M)):
cmd += ",%s=%s" % (param, value)
else:
msg = ("parameter %s is not supported in spice. It "
"only supports the following parameters:\n %s"
% (param, spice_help))
logging.warn(msg)
else:
cmd += ",%s" % param
if devices.has_option("qxl"):
qxl_dev_nr = params.get("qxl_dev_nr", 1)
cmd += " -qxl %s" % qxl_dev_nr
return cmd
def add_spice(port_range=(3000, 3199),
tls_port_range=(3200, 3399)):
"""
processes spice parameters
:param port_range - tuple with port range, default: (3000, 3199)
:param tls_port_range - tuple with tls port range,
default: (3200, 3399)
"""
spice_opts = [] # will be used for ",".join()
tmp = None
def optget(opt):
"""a helper function"""
return self.spice_options.get(opt)
def set_yes_no_value(key, yes_value=None, no_value=None):
"""just a helper function"""
tmp = optget(key)
if tmp == "no" and no_value:
spice_opts.append(no_value)
elif tmp == "yes" and yes_value:
spice_opts.append(yes_value)
def set_value(opt_string, key, fallback=None):
"""just a helper function"""
tmp = optget(key)
if tmp:
spice_opts.append(opt_string % tmp)
elif fallback:
spice_opts.append(fallback)
s_port = str(utils_misc.find_free_port(*port_range))
if optget("spice_port") == "generate":
if not self.is_alive():
self.spice_options['spice_port'] = s_port
spice_opts.append("port=%s" % s_port)
self.spice_port = s_port
else:
self.spice_options['spice_port'] = self.spice_port
spice_opts.append("port=%s" % self.spice_port)
else:
set_value("port=%s", "spice_port")
set_value("password=%s", "spice_password", "disable-ticketing")
if optget("listening_addr") == "ipv4":
host_ip = utils_net.get_host_ip_address(self.params)
self.spice_options['listening_addr'] = "ipv4"
spice_opts.append("addr=%s" % host_ip)
# set_value("addr=%s", "listening_addr", )
elif optget("listening_addr") == "ipv6":
host_ip = utils_net.get_host_ip_address(self.params)
host_ip_ipv6 = utils_misc.convert_ipv4_to_ipv6(host_ip)
self.spice_options['listening_addr'] = "ipv6"
spice_opts.append("addr=%s" % host_ip_ipv6)
set_yes_no_value(
"disable_copy_paste", yes_value="disable-copy-paste")
set_value("addr=%s", "spice_addr")
if optget("spice_ssl") == "yes":
# SSL only part
t_port = str(utils_misc.find_free_port(*tls_port_range))
if optget("spice_tls_port") == "generate":
if not self.is_alive():
self.spice_options['spice_tls_port'] = t_port
spice_opts.append("tls-port=%s" % t_port)
self.spice_tls_port = t_port
else:
self.spice_options[
'spice_tls_port'] = self.spice_tls_port
spice_opts.append("tls-port=%s" % self.spice_tls_port)
else:
set_value("tls-port=%s", "spice_tls_port")
prefix = optget("spice_x509_prefix")
if ((prefix is None or not os.path.exists(prefix)) and
(optget("spice_gen_x509") == "yes")):
# Generate spice_x509_* is not always necessary,
# Regenerate them will make your existing VM
# not longer accessiable via encrypted spice.
c_subj = optget("spice_x509_cacert_subj")
s_subj = optget("spice_x509_server_subj")
# If CN is not specified, add IP of host
if s_subj[-3:] == "CN=":
s_subj += utils_net.get_host_ip_address(self.params)
passwd = optget("spice_x509_key_password")
secure = optget("spice_x509_secure")
utils_misc.create_x509_dir(prefix, c_subj, s_subj, passwd,
secure)
tmp = optget("spice_x509_dir")
if tmp == "yes":
spice_opts.append("x509-dir=%s" % (prefix))
elif tmp == "no":
cacert = optget("spice_x509_cacert_file")
server_key = optget("spice_x509_key_file")
server_cert = optget("spice_x509_cert_file")
keyfile_str = ("x509-key-file=%s,x509-cacert-file=%s,"
"x509-cert-file=%s" %
(os.path.join(prefix, server_key),
os.path.join(prefix, cacert),
os.path.join(prefix, server_cert)))
spice_opts.append(keyfile_str)
set_yes_no_value("spice_x509_secure",
yes_value="x509-key-password=%s" %
(optget("spice_x509_key_password")))
tmp = optget("spice_secure_channels")
if tmp:
for item in tmp.split(","):
spice_opts.append("tls-channel=%s" % (item.strip()))
# Less common options
set_value("seamless-migration=%s", "spice_seamless_migration")
set_value("image-compression=%s", "spice_image_compression")
set_value("jpeg-wan-compression=%s", "spice_jpeg_wan_compression")
set_value("zlib-glz-wan-compression=%s",
"spice_zlib_glz_wan_compression")
set_value("streaming-video=%s", "spice_streaming_video")
set_value("agent-mouse=%s", "spice_agent_mouse")
set_value("playback-compression=%s", "spice_playback_compression")
set_yes_no_value("spice_ipv4", yes_value="ipv4")
set_yes_no_value("spice_ipv6", yes_value="ipv6")
return " -spice %s" % (",".join(spice_opts))
def add_qxl(qxl_nr, base_addr=29):
"""
adds extra qxl devices
:param qxl_nr total number of qxl devices
:param base_addr: base address of extra qxl device
"""
qxl_str = ""
for index in range(1, qxl_nr):
addr = base_addr + index
qxl_str += " -device qxl,id=video%d,addr=0x%x" % (index, addr)
return qxl_str
def add_vga(vga):
return " -vga %s" % vga
def add_kernel(devices, filename):
return " -kernel '%s'" % filename
def add_initrd(devices, filename):
return " -initrd '%s'" % filename
def add_rtc(devices):
# Pay attention that rtc-td-hack is for early version
# if "rtc " in help:
if devices.has_option("rtc"):
cmd = " -rtc base=%s" % params.get("rtc_base", "utc")
cmd += _add_option("clock", params.get("rtc_clock", "host"))
cmd += _add_option("driftfix", params.get("rtc_drift", None))
return cmd
elif devices.has_option("rtc-td-hack"):
return " -rtc-td-hack"
else:
return ""
def add_kernel_cmdline(devices, cmdline):
return " -append '%s'" % cmdline
def add_testdev(devices, filename=None):
if devices.has_device("testdev"):
return (" -chardev file,id=testlog,path=%s"
" -device testdev,chardev=testlog" % filename)
elif devices.has_device("pc-testdev"):
return " -device pc-testdev"
else:
return ""
def add_isa_debug_exit(devices, iobase=0xf4, iosize=0x04):
if devices.has_device("isa-debug-exit"):
return (" -device isa-debug-exit,iobase=%s,iosize=%s" %
(iobase, iosize))
else:
return ""
def add_no_hpet(devices):
if devices.has_option("no-hpet"):
return " -no-hpet"
else:
return ""
def add_cpu_flags(devices, cpu_model, flags=None, vendor_id=None,
family=None):
if devices.has_option('cpu'):
cmd = " -cpu '%s'" % cpu_model
if vendor_id:
cmd += ",vendor=\"%s\"" % vendor_id
if flags:
if not flags.startswith(","):
cmd += ","
cmd += "%s" % flags
if family is not None:
cmd += ",family=%s" % family
return cmd
else:
return ""
def add_boot(devices, boot_order, boot_once, boot_menu, boot_strict):
cmd = " -boot"
patterns = ["order", "once", "menu", "strict"]
options = []
for p in patterns:
pattern = "boot .*?(\[,?%s=(.*?)\]|\s+)" % p
if devices.has_option(pattern):
option = locals()["boot_%s" % p]
options.append("%s=%s" % (p, option))
if devices.has_option("boot \[a\|c\|d\|n\]"):
cmd += " %s" % boot_once
elif options:
cmd += " %s" % ",".join(options)
else:
cmd = ""
return cmd
def get_index(index):
while self.index_in_use.get(str(index)):
index += 1
return index
def add_sga(devices):
if not devices.has_option("device"):
return ""
return " -device sga"
def add_watchdog(devices, device_type=None, action="reset"):
watchdog_cmd = ""
if devices.has_option("watchdog"):
if device_type:
watchdog_cmd += " -watchdog %s" % device_type
watchdog_cmd += " -watchdog-action %s" % action
return watchdog_cmd
def add_option_rom(devices, opt_rom):
if not devices.has_option("option-rom"):
return ""
return " -option-rom %s" % opt_rom
def add_smartcard(devices, sc_chardev, sc_id):
sc_cmd = " -device usb-ccid,id=ccid0"
sc_cmd += " -chardev " + sc_chardev
sc_cmd += ",id=" + sc_id + ",name=smartcard"
sc_cmd += " -device ccid-card-passthru,chardev=" + sc_id
return sc_cmd
def add_numa_node(devices, mem=None, cpus=None, nodeid=None):
"""
This function is used to add numa node to guest command line
"""
if not devices.has_option("numa"):
return ""
numa_cmd = " -numa node"
if mem is not None:
numa_cmd += ",mem=%s" % mem
if cpus is not None:
numa_cmd += ",cpus=%s" % cpus
if nodeid is not None:
numa_cmd += ",nodeid=%s" % nodeid
return numa_cmd
def add_balloon(devices, devid=None, bus=None, use_old_format=None):
"""
This function is used to add balloon device
"""
if not devices.has_option("device") or use_old_format is True:
devices.insert(StrDev('balloon', cmdline=" -balloon virtio"))
return
dev = QDevice("virtio-balloon-pci", parent_bus=bus)
if devid:
dev.set_param("id", devid)
devices.insert(dev)
def add_disable_legacy(devices, dev, dev_type):
"""
This function is used to add disable_legacy option for virtio-pci
"""
options = devices.execute_qemu("-device %s,?" % dev_type)
if "disable-legacy" in options:
value = params.get("disable_legacy", "off")
dev.set_param("disable-legacy", value)
def add_disable_modern(devices, dev, dev_type):
"""
This function is used to add disable_modern option for virtio-pci
"""
options = devices.execute_qemu("-device %s,?" % dev_type)
if "disable-modern" in options:
value = params.get("disable_modern", "on")
dev.set_param("disable-modern", value)
# End of command line option wrappers
# If nothing changed and devices exists, return imediatelly
if (name is None and params is None and root_dir is None and
self.devices is not None):
return self.devices
if name is None:
name = self.name
if params is None:
params = self.params
if root_dir is None:
root_dir = self.root_dir
have_ahci = False
have_virtio_scsi = False
virtio_scsi_pcis = []
pci_bus = {'aobject': params.get('pci_bus', 'pci.0')}
# init value by default.
# PCI addr 0,1,2 are taken by PCI/ISA/IDE bridge and the GPU.
self.pci_addr_list = [0, 1, 2]
# Clone this VM using the new params
vm = self.clone(name, params, root_dir, copy_state=True)
# global counters
ide_bus = 0
ide_unit = 0
vdisk = 0
scsi_disk = 0
self.last_boot_index = 0
if params.get("kernel"):
self.last_boot_index = 1
qemu_binary = utils_misc.get_qemu_binary(params)
self.qemu_binary = qemu_binary
support_cpu_model = commands.getoutput("%s -cpu \\?" % qemu_binary)
self.last_driver_index = 0
# init the dict index_in_use
for key in params.keys():
if 'drive_index' in key:
self.index_in_use[params.get(key)] = True
cmd = ""
# Enable the use of glibc's malloc_perturb feature
if params.get("malloc_perturb", "no") == "yes":
cmd += "MALLOC_PERTURB_=1 "
# Set the X11 display parameter if requested
if params.get("x11_display"):
cmd += "DISPLAY=%s " % params.get("x11_display")
if params.get("qemu_audio_drv"):
cmd += "QEMU_AUDIO_DRV=%s " % params.get("qemu_audio_drv")
# Add command prefix for qemu-kvm. like taskset, valgrind and so on
if params.get("qemu_command_prefix"):
qemu_command_prefix = params.get("qemu_command_prefix")
cmd += "%s " % qemu_command_prefix
# Add numa memory cmd to pin guest memory to numa node
if params.get("numa_node"):
numa_node = int(params.get("numa_node"))
if len(utils_misc.get_node_cpus()) < int(params.get("smp", 1)):
logging.info("Skip pinning, no enough nodes")
elif numa_node < 0:
n = utils_misc.NumaNode(numa_node)
cmd += "numactl -m %s " % n.node_id
else:
n = numa_node - 1
cmd += "numactl -m %s " % n
# Start constructing devices representation
devices = qcontainer.DevContainer(qemu_binary, self.name,
params.get('strict_mode'),
params.get('workaround_qemu_qmp_crash'),
params.get('allow_hotplugged_vm'))
StrDev = qdevices.QStringDevice
QDevice = qdevices.QDevice
devices.insert(StrDev('PREFIX', cmdline=cmd))
# Add the qemu binary
devices.insert(StrDev('qemu', cmdline=qemu_binary))
devices.insert(StrDev('-S', cmdline="-S"))
# Add the VM's name
devices.insert(StrDev('vmname', cmdline=add_name(devices, name)))
qemu_sandbox = params.get("qemu_sandbox")
if qemu_sandbox == "on":
devices.insert(StrDev('qemu_sandbox', cmdline=process_sandbox(devices, "add")))
elif qemu_sandbox == "off":
devices.insert(StrDev('qemu_sandbox', cmdline=process_sandbox(devices, "rem")))
del qemu_sandbox
devs = devices.machine_by_params(params)
for dev in devs:
devices.insert(dev)
# no automagic devices please
defaults = params.get("defaults", "no")
if devices.has_option("nodefaults") and defaults != "yes":
devices.insert(StrDev('nodefaults', cmdline=" -nodefaults"))
# nodefconfig please
if params.get("defconfig", "yes") == "no":
devices.insert(StrDev('nodefconfig', cmdline=" -nodefconfig"))
vga = params.get("vga")
if vga:
if vga != 'none':
devices.insert(StrDev('VGA-%s' % vga,
cmdline=add_vga(vga),
parent_bus={'aobject': 'pci.0'}))
if vga == 'qxl':
qxl_dev_nr = int(params.get("qxl_dev_nr", 1))
if qxl_dev_nr > 1:
addr = int(params.get("qxl_base_addr", 29))
cmdline = add_qxl(qxl_dev_nr, addr)
devices.insert(StrDev('qxl', cmdline=cmdline))
else:
devices.insert(StrDev('VGA-none', cmdline=add_vga(vga)))
elif params.get('defaults', 'no') != 'no': # by default add cirrus
devices.insert(StrDev('VGA-cirrus',
cmdline=add_vga(vga),
parent_bus={'aobject': 'pci.0'}))
# When old scsi fmt is used, new device with lowest pci_addr is created
devices.hook_fill_scsi_hbas(params)
# Additional PCI RC/switch/bridges
for pcic in params.objects("pci_controllers"):
devs = devices.pcic_by_params(pcic, params.object_params(pcic))
devices.insert(devs)
# -soundhw addresses are always the lowest after scsi
soundhw = params.get("soundcards")
if soundhw:
if not devices.has_option('device') or soundhw == "all":
for sndcard in ('AC97', 'ES1370', 'intel-hda'):
# Add all dummy PCI devices and the actuall command below
devices.insert(StrDev("SND-%s" % sndcard,
parent_bus=pci_bus))
devices.insert(StrDev('SoundHW',
cmdline="-soundhw %s" % soundhw))
else:
# TODO: Use QDevices for this and set the addresses properly
for sound_device in soundhw.split(","):
if "hda" in sound_device:
devices.insert(QDevice('intel-hda',
parent_bus=pci_bus))
devices.insert(QDevice('hda-duplex'))
elif sound_device in ["es1370", "ac97"]:
devices.insert(QDevice(sound_device.upper(),
parent_bus=pci_bus))
else:
devices.insert(QDevice(sound_device,
parent_bus=pci_bus))
# Add monitors
catch_monitor = params.get("catch_monitor")
if catch_monitor:
if catch_monitor not in params.get("monitors"):
params["monitors"] += " %s" % catch_monitor
for monitor_name in params.objects("monitors"):
monitor_params = params.object_params(monitor_name)
monitor_filename = qemu_monitor.get_monitor_filename(vm,
monitor_name)
if monitor_params.get("monitor_type") == "qmp":
cmd = add_qmp_monitor(devices, monitor_name,
monitor_filename)
devices.insert(StrDev('QMP-%s' % monitor_name, cmdline=cmd))
else:
cmd = add_human_monitor(devices, monitor_name,
monitor_filename)
devices.insert(StrDev('HMP-%s' % monitor_name, cmdline=cmd))
# Add pvpanic device
if params.get("enable_pvpanic") == "yes":
if not devices.has_device("pvpanic"):
logging.warn("pvpanic device is not supportted")
else:
pvpanic_params = {"backend": "pvpanic"}
ioport = params.get("ioport_pvpanic")
if ioport:
pvpanic_params["ioport"] = ioport
pvpanic_dev = qdevices.QCustomDevice("device",
params=pvpanic_params,
backend="backend")
pvpanic_dev.set_param("id", utils_misc.generate_random_id())
devices.insert(pvpanic_dev)
# Add serial console redirection
for serial in params.objects("serials"):
serial_filename = vm.get_serial_console_filename(serial)
cmd = add_serial(devices, serial, serial_filename)
devices.insert(StrDev('SER-%s' % serial, cmdline=cmd))
# Add virtio_serial ports
if not devices.has_option("virtconsole"):
logging.warn("virtiocosole/serial device not support")
else:
no_virtio_serial_pcis = 0
no_virtio_ports = 0
virtio_port_spread = int(params.get('virtio_port_spread', 2))
for port_name in params.objects("virtio_ports"):
port_params = params.object_params(port_name)
bus = params.get('virtio_port_bus', False)
if bus is not False: # Manually set bus
bus = int(bus)
elif not virtio_port_spread:
# bus not specified, let qemu decide
pass
elif not no_virtio_ports % virtio_port_spread:
# Add new vio-pci every n-th port. (Spread ports)
bus = no_virtio_serial_pcis
else: # Port not overriden, use last vio-pci
bus = no_virtio_serial_pcis - 1
if bus < 0: # First bus
bus = 0
# Add virtio_serial_pcis
# Multiple virtio console devices can't share a
# single virtio-serial-pci bus. So add a virtio-serial-pci bus
# when the port is a virtio console.
if (port_params.get('virtio_port_type') == 'console' and
params.get('virtio_port_bus') is None):
if arch.ARCH == 'aarch64':
dev = QDevice('virtio-serial-device')
else:
dev = QDevice('virtio-serial-pci', parent_bus=pci_bus)
dev.set_param('id',
'virtio_serial_pci%d' % no_virtio_serial_pcis)
devices.insert(dev)
no_virtio_serial_pcis += 1
for i in range(no_virtio_serial_pcis, bus + 1):
if arch.ARCH == 'aarch64':
dev = QDevice('virtio-serial-device')
else:
dev = QDevice('virtio-serial-pci', parent_bus=pci_bus)
dev.set_param('id', 'virtio_serial_pci%d' % i)
devices.insert(dev)
no_virtio_serial_pcis += 1
if bus is not False:
bus = "virtio_serial_pci%d.0" % bus
# Add actual ports
cmd = add_virtio_port(devices, port_name, bus,
self.get_virtio_port_filename(port_name),
port_params.get('virtio_port_type'),
port_params.get('virtio_port_chardev'),
port_params.get('virtio_port_name_prefix'),
no_virtio_ports,
port_params.get('virtio_port_params', ''))
devices.insert(StrDev('VIO-%s' % port_name, cmdline=cmd))
no_virtio_ports += 1
# Add virtio-rng devices
for virtio_rng in params.objects("virtio_rngs"):
virtio_rng_params = params.object_params(virtio_rng)
add_virtio_rng(devices, virtio_rng_params, pci_bus)
# Add logging
devices.insert(StrDev('isa-log', cmdline=add_log_seabios(devices)))
if params.get("anaconda_log", "no") == "yes":
add_log_anaconda(devices, pci_bus)
# Add USB controllers
usbs = params.objects("usbs")
if not devices.has_option("device"):
usbs = ("oldusb",) # Old qemu, add only one controller '-usb'
for usb_name in usbs:
usb_params = params.object_params(usb_name)
for dev in devices.usbc_by_params(usb_name, usb_params):
devices.insert(dev)
for iothread in params.get("iothreads", "").split():
cmd = "-object iothread,"
iothread_id = params.get("%s_id" % iothread.strip())
if not iothread_id:
iothread_id = iothread.strip()
cmd += "id=%s" % iothread_id
devices.insert(StrDev("IOthread_%s" % iothread_id, cmdline=cmd))
# Add images (harddrives)
for image_name in params.objects("images"):
# FIXME: Use qemu_devices for handling indexes
image_params = params.object_params(image_name)
if image_params.get("boot_drive") == "no":
continue
if params.get("index_enable") == "yes":
drive_index = image_params.get("drive_index")
if drive_index:
index = drive_index
else:
self.last_driver_index = get_index(self.last_driver_index)
index = str(self.last_driver_index)
self.last_driver_index += 1
else:
index = None
image_bootindex = None
image_boot = image_params.get("image_boot")
if not re.search("boot=on\|off", devices.get_help_text(),
re.MULTILINE):
if image_boot in ['yes', 'on', True]:
image_bootindex = str(self.last_boot_index)
self.last_boot_index += 1
image_boot = "unused"
image_bootindex = image_params.get('bootindex',
image_bootindex)
else:
if image_boot in ['yes', 'on', True]:
if self.last_boot_index > 0:
image_boot = False
self.last_boot_index += 1
if image_params.get("boot_drive") == "no":
continue
devs = devices.images_define_by_params(image_name, image_params,
'disk', index, image_boot,
image_bootindex)
for _ in devs:
devices.insert(_)
# Networking
redirs = []
for redir_name in params.objects("redirs"):
redir_params = params.object_params(redir_name)
guest_port = int(redir_params.get("guest_port"))
host_port = vm.redirs.get(guest_port)
redirs += [(host_port, guest_port)]
iov = 0
for nic in vm.virtnet:
nic_params = params.object_params(nic.nic_name)
if nic_params.get('pci_assignable') == "no":
script = nic_params.get("nic_script")
downscript = nic_params.get("nic_downscript")
vhost = nic_params.get("vhost")
vhostforce = nic_params.get("vhostforce")
script_dir = data_dir.get_data_dir()
if script:
script = utils_misc.get_path(script_dir, script)
if downscript:
downscript = utils_misc.get_path(script_dir, downscript)
# setup nic parameters as needed
# add_netdev if netdev_id not set
nic = vm.add_nic(**dict(nic))
# gather set values or None if unset
vlan = int(nic.get('vlan'))
netdev_id = nic.get('netdev_id')
device_id = nic.get('device_id')
mac = nic.get('mac')
nic_model = nic.get("nic_model")
nic_extra = nic.get("nic_extra_params")
bootindex = nic_params.get("bootindex")
netdev_extra = nic.get("netdev_extra_params")
bootp = nic.get("bootp")
add_queues = nic_params.get("add_queues", "no") == "yes"
add_tapfd = nic_params.get("add_tapfd", "no") == "yes"
add_vhostfd = nic_params.get("add_vhostfd", "no") == "yes"
helper = nic_params.get("helper")
tapfds_len = int(nic_params.get("tapfds_len", -1))
vhostfds_len = int(nic_params.get("vhostfds_len", -1))
if nic.get("tftp"):
tftp = utils_misc.get_path(root_dir, nic.get("tftp"))
else:
tftp = None
nettype = nic.get("nettype", "bridge")
# don't force conversion add_nic()/add_net() optional parameter
if 'tapfds' in nic:
tapfds = nic.tapfds
else:
tapfds = None
if 'vhostfds' in nic:
vhostfds = nic.vhostfds
else:
vhostfds = None
ifname = nic.get('ifname')
queues = nic.get("queues", 1)
# specify the number of MSI-X vectors that the card should have;
# this option currently only affects virtio cards
if nic_params.get("enable_msix_vectors") == "yes":
if "vectors" in nic:
vectors = nic.vectors
else:
vectors = 2 * int(queues) + 2
else:
vectors = None
# Setup some exclusive parameters if we are not running a
# negative test.
if nic_params.get("run_invalid_cmd_nic") != "yes":
if vhostfds or tapfds or add_queues:
helper = None
if vhostfds or tapfds:
add_queues = None
add_vhostfd = None
add_tapfd = None
else:
if vhostfds and vhostfds_len > -1:
vhostfd_list = re.split(":", vhostfds)
if vhostfds_len < len(vhostfd_list):
vhostfds = ":".join(vhostfd_list[:vhostfds_len])
if tapfds and tapfds_len > -1:
tapfd_list = re.split(":", tapfds)
if tapfds_len < len(tapfd_list):
tapfds = ":".join(tapfd_list[:tapfds_len])
# Handle the '-net nic' part
add_nic(devices, vlan, nic_model, mac,
device_id, netdev_id, nic_extra,
nic_params.get("nic_pci_addr"),
bootindex, queues, vectors, pci_bus,
nic_params.get("ctrl_mac_addr"))
# Handle the '-net tap' or '-net user' or '-netdev' part
cmd, cmd_nd = add_net(devices, vlan, nettype, ifname, tftp,
bootp, redirs, netdev_id, netdev_extra,
tapfds, script, downscript, vhost,
queues, vhostfds, add_queues, helper,
add_tapfd, add_vhostfd, vhostforce)
if vhostfds is None:
vhostfds = ""
if tapfds is None:
tapfds = ""
net_params = {'netdev_id': netdev_id,
'vhostfd': vhostfds.split(":")[0],
'vhostfds': vhostfds,
'tapfd': tapfds.split(":")[0],
'tapfds': tapfds,
'ifname': ifname,
}
for i, (host_port, guest_port) in enumerate(redirs):
net_params["host_port%d" % i] = host_port
net_params["guest_port%d" % i] = guest_port
# TODO: Is every NIC a PCI device?
devices.insert(StrDev("NET-%s" % nettype, cmdline=cmd,
params=net_params, cmdline_nd=cmd_nd))
else:
device_driver = nic_params.get("device_driver", "pci-assign")
pci_id = vm.pa_pci_ids[iov]
pci_id = ":".join(pci_id.split(":")[1:])
add_pcidevice(devices, pci_id, params=nic_params,
device_driver=device_driver,
pci_bus=pci_bus)
iov += 1
mem = params.get("mem")
if mem:
devices.insert(StrDev('mem', cmdline=add_mem(devices, mem)))
smp = int(params.get("smp", 0))
vcpu_maxcpus = int(params.get("vcpu_maxcpus", 0))
vcpu_sockets = int(params.get("vcpu_sockets", 0))
vcpu_cores = int(params.get("vcpu_cores", 0))
vcpu_threads = int(params.get("vcpu_threads", 0))
# Some versions of windows don't support more than 2 sockets of cpu,
# here is a workaround to make all windows use only 2 sockets.
if (vcpu_sockets and vcpu_sockets > 2 and
params.get("os_type") == 'windows'):
vcpu_sockets = 2
amd_vendor_string = params.get("amd_vendor_string")
if not amd_vendor_string:
amd_vendor_string = "AuthenticAMD"
if amd_vendor_string == utils_misc.get_cpu_vendor():
# AMD cpu do not support multi threads.
if params.get("test_negative_thread", "no") != "yes":
vcpu_threads = 1
txt = "Set vcpu_threads to 1 for AMD cpu."
logging.warn(txt)
if smp == 0 or vcpu_sockets == 0:
vcpu_cores = vcpu_cores or 1
vcpu_threads = vcpu_threads or 1
if smp and vcpu_sockets == 0:
vcpu_sockets = int(smp / (vcpu_cores * vcpu_threads)) or 1
else:
vcpu_sockets = vcpu_sockets or 1
if smp == 0:
smp = vcpu_cores * vcpu_threads * vcpu_sockets
else:
if vcpu_cores == 0:
vcpu_threads = vcpu_threads or 1
vcpu_cores = int(smp / (vcpu_sockets * vcpu_threads)) or 1
else:
vcpu_threads = int(smp / (vcpu_cores * vcpu_sockets)) or 1
self.cpuinfo.smp = smp
self.cpuinfo.maxcpus = vcpu_maxcpus or smp
self.cpuinfo.cores = vcpu_cores
self.cpuinfo.threads = vcpu_threads
self.cpuinfo.sockets = vcpu_sockets
devices.insert(StrDev('smp', cmdline=add_smp(devices)))
numa_total_cpus = 0
numa_total_mem = 0
for numa_node in params.objects("guest_numa_nodes"):
numa_params = params.object_params(numa_node)
numa_mem = numa_params.get("numa_mem")
numa_cpus = numa_params.get("numa_cpus")
numa_nodeid = numa_params.get("numa_nodeid")
if numa_mem is not None:
numa_total_mem += int(numa_mem)
if numa_cpus is not None:
numa_total_cpus += len(utils_misc.cpu_str_to_list(numa_cpus))
devices.insert(StrDev('numa', cmdline=add_numa_node(devices)))
if params.get("numa_consistency_check_cpu_mem", "no") == "yes":
if (numa_total_cpus > int(smp) or numa_total_mem > int(mem) or
len(params.objects("guest_numa_nodes")) > int(smp)):
logging.debug("-numa need %s vcpu and %s memory. It is not "
"matched the -smp and -mem. The vcpu number "
"from -smp is %s, and memory size from -mem is"
" %s" % (numa_total_cpus, numa_total_mem, smp,
mem))
raise virt_vm.VMDeviceError("The numa node cfg can not fit"
" smp and memory cfg.")
cpu_model = params.get("cpu_model")
use_default_cpu_model = True
if cpu_model:
use_default_cpu_model = False
for model in re.split(",", cpu_model):
model = model.strip()
if model not in support_cpu_model:
continue
cpu_model = model
break
else:
cpu_model = model
logging.error("Non existing CPU model %s will be passed "
"to qemu (wrong config or negative test)", model)
if use_default_cpu_model:
cpu_model = params.get("default_cpu_model")
if cpu_model:
vendor = params.get("cpu_model_vendor")
flags = params.get("cpu_model_flags")
family = params.get("cpu_family")
self.cpuinfo.model = cpu_model
self.cpuinfo.vendor = vendor
self.cpuinfo.flags = flags
self.cpuinfo.family = family
cmd = add_cpu_flags(devices, cpu_model, flags, vendor, family)
devices.insert(StrDev('cpu', cmdline=cmd))
# Add cdroms
for cdrom in params.objects("cdroms"):
image_params = params.object_params(cdrom)
# FIXME: Use qemu_devices for handling indexes
if image_params.get("boot_drive") == "no":
continue
if params.get("index_enable") == "yes":
drive_index = image_params.get("drive_index")
if drive_index:
index = drive_index
else:
self.last_driver_index = get_index(self.last_driver_index)
index = str(self.last_driver_index)
self.last_driver_index += 1
else:
index = None
image_bootindex = None
image_boot = image_params.get("image_boot")
if not re.search("boot=on\|off", devices.get_help_text(),
re.MULTILINE):
if image_boot in ['yes', 'on', True]:
image_bootindex = str(self.last_boot_index)
self.last_boot_index += 1
image_boot = "unused"
image_bootindex = image_params.get(
'bootindex', image_bootindex)
else:
if image_boot in ['yes', 'on', True]:
if self.last_boot_index > 0:
image_boot = False
self.last_boot_index += 1
iso = image_params.get("cdrom")
if iso or image_params.get("cdrom_without_file") == "yes":
devs = devices.cdroms_define_by_params(cdrom, image_params,
'cdrom', index,
image_boot,
image_bootindex)
for _ in devs:
devices.insert(_)
# We may want to add {floppy_otps} parameter for -fda, -fdb
# {fat:floppy:}/path/. However vvfat is not usually recommended.
for floppy_name in params.objects('floppies'):
image_params = params.object_params(floppy_name)
# TODO: Unify image, cdrom, floppy params
image_params['drive_format'] = 'floppy'
image_params[
'image_readonly'] = image_params.get("floppy_readonly",
"no")
# Use the absolute patch with floppies (pure *.vfd)
image_params['image_raw_device'] = 'yes'
image_params['image_name'] = utils_misc.get_path(
data_dir.get_data_dir(),
image_params["floppy_name"])
image_params['image_format'] = None
devs = devices.images_define_by_params(floppy_name, image_params,
media='')
for _ in devs:
devices.insert(_)
# Add usb devices
for usb_dev in params.objects("usb_devices"):
usb_dev_params = params.object_params(usb_dev)
devices.insert(devices.usb_by_params(usb_dev, usb_dev_params))
tftp = params.get("tftp")
if tftp:
tftp = utils_misc.get_path(data_dir.get_data_dir(), tftp)
devices.insert(StrDev('tftp', cmdline=add_tftp(devices, tftp)))
bootp = params.get("bootp")
if bootp:
devices.insert(StrDev('bootp',
cmdline=add_bootp(devices, bootp)))
kernel = params.get("kernel")
if kernel:
kernel = utils_misc.get_path(data_dir.get_data_dir(), kernel)
devices.insert(StrDev('kernel',
cmdline=add_kernel(devices, kernel)))
kernel_params = params.get("kernel_params")
if kernel_params:
cmd = add_kernel_cmdline(devices, kernel_params)
devices.insert(StrDev('kernel-params', cmdline=cmd))
initrd = params.get("initrd")
if initrd:
initrd = utils_misc.get_path(data_dir.get_data_dir(), initrd)
devices.insert(StrDev('initrd',
cmdline=add_initrd(devices, initrd)))
for host_port, guest_port in redirs:
cmd = add_tcp_redir(devices, host_port, guest_port)
devices.insert(StrDev('tcp-redir', cmdline=cmd))
cmd = ""
if params.get("display") == "vnc":
vnc_extra_params = params.get("vnc_extra_params")
vnc_password = params.get("vnc_password", "no")
cmd += add_vnc(devices, self.vnc_port, vnc_password,
vnc_extra_params)
elif params.get("display") == "sdl":
cmd += add_sdl(devices)
elif params.get("display") == "nographic":
cmd += add_nographic(devices)
elif params.get("display") == "spice":
if params.get("rhel5_spice"):
spice_params = params.get("spice_params")
cmd += add_spice_rhel5(devices, spice_params)
else:
spice_keys = (
"spice_port", "spice_password", "spice_addr", "spice_ssl",
"spice_tls_port", "spice_tls_ciphers", "spice_gen_x509",
"spice_x509_dir", "spice_x509_prefix",
"spice_x509_key_file", "spice_x509_cacert_file",
"spice_x509_key_password", "spice_x509_secure",
"spice_x509_cacert_subj", "spice_x509_server_subj",
"spice_secure_channels", "spice_image_compression",
"spice_jpeg_wan_compression",
"spice_zlib_glz_wan_compression", "spice_streaming_video",
"spice_agent_mouse", "spice_playback_compression",
"spice_ipv4", "spice_ipv6", "spice_x509_cert_file",
"disable_copy_paste", "spice_seamless_migration",
"listening_addr"
)
for skey in spice_keys:
value = params.get(skey, None)
if value:
self.spice_options[skey] = value
cmd += add_spice()
if cmd:
devices.insert(StrDev('display', cmdline=cmd))
if params.get("uuid") == "random":
cmd = add_uuid(devices, vm.uuid)
devices.insert(StrDev('uuid', cmdline=cmd))
elif params.get("uuid"):
cmd = add_uuid(devices, params.get("uuid"))
devices.insert(StrDev('uuid', cmdline=cmd))
if params.get("testdev") == "yes":
cmd = add_testdev(devices, vm.get_testlog_filename())
devices.insert(StrDev('testdev', cmdline=cmd))
if params.get("isa_debugexit") == "yes":
iobase = params.get("isa_debugexit_iobase")
iosize = params.get("isa_debugexit_iosize")
cmd = add_isa_debug_exit(devices, iobase, iosize)
devices.insert(StrDev('isa_debugexit', cmdline=cmd))
if params.get("disable_hpet") == "yes":
devices.insert(StrDev('nohpet', cmdline=add_no_hpet(devices)))
devices.insert(StrDev('rtc', cmdline=add_rtc(devices)))
if devices.has_option("boot"):
boot_order = params.get("boot_order", "cdn")
boot_once = params.get("boot_once", "c")
boot_menu = params.get("boot_menu", "off")
boot_strict = params.get("boot_strict", "off")
cmd = add_boot(devices, boot_order, boot_once, boot_menu, boot_strict)
devices.insert(StrDev('bootmenu', cmdline=cmd))
p9_export_dir = params.get("9p_export_dir")
if p9_export_dir:
cmd = " -fsdev"
p9_fs_driver = params.get("9p_fs_driver")
if p9_fs_driver == "handle":
cmd += " handle,id=local1,path=" + p9_export_dir
elif p9_fs_driver == "proxy":
cmd += " proxy,id=local1,socket="
else:
p9_fs_driver = "local"
cmd += " local,id=local1,path=" + p9_export_dir
# security model is needed only for local fs driver
if p9_fs_driver == "local":
p9_security_model = params.get("9p_security_model")
if not p9_security_model:
p9_security_model = "none"
cmd += ",security_model=" + p9_security_model
elif p9_fs_driver == "proxy":
p9_socket_name = params.get("9p_socket_name")
if not p9_socket_name:
raise virt_vm.VMImageMissingError("Socket name not "
"defined")
cmd += p9_socket_name
p9_immediate_writeout = params.get("9p_immediate_writeout")
if p9_immediate_writeout == "yes":
cmd += ",writeout=immediate"
p9_readonly = params.get("9p_readonly")
if p9_readonly == "yes":
cmd += ",readonly"
devices.insert(StrDev('fsdev', cmdline=cmd))
dev = QDevice('virtio-9p-pci', parent_bus=pci_bus)
dev.set_param('fsdev', 'local1')
dev.set_param('mount_tag', 'autotest_tag')
devices.insert(dev)
extra_params = params.get("extra_params")
if extra_params:
devices.insert(StrDev('extra', cmdline=extra_params))
bios_path = params.get("bios_path")
if bios_path:
devices.insert(StrDev('bios', cmdline="-bios %s" % bios_path))
if params.get('ovmf_path'):
if not os.path.exists(params['ovmf_path']):
raise error.TestError("The OVMF path is not exist. Maybe you"
" need to install related packages.")
autotest_data_dir = data_dir.get_data_dir()
ovmf_code_filename = params["ovmf_code_filename"]
ovmf_code_path = os.path.join(params['ovmf_path'],
ovmf_code_filename)
ovmf_vars_filename = params["ovmf_vars_filename"]
ovmf_vars_src_path = os.path.join(params['ovmf_path'],
ovmf_vars_filename)
# To ignore the influence from backends
path = storage.get_image_filename_filesytem(params,
autotest_data_dir)
ovmf_vars_path = "%s.fd" % path
dev = qdevices.QDrive('ovmf_code', use_device=False)
dev.set_param("if", "pflash")
dev.set_param("format", "raw")
dev.set_param("readonly", "on")
dev.set_param("file", ovmf_code_path)
devices.insert(dev)
if (not os.path.exists(ovmf_vars_path) or
params.get("restore_ovmf_vars") == "yse"):
cp_cmd = "cp -f %s %s" % (ovmf_vars_src_path, ovmf_vars_path)
utils.system(cp_cmd)
dev = qdevices.QDrive('ovmf_vars', use_device=False)
dev.set_param("if", "pflash")
dev.set_param("format", "raw")
dev.set_param("file", ovmf_vars_path)
devices.insert(dev)
disable_kvm_option = ""
if (devices.has_option("no-kvm")):
disable_kvm_option = "-no-kvm"
enable_kvm_option = ""
if (devices.has_option("enable-kvm")):
enable_kvm_option = "-enable-kvm"
if (params.get("disable_kvm", "no") == "yes"):
params["enable_kvm"] = "no"
if (params.get("enable_kvm", "yes") == "no"):
devices.insert(StrDev('nokvm', cmdline=disable_kvm_option))
logging.debug("qemu will run in TCG mode")
else:
devices.insert(StrDev('kvm', cmdline=enable_kvm_option))
logging.debug("qemu will run in KVM mode")
self.no_shutdown = (devices.has_option("no-shutdown") and
params.get("disable_shutdown", "no") == "yes")
if self.no_shutdown:
devices.insert(StrDev('noshutdown', cmdline="-no-shutdown"))
user_runas = params.get("user_runas")
if devices.has_option("runas") and user_runas:
devices.insert(StrDev('runas', cmdline="-runas %s" % user_runas))
if params.get("enable_sga") == "yes":
devices.insert(StrDev('sga', cmdline=add_sga(devices)))
if params.get("smartcard", "no") == "yes":
sc_chardev = params.get("smartcard_chardev")
sc_id = params.get("smartcard_id")
devices.insert(StrDev('smartcard',
cmdline=add_smartcard(devices, sc_chardev, sc_id)))
if params.get("enable_watchdog", "no") == "yes":
cmd = add_watchdog(devices,
params.get("watchdog_device_type", None),
params.get("watchdog_action", "reset"))
devices.insert(StrDev('watchdog', cmdline=cmd))
option_roms = params.get("option_roms")
if option_roms:
cmd = ""
for opt_rom in option_roms.split():
cmd += add_option_rom(devices, opt_rom)
if cmd:
devices.insert(StrDev('ROM', cmdline=cmd))
for balloon_device in params.objects("balloon"):
params_balloon = params.object_params(balloon_device)
balloon_devid = params_balloon.get("balloon_dev_devid")
balloon_bus = None
use_ofmt = params_balloon.get("balloon_use_old_format",
"no") == "yes"
if params_balloon.get("balloon_dev_add_bus") == "yes":
balloon_bus = pci_bus
add_balloon(devices, devid=balloon_devid, bus=balloon_bus,
use_old_format=use_ofmt)
# Add qemu options
if params.get("msg_timestamp"):
attr_info = ["timestamp", params["msg_timestamp"], bool]
add_qemu_option(devices, "msg", [attr_info])
if params.get("realtime_mlock"):
attr_info = ["mlock", params["realtime_mlock"], bool]
add_qemu_option(devices, "realtime", [attr_info])
if params.get("keyboard_layout"):
attr_info = [None, params["keyboard_layout"], None]
add_qemu_option(devices, "k", [attr_info])
for device in devices:
virtio_pci_devices = ["virtio-net-pci", "virtio-blk-pci",
"virtio-scsi-pci", "virtio-balloon-pci",
"virtio-serial-pci", "virtio-rng-pci"]
dev_type = device.get_param("driver")
if dev_type in virtio_pci_devices:
add_disable_legacy(devices, device, dev_type)
add_disable_modern(devices, device, dev_type)
return devices
def _nic_tap_add_helper(self, nic):
if nic.nettype == 'macvtap':
macvtap_mode = self.params.get("macvtap_mode", "vepa")
nic.tapfds = utils_net.create_and_open_macvtap(nic.ifname,
macvtap_mode,
nic.queues,
nic.netdst,
nic.mac)
else:
nic.tapfds = utils_net.open_tap("/dev/net/tun", nic.ifname,
queues=nic.queues, vnet_hdr=True)
logging.debug("Adding VM %s NIC ifname %s to bridge %s",
self.name, nic.ifname, nic.netdst)
if nic.nettype == 'bridge':
utils_net.add_to_bridge(nic.ifname, nic.netdst)
utils_net.bring_up_ifname(nic.ifname)
def _nic_tap_remove_helper(self, nic):
try:
if nic.nettype == 'macvtap':
logging.info("Remove macvtap ifname %s", nic.ifname)
tap = utils_net.Macvtap(nic.ifname)
tap.delete()
else:
logging.debug("Removing VM %s NIC ifname %s from bridge %s",
self.name, nic.ifname, nic.netdst)
if nic.tapfds:
for i in nic.tapfds.split(':'):
os.close(int(i))
if nic.vhostfds:
for i in nic.vhostfds.split(':'):
os.close(int(i))
if nic.ifname and nic.ifname not in utils_net.get_net_if():
_, br_name = utils_net.find_current_bridge(nic.ifname)
if br_name == nic.netdst:
utils_net.del_from_bridge(nic.ifname, nic.netdst)
except TypeError:
pass
[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
"""
try:
tmp_serial = self.serial_ports[0]
except IndexError:
raise virt_vm.VMConfigMissingError(self.name, "serial")
self.serial_console = aexpect.ShellSession(
"nc -U %s" % self.get_serial_console_filename(tmp_serial),
auto_close=False,
output_func=utils_misc.log_line,
output_params=("serial-%s-%s.log" % (tmp_serial, self.name),),
prompt=self.params.get("shell_prompt", "[\#\$]"))
del tmp_serial
[docs] def create_virtio_console(self):
"""
Establish a session with the serial console.
"""
for port in self.virtio_ports:
if isinstance(port, qemu_virtio_port.VirtioConsole):
logfile = "serial-%s-%s.log" % (port.name, self.name)
socat_cmd = "nc -U %s" % port.hostfile
self.virtio_console = aexpect.ShellSession(
socat_cmd,
auto_close=False,
output_func=utils_misc.log_line,
output_params=(logfile,),
prompt=self.params.get("shell_prompt", "[\#\$]"))
return
if self.virtio_ports:
logging.warning("No virtio console created in VM. Virtio ports: %s", self.virtio_ports)
self.virtio_console = None
[docs] def update_system_dependent_devs(self):
# Networking
devices = self.devices
params = self.params
redirs = []
for redir_name in params.objects("redirs"):
redir_params = params.object_params(redir_name)
guest_port = int(redir_params.get("guest_port"))
host_port = self.redirs.get(guest_port)
redirs += [(host_port, guest_port)]
for nic in self.virtnet:
nic_params = params.object_params(nic.nic_name)
if nic_params.get('pci_assignable') == "no":
script = nic_params.get("nic_script")
downscript = nic_params.get("nic_downscript")
script_dir = data_dir.get_data_dir()
if script:
script = utils_misc.get_path(script_dir, script)
if downscript:
downscript = utils_misc.get_path(script_dir,
downscript)
# setup nic parameters as needed
# add_netdev if netdev_id not set
nic = self.add_nic(**dict(nic))
# gather set values or None if unset
netdev_id = nic.get('netdev_id')
# don't force conversion add_nic()/add_net() optional
# parameter
if 'tapfds' in nic:
tapfds = nic.tapfds
else:
tapfds = ""
if 'vhostfds' in nic:
vhostfds = nic.vhostfds
else:
vhostfds = ""
ifname = nic.get('ifname')
# specify the number of MSI-X vectors that the card should
# have this option currently only affects virtio cards
net_params = {'netdev_id': netdev_id,
'vhostfd': vhostfds.split(":")[0],
'vhostfds': vhostfds,
'tapfd': tapfds.split(":")[0],
'tapfds': tapfds,
'ifname': ifname,
}
for i, (host_port, guest_port) in enumerate(redirs):
net_params["host_port%d" % i] = host_port
net_params["guest_port%d" % i] = guest_port
# TODO: Is every NIC a PCI device?
devs = devices.get_by_params({'netdev_id': netdev_id})
# TODO: Is every NIC a PCI device?
if len(devs) > 1:
logging.error("There are %d devices with netdev_id %s."
" This shouldn't happens." % (len(devs),
netdev_id))
devs[0].params.update(net_params)
[docs] def update_vga_global_default(self, params, migrate=None):
"""
Update VGA global default settings
:param params: dict for create vm
:param migrate: is vm create for migration
"""
if not self.devices:
return
vga_mapping = {'VGA-std': 'VGA',
'VGA-cirrus': 'cirrus-vga',
'VGA-qxl': 'qxl-vga',
'qxl': 'qxl',
'VGA-none': None}
for device in self.devices:
if not isinstance(device, qdevices.QStringDevice):
continue
vga_type = vga_mapping.get(device.type)
if not vga_type:
continue
help_cmd = '%s -device %s,\? 2>&1' % (self.qemu_binary, vga_type)
help_info = utils.system_output(help_cmd)
for pro in re.findall(r'%s.(\w+)=' % vga_type, help_info):
key = [vga_type.lower(), pro]
if migrate:
key.append('dst')
key = '_'.join(key)
val = params.get(key)
if not val:
continue
qdev = qdevices.QGlobal(vga_type, pro, val)
self.devices.insert(qdev)
@error.context_aware
def create(self, name=None, params=None, root_dir=None,
timeout=20, migration_mode=None,
migration_exec_cmd=None, migration_fd=None,
mac_source=None):
"""
Start the VM by running a qemu command.
All parameters are optional. If name, params or root_dir are not
supplied, the respective values stored as class attributes are used.
:param name: The name of the object
:param params: A dict containing VM params
:param root_dir: Base directory for relative filenames
:param migration_mode: If supplied, start VM for incoming migration
using this protocol (either 'rdma', 'x-rdma', 'rdma', 'tcp', 'unix' or 'exec')
:param migration_exec_cmd: Command to embed in '-incoming "exec: ..."'
(e.g. 'gzip -c -d filename') if migration_mode is 'exec'
default to listening on a random TCP port
:param migration_fd: Open descriptor from machine should migrate.
:param mac_source: A VM object from which to copy MAC addresses. If not
specified, new addresses will be generated.
:raise VMCreateError: If qemu terminates unexpectedly
:raise VMKVMInitError: If KVM initialization fails
:raise VMHugePageError: If hugepage initialization fails
:raise VMImageMissingError: If a CD image is missing
:raise VMHashMismatchError: If a CD image hash has doesn't match the
expected hash
:raise VMBadPATypeError: If an unsupported PCI assignment type is
requested
:raise VMPAError: If no PCI assignable devices could be assigned
:raise TAPCreationError: If fail to create tap fd
:raise BRAddIfError: If fail to add a tap to a bridge
:raise TAPBringUpError: If fail to bring up a tap
:raise PrivateBridgeError: If fail to bring the private bridge
"""
error.context("creating '%s'" % self.name)
self.destroy(free_mac_addresses=False)
if name is not None:
self.name = name
self.devices = None # Representation changed
if params is not None:
self.params = params
self.devices = None # Representation changed
if root_dir is not None:
self.root_dir = root_dir
self.devices = None # Representation changed
name = self.name
params = self.params
root_dir = self.root_dir
# Verify the md5sum of the ISO images
for cdrom in params.objects("cdroms"):
cdrom_params = params.object_params(cdrom)
if cdrom_params.get("enable_gluster") == "yes":
continue
if cdrom_params.get("enable_ceph") == "yes":
continue
iso = cdrom_params.get("cdrom")
if iso:
iso = utils_misc.get_path(data_dir.get_data_dir(), iso)
if not os.path.exists(iso):
raise virt_vm.VMImageMissingError(iso)
compare = False
if cdrom_params.get("skip_hash"):
logging.debug("Skipping hash comparison")
elif cdrom_params.get("md5sum_1m"):
logging.debug("Comparing expected MD5 sum with MD5 sum of "
"first MB of ISO file...")
actual_hash = utils.hash_file(iso, 1048576, method="md5")
expected_hash = cdrom_params.get("md5sum_1m")
compare = True
elif cdrom_params.get("md5sum"):
logging.debug("Comparing expected MD5 sum with MD5 sum of "
"ISO file...")
actual_hash = utils.hash_file(iso, method="md5")
expected_hash = cdrom_params.get("md5sum")
compare = True
elif cdrom_params.get("sha1sum"):
logging.debug("Comparing expected SHA1 sum with SHA1 sum "
"of ISO file...")
actual_hash = utils.hash_file(iso, method="sha1")
expected_hash = cdrom_params.get("sha1sum")
compare = True
if compare:
if actual_hash == expected_hash:
logging.debug("Hashes match")
else:
raise virt_vm.VMHashMismatchError(actual_hash,
expected_hash)
# Make sure the following code is not executed by more than one thread
# at the same time
lockfile = open(CREATE_LOCK_FILENAME, "w+")
fcntl.lockf(lockfile, fcntl.LOCK_EX)
try:
# Handle port redirections
redir_names = params.objects("redirs")
host_ports = utils_misc.find_free_ports(
5000, 6000, len(redir_names))
old_redirs = {}
if self.redirs:
old_redirs = self.redirs
self.redirs = {}
for i in range(len(redir_names)):
redir_params = params.object_params(redir_names[i])
guest_port = int(redir_params.get("guest_port"))
self.redirs[guest_port] = host_ports[i]
if self.redirs != old_redirs:
self.devices = None
# Update the network related parameters as well to conform to
# expected behavior on VM creation
getattr(self, 'virtnet').__init__(self.params,
self.name,
self.instance)
# Generate basic parameter values for all NICs and create TAP fd
for nic in self.virtnet:
nic_params = params.object_params(nic.nic_name)
pa_type = nic_params.get("pci_assignable")
if pa_type and pa_type != "no":
device_driver = nic_params.get("device_driver",
"pci-assign")
if "mac" not in nic:
self.virtnet.generate_mac_address(nic["nic_name"])
mac = nic["mac"]
if self.pci_assignable is None:
self.pci_assignable = test_setup.PciAssignable(
driver=params.get("driver"),
driver_option=params.get("driver_option"),
host_set_flag=params.get("host_setup_flag"),
kvm_params=params.get("kvm_default"),
vf_filter_re=params.get("vf_filter_re"),
pf_filter_re=params.get("pf_filter_re"),
device_driver=device_driver,
nic_name_re=params.get("nic_name_re"))
# Virtual Functions (VF) assignable devices
if pa_type == "vf":
self.pci_assignable.add_device(device_type=pa_type,
mac=mac,
name=nic_params.get("device_name"))
# Physical NIC (PF) assignable devices
elif pa_type == "pf":
self.pci_assignable.add_device(device_type=pa_type,
name=nic_params.get("device_name"))
else:
raise virt_vm.VMBadPATypeError(pa_type)
else:
# fill in key values, validate nettype
# note: make_create_command() calls vm.add_nic (i.e. on a
# copy)
if nic_params.get('netdst') == 'private':
nic.netdst = (test_setup.
PrivateBridgeConfig(nic_params).brname)
nic = self.add_nic(**dict(nic)) # implied add_netdev
if mac_source:
# Will raise exception if source doesn't
# have cooresponding nic
logging.debug("Copying mac for nic %s from VM %s"
% (nic.nic_name, mac_source.name))
nic.mac = mac_source.get_mac_address(nic.nic_name)
if nic.ifname in utils_net.get_net_if():
self.virtnet.generate_ifname(nic.nic_name)
elif (utils_net.find_current_bridge(nic.ifname)[1] ==
nic.netdst):
utils_net.del_from_bridge(nic.ifname, nic.netdst)
if nic.nettype in ['bridge', 'network', 'macvtap']:
self._nic_tap_add_helper(nic)
if ((nic_params.get("vhost") in ['on',
'force',
'vhost=on']) and
(nic_params.get("enable_vhostfd", "yes") == "yes")):
vhostfds = []
for i in xrange(int(nic.queues)):
vhostfds.append(str(os.open("/dev/vhost-net",
os.O_RDWR)))
nic.vhostfds = ':'.join(vhostfds)
elif nic.nettype == 'user':
logging.info("Assuming dependencies met for "
"user mode nic %s, and ready to go"
% nic.nic_name)
# Update the fd and vhostfd for nic devices
if self.devices is not None:
for device in self.devices:
cmd = device.cmdline()
if cmd is not None and "fd=" in cmd:
new_cmd = ""
for opt in cmd.split(","):
if re.match('fd=', opt):
opt = 'fd=%s' % nic.tapfds
if re.match('vhostfd=', opt):
opt = 'vhostfd=%s' % nic.vhostfds
new_cmd += "%s," % opt
device._cmdline = new_cmd.rstrip(",")
self.virtnet.update_db()
# Find available VNC port, if needed
if params.get("display") == "vnc":
self.vnc_port = utils_misc.find_free_port(5900, 6100)
# Find random UUID if specified 'uuid = random' in config file
if params.get("uuid") == "random":
f = open("/proc/sys/kernel/random/uuid")
self.uuid = f.read().strip()
f.close()
if self.pci_assignable is not None:
self.pa_pci_ids = self.pci_assignable.request_devs()
if self.pa_pci_ids:
logging.debug("Successfully assigned devices: %s",
self.pa_pci_ids)
else:
raise virt_vm.VMPAError(pa_type)
if (name is None and params is None and root_dir is None and
self.devices is not None):
self.update_system_dependent_devs()
# Make qemu command
try:
self.devices = self.make_create_command()
self.update_vga_global_default(params, migration_mode)
logging.debug(self.devices.str_short())
logging.debug(self.devices.str_bus_short())
qemu_command = self.devices.cmdline()
except error.TestNAError:
# TestNAErrors should be kept as-is so we generate SKIP
# results instead of bogus FAIL results
raise
except Exception:
for nic in self.virtnet:
self._nic_tap_remove_helper(nic)
# TODO: log_last_traceback is being moved into autotest.
# use autotest.client.shared.base_utils when it's completed.
if 'log_last_traceback' in utils.__dict__:
utils.log_last_traceback('Fail to create qemu command:')
else:
utils_misc.log_last_traceback('Fail to create qemu'
'command:')
raise virt_vm.VMStartError(self.name, 'Error occurred while '
'executing make_create_command(). '
'Check the log for traceback.')
# Add migration parameters if required
if migration_mode in ["tcp", "rdma", "x-rdma"]:
self.migration_port = utils_misc.find_free_port(5200, 6000)
qemu_command += (" -incoming " + migration_mode +
":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":
if migration_exec_cmd is None:
self.migration_port = utils_misc.find_free_port(5200, 6000)
qemu_command += (' -incoming "exec:nc -l %s"' %
self.migration_port)
else:
qemu_command += (' -incoming "exec:%s"' %
migration_exec_cmd)
elif migration_mode == "fd":
qemu_command += ' -incoming "fd:%d"' % (migration_fd)
p9_fs_driver = params.get("9p_fs_driver")
if p9_fs_driver == "proxy":
proxy_helper_name = params.get("9p_proxy_binary",
"virtfs-proxy-helper")
proxy_helper_cmd = utils_misc.get_path(root_dir,
proxy_helper_name)
if not proxy_helper_cmd:
raise virt_vm.VMConfigMissingError(self.name,
"9p_proxy_binary")
p9_export_dir = params.get("9p_export_dir")
if not p9_export_dir:
raise virt_vm.VMConfigMissingError(self.name,
"9p_export_dir")
proxy_helper_cmd += " -p " + p9_export_dir
proxy_helper_cmd += " -u 0 -g 0"
p9_socket_name = params.get("9p_socket_name")
proxy_helper_cmd += " -s " + p9_socket_name
proxy_helper_cmd += " -n"
logging.info("Running Proxy Helper:\n%s", proxy_helper_cmd)
self.process = aexpect.run_tail(proxy_helper_cmd,
None,
logging.info,
"[9p proxy helper]",
auto_close=False)
else:
logging.info("Running qemu command (reformatted):\n%s",
qemu_command.replace(" -", " \\\n -"))
self.qemu_command = qemu_command
self.process = aexpect.run_tail(qemu_command,
None,
logging.info,
"[qemu output] ",
auto_close=False)
logging.info("Created qemu process with parent PID %d",
self.process.get_pid())
self.start_time = time.time()
self.start_monotonic_time = utils_misc.monotonic_time()
# test doesn't need to hold tapfd's open
for nic in self.virtnet:
if 'tapfds' in nic: # implies bridge/tap
try:
for i in nic.tapfds.split(':'):
os.close(int(i))
# qemu process retains access via open file
# remove this attribute from virtnet because
# fd numbers are not always predictable and
# vm instance must support cloning.
del nic['tapfds']
# File descriptor is already closed
except OSError:
pass
if 'vhostfds' in nic:
try:
for i in nic.vhostfds.split(':'):
os.close(int(i))
del nic['vhostfds']
except OSError:
pass
# Make sure qemu is not defunct
if self.process.is_defunct():
logging.error("Bad things happened, qemu process is defunct")
err = ("Qemu is defunct.\nQemu output:\n%s"
% self.process.get_output())
self.destroy()
raise virt_vm.VMStartError(self.name, err)
# Make sure the process was started successfully
if not self.process.is_alive():
status = self.process.get_status()
output = self.process.get_output().strip()
migration_in_course = migration_mode is not None
unknown_protocol = "unknown migration protocol" in output
if migration_in_course and unknown_protocol:
e = VMMigrateProtoUnsupportedError(migration_mode, output)
else:
e = virt_vm.VMCreateError(qemu_command, status, output)
self.destroy()
raise e
# Establish monitor connections
self.monitors = []
for m_name in params.objects("monitors"):
m_params = params.object_params(m_name)
try:
monitor = qemu_monitor.wait_for_create_monitor(self,
m_name,
m_params,
timeout)
except qemu_monitor.MonitorConnectError, detail:
logging.error(detail)
self.destroy()
raise
# Add this monitor to the list
self.monitors.append(monitor)
# Create serial ports.
for serial in params.objects("serials"):
self.serial_ports.append(serial)
# Create virtio_ports (virtio_serialports and virtio_consoles)
i = 0
self.virtio_ports = []
for port in params.objects("virtio_ports"):
port_params = params.object_params(port)
if port_params.get('virtio_port_chardev') == "spicevmc":
filename = 'dev%s' % port
else:
filename = self.get_virtio_port_filename(port)
port_name = port_params.get('virtio_port_name_prefix', None)
if port_name: # If port_name_prefix was used
port_name = port_name + str(i)
else: # Implicit name - port
port_name = port
if port_params.get('virtio_port_type') in ("console",
"virtio_console"):
self.virtio_ports.append(
qemu_virtio_port.VirtioConsole(port, port_name,
filename))
else:
self.virtio_ports.append(
qemu_virtio_port.VirtioSerial(port, port_name,
filename))
i += 1
self.create_virtio_console()
# Get the output so far, to see if we have any problems with
# KVM modules or with hugepage setup.
output = self.process.get_output()
if re.search("Could not initialize KVM", output, re.IGNORECASE):
e = virt_vm.VMKVMInitError(
qemu_command, self.process.get_output())
self.destroy()
raise e
if "alloc_mem_area" in output:
e = virt_vm.VMHugePageError(
qemu_command, self.process.get_output())
self.destroy()
raise e
logging.debug("VM appears to be alive with PID %s", self.get_pid())
vcpu_thread_pattern = self.params.get("vcpu_thread_pattern",
r"thread_id.?[:|=]\s*(\d+)")
self.vcpu_threads = self.get_vcpu_pids(vcpu_thread_pattern)
vhost_thread_pattern = params.get("vhost_thread_pattern",
r"\w+\s+(\d+)\s.*\[vhost-%s\]")
self.vhost_threads = self.get_vhost_threads(vhost_thread_pattern)
self.create_serial_console()
for key, value in self.logs.items():
outfile = "%s-%s.log" % (key, name)
self.logsessions[key] = aexpect.Tail(
"nc -U %s" % value,
auto_close=False,
output_func=utils_misc.log_line,
output_params=(outfile,))
self.logsessions[key].set_log_file(outfile)
if params.get("paused_after_start_vm") != "yes":
# start guest
if self.monitor.verify_status("paused"):
try:
self.monitor.cmd("cont")
except qemu_monitor.QMPCmdError, e:
if ((e.data['class'] == "MigrationExpected") and
(migration_mode is not None)):
logging.debug("Migration did not start yet...")
else:
raise e
# Update mac and IP info for assigned device
# NeedFix: Can we find another way to get guest ip?
if params.get("mac_changeable") == "yes":
utils_net.update_mac_ip_address(self, params)
finally:
fcntl.lockf(lockfile, fcntl.LOCK_UN)
lockfile.close()
[docs] def wait_for_status(self, status, timeout, first=0.0, step=1.0, text=None):
"""
Wait until the VM status changes to specified status
:param timeout: Timeout in seconds
:param first: Time to sleep before first attempt
:param steps: Time to sleep between attempts in seconds
:param text: Text to print while waiting, for debug purposes
:return: True in case the status has changed before timeout, otherwise
return None.
"""
return utils_misc.wait_for(lambda: self.monitor.verify_status(status),
timeout, first, step, text)
[docs] def wait_until_paused(self, timeout):
"""
Wait until the VM is paused.
:param timeout: Timeout in seconds.
:return: True in case the VM is paused before timeout, otherwise
return None.
"""
return self.wait_for_status("paused", timeout)
[docs] def wait_until_dead(self, timeout, first=0.0, step=1.0):
"""
Wait until VM is dead.
:return: True if VM is dead before timeout, otherwise returns None.
:param timeout: Timeout in seconds
:param first: Time to sleep before first attempt
:param steps: Time to sleep between attempts in seconds
"""
return utils_misc.wait_for(self.is_dead, timeout, first, step)
[docs] def wait_for_shutdown(self, timeout=60):
"""
Wait until guest shuts down.
Helps until the VM is shut down by the guest.
:return: True in case the VM was shut down, None otherwise.
Note that the VM is not necessarily dead when this function returns
True. If QEMU is running in -no-shutdown mode, the QEMU process
may be still alive.
"""
if self.no_shutdown:
return self.wait_until_paused(timeout)
else:
return self.wait_until_dead(timeout, 1, 1)
[docs] def graceful_shutdown(self, timeout=60):
"""
Try to gracefully shut down the VM.
:return: True if VM was successfully shut down, None otherwise.
Note that the VM is not necessarily dead when this function returns
True. If QEMU is running in -no-shutdown mode, the QEMU process
may be still alive.
"""
def _shutdown_by_sendline():
try:
session.sendline(self.params.get("shutdown_command"))
if self.wait_for_shutdown(timeout):
return True
finally:
session.close()
if self.params.get("shutdown_command"):
# Try to destroy with shell command
logging.debug("Shutting down VM %s (shell)", self.name)
try:
if len(self.virtnet) > 0:
session = self.login()
else:
session = self.serial_login()
except (IndexError), e:
try:
session = self.serial_login()
except (remote.LoginError, virt_vm.VMError), e:
logging.debug(e)
else:
# Successfully get session by serial_login()
_shutdown_by_sendline()
except (remote.LoginError, virt_vm.VMError), e:
logging.debug(e)
else:
# There is no exception occurs
_shutdown_by_sendline()
def _cleanup(self, free_mac_addresses):
"""
Do cleanup works
.removes VM monitor files.
.process close
.serial_console close
.logsessions close
.delete tmp files
.free_mac_addresses, if needed
.delete macvtap, if needed
:param free_mac_addresses: Whether to release the VM's NICs back
to the address pool.
"""
self.monitors = []
if self.pci_assignable:
self.pci_assignable.release_devs()
self.pci_assignable = None
if self.process:
self.process.close()
if self.serial_console:
self.serial_console.close()
if self.logsessions:
for key in self.logsessions:
self.logsessions[key].close()
# Generate the tmp file which should be deleted.
file_list = [self.get_testlog_filename()]
file_list += qemu_monitor.get_monitor_filenames(self)
file_list += self.get_virtio_port_filenames()
file_list += self.get_serial_console_filenames()
file_list += self.logs.values()
for f in file_list:
try:
if f:
os.unlink(f)
except OSError:
pass
if hasattr(self, "migration_file"):
try:
os.unlink(self.migration_file)
except OSError:
pass
if free_mac_addresses:
for nic_index in xrange(0, len(self.virtnet)):
self.free_mac_address(nic_index)
for nic in self.virtnet:
if nic.nettype == 'macvtap':
tap = utils_net.Macvtap(nic.ifname)
tap.delete()
elif nic.ifname and nic.ifname not in utils_net.get_net_if():
_, br_name = utils_net.find_current_bridge(nic.ifname)
if br_name == nic.netdst:
utils_net.del_from_bridge(nic.ifname, nic.netdst)
[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.
"""
try:
# Is it already dead?
if self.is_dead():
return
logging.debug("Destroying VM %s (PID %s)", self.name,
self.get_pid())
kill_timeout = int(self.params.get("kill_timeout", "60"))
if gracefully:
self.graceful_shutdown(kill_timeout)
if self.is_dead():
logging.debug("VM %s down (shell)", self.name)
return
else:
logging.debug("VM %s failed to go down (shell)", self.name)
if self.monitor:
# Try to finish process with a monitor command
logging.debug("Ending VM %s process (monitor)", self.name)
try:
self.monitor.quit()
except Exception, e:
logging.warn(e)
if self.is_dead():
logging.warn("VM %s down during try to kill it "
"by monitor", self.name)
return
else:
# Wait for the VM to be really dead
if self.wait_until_dead(5, 0.5, 0.5):
logging.debug("VM %s down (monitor)", self.name)
return
else:
logging.debug("VM %s failed to go down (monitor)",
self.name)
# If the VM isn't dead yet...
pid = self.process.get_pid()
logging.debug("Ending VM %s process (killing PID %s)",
self.name, pid)
utils_misc.kill_process_tree(pid, 9)
# Wait for the VM to be really dead
if utils_misc.wait_for(self.is_dead, 5, 0.5, 0.5):
logging.debug("VM %s down (process killed)", self.name)
return
# If all else fails, we've got a zombie...
logging.error("VM %s (PID %s) is a zombie!", self.name,
self.process.get_pid())
finally:
self._cleanup(free_mac_addresses)
@property
def monitor(self):
"""
Return the main monitor object, selected by the parameter main_monitor.
If main_monitor isn't defined or it refers to a nonexistent monitor,
return the first monitor.
If no monitors exist, return None.
"""
for m in self.monitors:
if m.name == self.params.get("main_monitor"):
return m
if self.monitors:
return self.monitors[0]
return None
@property
def catch_monitor(self):
"""
Return the catch monitor object, selected by the parameter
catch_monitor.
If catch_monitor isn't defined or it refers to a nonexistent monitor,
return the last monitor.
If no monitors exist, return None.
"""
for m in self.monitors:
if m.name == self.params.get("catch_monitor"):
return m
if self.monitors:
return self.monitors[-1]
return None
[docs] def get_monitors_by_type(self, mon_type):
"""
Return list of monitors of mon_type type.
:param mon_type: desired monitor type (qmp, human)
"""
return [_ for _ in self.monitors if _.protocol == mon_type]
[docs] def get_peer(self, netid):
"""
Return the peer of netdev or network deivce.
:param netid: id of netdev or device
:return: id of the peer device otherwise None
"""
o = self.monitor.info("network")
network_info = o
if isinstance(o, dict):
network_info = o.get["return"]
netdev_peer_re = self.params.get("netdev_peer_re")
if not netdev_peer_re:
default_netdev_peer_re = "\s{2,}(.*?): .*?\\\s(.*?):"
logging.warning("Missing config netdev_peer_re for VM %s, "
"using default %s", self.name,
default_netdev_peer_re)
netdev_peer_re = default_netdev_peer_re
pairs = re.findall(netdev_peer_re, network_info, re.S)
for nic, tap in pairs:
if nic == netid:
return tap
if tap == netid:
return nic
return None
[docs] def get_ifname(self, nic_index=0):
"""
Return the ifname of a bridge/tap device associated with a NIC.
:param nic_index: Index of the NIC
"""
return self.virtnet[nic_index].ifname
[docs] def get_pid(self):
"""
Return the VM's PID. If the VM is dead return None.
:note: This works under the assumption that self.process.get_pid()
:return: the PID of the parent shell process.
"""
try:
children = commands.getoutput("ps --ppid=%d -o pid=" %
self.process.get_pid()).split()
return int(children[0])
except (TypeError, IndexError, ValueError):
return None
[docs] def get_shell_pid(self):
"""
Return the PID of the parent shell process.
:note: This works under the assumption that self.process.get_pid()
:return: the PID of the parent shell process.
"""
return self.process.get_pid()
[docs] def get_vnc_port(self):
"""
Return self.vnc_port.
"""
return self.vnc_port
[docs] def get_vcpu_pids(self, vcpu_thread_pattern):
"""
Return the list of vcpu PIDs
:return: the list of vcpu PIDs
"""
return [int(_) for _ in re.findall(vcpu_thread_pattern,
str(self.monitor.info("cpus")))]
[docs] def get_vhost_threads(self, vhost_thread_pattern):
"""
Return the list of vhost threads PIDs
:param vhost_thread_pattern: a regex to match the vhost threads
:type vhost_thread_pattern: string
:return: a list of vhost threads PIDs
:rtype: list of integer
"""
return [int(_) for _ in re.findall(vhost_thread_pattern %
self.get_pid(),
utils.system_output("ps aux"))]
[docs] def get_shared_meminfo(self):
"""
Returns the VM's shared memory information.
:return: Shared memory used by VM (MB)
"""
if self.is_dead():
logging.error("Could not get shared memory info from dead VM.")
return None
filename = "/proc/%d/statm" % self.get_pid()
shm = int(open(filename).read().split()[2])
# statm stores informations in pages, translate it to MB
return shm * 4.0 / 1024
[docs] def get_spice_var(self, spice_var):
"""
Returns string value of spice variable of choice or None
:param spice_var - spice related variable 'spice_port', ...
"""
return self.spice_options.get(spice_var, None)
@error.context_aware
def hotplug_vcpu(self, cpu_id=None, plug_command=""):
"""
Hotplug a vcpu, if not assign the cpu_id, will use the minimum unused.
the function will use the plug_command if you assigned it, else the
function will use the command automatically generated based on the
type of monitor
:param cpu_id the cpu_id you want hotplug.
"""
vcpu_threads_count = len(self.vcpu_threads)
plug_cpu_id = cpu_id
if plug_cpu_id is None:
plug_cpu_id = vcpu_threads_count
if plug_command:
vcpu_add_cmd = plug_command % plug_cpu_id
else:
if self.monitor.protocol == 'human':
vcpu_add_cmd = "cpu_set %s online" % plug_cpu_id
elif self.monitor.protocol == 'qmp':
vcpu_add_cmd = "cpu-add id=%s" % plug_cpu_id
try:
self.monitor.verify_supported_cmd(vcpu_add_cmd.split()[0])
except qemu_monitor.MonitorNotSupportedCmdError:
raise error.TestNAError("%s monitor not support cmd '%s'" %
(self.monitor.protocol, vcpu_add_cmd))
try:
cmd_output = self.monitor.send_args_cmd(vcpu_add_cmd)
except qemu_monitor.QMPCmdError, e:
return (False, str(e))
vcpu_thread_pattern = self.params.get("vcpu_thread_pattern",
r"thread_id.?[:|=]\s*(\d+)")
self.vcpu_threads = self.get_vcpu_pids(vcpu_thread_pattern)
if len(self.vcpu_threads) == vcpu_threads_count + 1:
return(True, plug_cpu_id)
else:
return(False, cmd_output)
@error.context_aware
def hotplug_nic(self, **params):
"""
Convenience method wrapper for add_nic() and add_netdev().
:return: dict-like object containing nic's details
"""
nic_name = self.add_nic(**params)["nic_name"]
self.activate_netdev(nic_name)
self.activate_nic(nic_name)
return self.virtnet[nic_name]
@error.context_aware
def hotunplug_nic(self, nic_index_or_name):
"""
Convenience method wrapper for del/deactivate nic and netdev.
"""
# make sure we got a name
nic_name = self.virtnet[nic_index_or_name].nic_name
self.deactivate_nic(nic_name)
self.deactivate_netdev(nic_name)
self.del_nic(nic_name)
@error.context_aware
def add_netdev(self, **params):
"""
Hotplug a netdev device.
:param params: NIC info. dict.
:return: netdev_id
"""
nic_name = params['nic_name']
nic = self.virtnet[nic_name]
nic_index = self.virtnet.nic_name_index(nic_name)
nic.set_if_none('netdev_id', utils_misc.generate_random_id())
nic.set_if_none('ifname', self.virtnet.generate_ifname(nic_index))
nic.set_if_none('netdev_extra_params',
params.get('netdev_extra_params'))
nic.set_if_none('nettype', 'bridge')
if nic.nettype in ['bridge', 'macvtap']: # implies tap
# destination is required, hard-code reasonable default if unset
# nic.set_if_none('netdst', 'virbr0')
# tapfd allocated/set in activate because requires system resources
nic.set_if_none('queues', '1')
ids = []
for i in range(int(nic.queues)):
ids.append(utils_misc.generate_random_id())
nic.set_if_none('tapfd_ids', ids)
elif nic.nettype == 'user':
pass # nothing to do
else: # unsupported nettype
raise virt_vm.VMUnknownNetTypeError(self.name, nic_name,
nic.nettype)
return nic.netdev_id
@error.context_aware
def del_netdev(self, nic_index_or_name):
"""
Remove netdev info. from nic on VM, does not deactivate.
:param: nic_index_or_name: name or index number for existing NIC
"""
nic = self.virtnet[nic_index_or_name]
error.context("removing netdev info from nic %s from vm %s" % (
nic, self.name))
for propertea in ['netdev_id', 'ifname', 'queues',
'tapfds', 'tapfd_ids', 'vectors']:
if nic.has_key(propertea):
del nic[propertea]
[docs] def add_nic(self, **params):
"""
Add new or setup existing NIC, optionally creating netdev if None
:param params: Parameters to set
:param nic_name: Name for existing or new device
:param nic_model: Model name to emulate
:param netdev_id: Existing qemu net device ID name, None to create new
:param mac: Optional MAC address, None to randomly generate.
"""
# returns existing or new nic object
nic = super(VM, self).add_nic(**params)
nic_index = self.virtnet.nic_name_index(nic.nic_name)
nic.set_if_none('vlan', str(nic_index))
nic.set_if_none('device_id', utils_misc.generate_random_id())
nic.set_if_none('queues', '1')
if not nic.has_key('netdev_id'):
# virtnet items are lists that act like dicts
nic.netdev_id = self.add_netdev(**dict(nic))
nic.set_if_none('nic_model', params['nic_model'])
nic.set_if_none('queues', params.get('queues', '1'))
if params.get("enable_msix_vectors") == "yes":
nic.set_if_none('vectors', 2 * int(nic.queues) + 2)
return nic
@error.context_aware
def activate_netdev(self, nic_index_or_name):
"""
Activate an inactive host-side networking device
:raise: IndexError if nic doesn't exist
:raise: VMUnknownNetTypeError: if nettype is unset/unsupported
:raise: IOError if TAP device node cannot be opened
:raise: VMAddNetDevError: if operation failed
"""
nic = self.virtnet[nic_index_or_name]
error.context("Activating netdev for %s based on %s" %
(self.name, nic))
msg_sfx = ("nic %s on vm %s with attach_cmd " %
(self.virtnet[nic_index_or_name], self.name))
attach_cmd = "netdev_add"
if nic.nettype in ['bridge', 'macvtap']:
error.context("Opening tap device node for %s " % nic.ifname,
logging.debug)
if nic.nettype == "bridge":
tun_tap_dev = "/dev/net/tun"
python_tapfds = utils_net.open_tap(tun_tap_dev,
nic.ifname,
queues=nic.queues,
vnet_hdr=False)
elif nic.nettype == "macvtap":
macvtap_mode = self.params.get("macvtap_mode", "vepa")
o_macvtap = utils_net.create_macvtap(nic.ifname, macvtap_mode,
nic.netdst, nic.mac)
tun_tap_dev = o_macvtap.get_device()
python_tapfds = utils_net.open_macvtap(o_macvtap, nic.queues)
qemu_fds = "/proc/%s/fd" % self.get_pid()
openfd_list = os.listdir(qemu_fds)
for i in range(int(nic.queues)):
error.context("Assigning tap %s to qemu by fd" %
nic.tapfd_ids[i], logging.info)
self.monitor.getfd(int(python_tapfds.split(':')[i]),
nic.tapfd_ids[i])
n_openfd_list = os.listdir(qemu_fds)
new_fds = list(set(n_openfd_list) - set(openfd_list))
if not new_fds:
err_msg = "Can't get the fd that qemu process opened!"
raise virt_vm.VMAddNetDevError(err_msg)
qemu_tapfds = [fd for fd in new_fds if os.readlink(
os.path.join(qemu_fds, fd)) == tun_tap_dev]
if not qemu_tapfds or len(qemu_tapfds) != int(nic.queues):
err_msg = "Can't get the tap fd in qemu process!"
raise virt_vm.VMAddNetDevError(err_msg)
nic.set_if_none("tapfds", ":".join(qemu_tapfds))
if not self.devices:
err_msg = "Can't add nic for VM which is not running."
raise virt_vm.VMAddNetDevError(err_msg)
if ((int(nic.queues)) > 1 and
',fds=' in self.devices.get_help_text()):
attach_cmd += " type=tap,id=%s,fds=%s" % (nic.device_id,
nic.tapfds)
else:
attach_cmd += " type=tap,id=%s,fd=%s" % (nic.device_id,
nic.tapfds)
error.context("Raising interface for " + msg_sfx + attach_cmd,
logging.debug)
utils_net.bring_up_ifname(nic.ifname)
# assume this will puke if netdst unset
if nic.netdst is not None and nic.nettype == "bridge":
error.context("Raising bridge for " + msg_sfx + attach_cmd,
logging.debug)
utils_net.add_to_bridge(nic.ifname, nic.netdst)
elif nic.nettype == 'user':
attach_cmd += " user,id=%s" % nic.device_id
elif nic.nettype == 'none':
attach_cmd += " none"
else: # unsupported nettype
raise virt_vm.VMUnknownNetTypeError(self.name, nic_index_or_name,
nic.nettype)
if nic.has_key('netdev_extra_params') and nic.netdev_extra_params:
attach_cmd += nic.netdev_extra_params
error.context("Hotplugging " + msg_sfx + attach_cmd, logging.debug)
if self.monitor.protocol == 'qmp':
self.monitor.send_args_cmd(attach_cmd)
else:
self.monitor.send_args_cmd(attach_cmd, convert=False)
network_info = self.monitor.info("network")
if nic.device_id not in network_info:
# Don't leave resources dangling
self.deactivate_netdev(nic_index_or_name)
raise virt_vm.VMAddNetDevError(("Failed to add netdev: %s for " %
nic.device_id) + msg_sfx +
attach_cmd)
@error.context_aware
def activate_nic(self, nic_index_or_name):
"""
Activate an VM's inactive NIC device and verify state
:param nic_index_or_name: name or index number for existing NIC
"""
error.context("Retrieving info for NIC %s on VM %s" % (
nic_index_or_name, self.name))
nic = self.virtnet[nic_index_or_name]
device_add_cmd = "device_add"
if nic.has_key('nic_model'):
device_add_cmd += ' driver=%s' % nic.nic_model
device_add_cmd += ",netdev=%s" % nic.device_id
if nic.has_key('mac'):
device_add_cmd += ",mac=%s" % nic.mac
device_add_cmd += ",id=%s" % nic.nic_name
if nic['nic_model'] == 'virtio-net-pci':
if int(nic['queues']) > 1:
device_add_cmd += ",mq=on"
if nic.has_key('vectors'):
device_add_cmd += ",vectors=%s" % nic.vectors
device_add_cmd += nic.get('nic_extra_params', '')
if nic.has_key('romfile'):
device_add_cmd += ",romfile=%s" % nic.romfile
error.context("Activating nic on VM %s with monitor command %s" % (
self.name, device_add_cmd))
if self.monitor.protocol == 'qmp':
self.monitor.send_args_cmd(device_add_cmd)
else:
self.monitor.send_args_cmd(device_add_cmd, convert=False)
error.context("Verifying nic %s shows in qtree" % nic.nic_name)
qtree = self.monitor.info("qtree")
if nic.nic_name not in qtree:
logging.error(qtree)
raise virt_vm.VMAddNicError("Device %s was not plugged into qdev"
"tree" % nic.nic_name)
@error.context_aware
def deactivate_nic(self, nic_index_or_name, wait=20):
"""
Reverses what activate_nic did
:param nic_index_or_name: name or index number for existing NIC
:param wait: Time test will wait for the guest to unplug the device
"""
nic = self.virtnet[nic_index_or_name]
error.context("Removing nic %s from VM %s" % (nic_index_or_name,
self.name))
nic_del_cmd = "device_del id=%s" % (nic.nic_name)
if self.monitor.protocol == 'qmp':
self.monitor.send_args_cmd(nic_del_cmd)
else:
self.monitor.send_args_cmd(nic_del_cmd, convert=True)
if wait:
logging.info("waiting for the guest to finish the unplug")
nic_eigenvalue = r'dev:\s+%s,\s+id\s+"%s"' % (nic.nic_model,
nic.nic_name)
if not utils_misc.wait_for(lambda: nic_eigenvalue not in
self.monitor.info("qtree"),
wait, 5, 1):
raise virt_vm.VMDelNicError("Device is not unplugged by "
"guest, please check whether the "
"hotplug module was loaded in "
"guest")
@error.context_aware
def deactivate_netdev(self, nic_index_or_name):
"""
Reverses what activate_netdev() did
:param: nic_index_or_name: name or index number for existing NIC
"""
# FIXME: Need to down interface & remove from bridge????
nic = self.virtnet[nic_index_or_name]
netdev_id = nic.device_id
error.context("removing netdev id %s from vm %s" %
(netdev_id, self.name))
nic_del_cmd = "netdev_del id=%s" % netdev_id
if self.monitor.protocol == 'qmp':
self.monitor.send_args_cmd(nic_del_cmd)
else:
self.monitor.send_args_cmd(nic_del_cmd, convert=True)
network_info = self.monitor.info("network")
netdev_eigenvalue = r'netdev\s+=\s+%s' % netdev_id
if netdev_eigenvalue in network_info:
raise virt_vm.VMDelNetDevError("Fail to remove netdev %s" %
netdev_id)
if nic.nettype == 'macvtap':
tap = utils_net.Macvtap(nic.ifname)
tap.delete()
@error.context_aware
def del_nic(self, nic_index_or_name):
"""
Undefine nic prameters, reverses what add_nic did.
:param nic_index_or_name: name or index number for existing NIC
:param wait: Time test will wait for the guest to unplug the device
"""
super(VM, self).del_nic(nic_index_or_name)
@error.context_aware
def send_fd(self, fd, fd_name="migfd"):
"""
Send file descriptor over unix socket to VM.
:param fd: File descriptor.
:param fd_name: File descriptor identificator in VM.
"""
error.context("Send fd %d like %s to VM %s" % (fd, fd_name, self.name))
logging.debug("Send file descriptor %s to source VM.", fd_name)
if self.monitor.protocol == 'human':
self.monitor.cmd("getfd %s" % (fd_name), fd=fd)
elif self.monitor.protocol == 'qmp':
self.monitor.cmd("getfd", args={'fdname': fd_name}, fd=fd)
error.context()
[docs] def mig_finished(self):
ret = True
if (self.params["display"] == "spice" and
self.get_spice_var("spice_seamless_migration") == "on"):
s = self.monitor.info("spice")
if isinstance(s, str):
ret = len(re.findall("migrated: true", s, re.I)) > 0
else:
ret = len(re.findall("true", str(s.get("migrated")), re.I)) > 0
o = self.monitor.info("migrate")
if isinstance(o, str):
return ret and ("status: active" not in o)
else:
return ret and (o.get("status") != "active")
[docs] def mig_succeeded(self):
o = self.monitor.info("migrate")
if isinstance(o, str):
return "status: completed" in o
else:
return o.get("status") == "completed"
[docs] def mig_failed(self):
o = self.monitor.info("migrate")
if isinstance(o, str):
return "status: failed" in o
else:
return o.get("status") == "failed"
[docs] def mig_cancelled(self):
if self.mig_succeeded():
raise virt_vm.VMMigrateCancelError(
"Migration completed successfully")
elif self.mig_failed():
raise virt_vm.VMMigrateFailedError("Migration failed")
o = self.monitor.info("migrate")
if isinstance(o, str):
return ("Migration status: cancelled" in o or
"Migration status: canceled" in o)
else:
return (o.get("status") == "cancelled" or
o.get("status") == "canceled")
[docs] def wait_for_migration(self, timeout):
if not utils_misc.wait_for(self.mig_finished, timeout, 2, 2,
"Waiting for migration to complete"):
raise virt_vm.VMMigrateTimeoutError("Timeout expired while waiting"
" for migration to finish")
@error.context_aware
def migrate(self, timeout=virt_vm.BaseVM.MIGRATE_TIMEOUT, protocol="tcp",
cancel_delay=None, offline=False, stable_check=False,
clean=True, save_path="/tmp", dest_host="localhost",
remote_port=None, not_wait_for_migration=False,
fd_src=None, fd_dst=None, migration_exec_cmd_src=None,
migration_exec_cmd_dst=None, env=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 (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.
: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.
:param not_wait_for_migration: If True migration start but not wait till
the end of migration.
:param fd_s: File descriptor for migration to which source
VM write data. Descriptor is closed during the migration.
:param fd_d: File descriptor for migration from which destination
VM read data.
:param migration_exec_cmd_src: Command to embed in '-incoming "exec: "'
(e.g. 'exec:gzip -c > filename') if migration_mode is 'exec'
default to listening on a random TCP port
:param migration_exec_cmd_dst: Command to embed in '-incoming "exec: "'
(e.g. 'gzip -c -d filename') if migration_mode is 'exec'
default to listening on a random TCP port
:param env: Dictionary with test environment
"""
if protocol not in self.MIGRATION_PROTOS:
raise virt_vm.VMMigrateProtoUnknownError(protocol)
error.base_context("migrating '%s'" % self.name)
local = dest_host == "localhost"
mig_fd_name = None
if protocol == "fd":
# Check if descriptors aren't None for local migration.
if local and (fd_dst is None or fd_src is None):
(fd_dst, fd_src) = os.pipe()
mig_fd_name = "migfd_%d_%d" % (fd_src, time.time())
self.send_fd(fd_src, mig_fd_name)
os.close(fd_src)
clone = self.clone()
if self.params.get('qemu_dst_binary', None) is not None:
clone.params['qemu_binary'] = utils_misc.get_qemu_dst_binary(self.params)
if env:
env.register_vm("%s_clone" % clone.name, clone)
if (local and not (migration_exec_cmd_src and
"gzip" in migration_exec_cmd_src)):
error.context("creating destination VM")
if stable_check:
# Pause the dest vm after creation
extra_params = clone.params.get("extra_params", "") + " -S"
clone.params["extra_params"] = extra_params
clone.create(migration_mode=protocol, mac_source=self,
migration_fd=fd_dst,
migration_exec_cmd=migration_exec_cmd_dst)
if fd_dst:
os.close(fd_dst)
error.context()
try:
if (self.params["display"] == "spice" and local and
not (protocol == "exec" and
(migration_exec_cmd_src and "gzip" in migration_exec_cmd_src))):
host_ip = utils_net.get_host_ip_address(self.params)
dest_port = clone.spice_options.get('spice_port', '')
if self.params.get("spice_ssl") == "yes":
dest_tls_port = clone.spice_options.get("spice_tls_port",
"")
cert_s = clone.spice_options.get("spice_x509_server_subj",
"")
cert_subj = "%s" % cert_s[1:]
cert_subj += host_ip
cert_subj = "\"%s\"" % cert_subj
else:
dest_tls_port = ""
cert_subj = ""
logging.debug("Informing migration to spice client")
commands = ["__com.redhat_spice_migrate_info",
"spice_migrate_info",
"client_migrate_info"]
cmdline = ""
for command in commands:
try:
self.monitor.verify_supported_cmd(command)
except qemu_monitor.MonitorNotSupportedCmdError:
continue
# spice_migrate_info requires host_ip, dest_port
# client_migrate_info also requires protocol
cmdline = "%s " % (command)
if command == "client_migrate_info":
cmdline += " protocol=%s," % self.params['display']
cmdline += " hostname=%s" % (host_ip)
if dest_port:
cmdline += ",port=%s" % dest_port
if dest_tls_port:
cmdline += ",tls-port=%s" % dest_tls_port
if cert_subj:
cmdline += ",cert-subject=%s" % cert_subj
break
if cmdline:
self.monitor.send_args_cmd(cmdline)
if protocol in ["tcp", "rdma", "x-rdma"]:
if local:
uri = protocol + ":localhost:%d" % clone.migration_port
else:
uri = protocol + ":%s:%d" % (dest_host, remote_port)
elif protocol == "unix":
uri = "unix:%s" % clone.migration_file
elif protocol == "exec":
if local:
if not migration_exec_cmd_src:
uri = '"exec:nc localhost %s"' % clone.migration_port
else:
uri = '"exec:%s"' % (migration_exec_cmd_src)
else:
uri = '"exec:%s"' % (migration_exec_cmd_src)
elif protocol == "fd":
uri = "fd:%s" % mig_fd_name
if offline is True:
self.monitor.cmd("stop")
logging.info("Migrating to %s", uri)
self.monitor.migrate(uri)
if not_wait_for_migration:
return clone
if cancel_delay:
time.sleep(cancel_delay)
self.monitor.cmd("migrate_cancel")
if not utils_misc.wait_for(self.mig_cancelled, 60, 2, 2,
"Waiting for migration "
"cancellation"):
raise virt_vm.VMMigrateCancelError(
"Cannot cancel migration")
return
self.wait_for_migration(timeout)
if (local and (migration_exec_cmd_src and
"gzip" in migration_exec_cmd_src)):
error.context("creating destination VM")
if stable_check:
# Pause the dest vm after creation
extra_params = clone.params.get("extra_params", "") + " -S"
clone.params["extra_params"] = extra_params
clone.create(migration_mode=protocol, mac_source=self,
migration_fd=fd_dst,
migration_exec_cmd=migration_exec_cmd_dst)
self.verify_alive()
# Report migration status
if self.mig_succeeded():
logging.info("Migration completed successfully")
elif self.mig_failed():
raise virt_vm.VMMigrateFailedError("Migration failed")
else:
raise virt_vm.VMMigrateFailedError("Migration ended with "
"unknown status")
# Switch self <-> clone
temp = self.clone(copy_state=True)
self.__dict__ = clone.__dict__
clone = temp
# From now on, clone is the source VM that will soon be destroyed
# and self is the destination VM that will remain alive. If this
# is remote migration, self is a dead VM object.
error.context("after migration")
if local:
time.sleep(1)
self.verify_kernel_crash()
self.verify_alive()
if local and stable_check:
try:
save1 = os.path.join(save_path, "src-" + clone.instance)
save2 = os.path.join(save_path, "dst-" + self.instance)
clone.save_to_file(save1)
self.save_to_file(save2)
# Fail if we see deltas
md5_save1 = utils.hash_file(save1)
md5_save2 = utils.hash_file(save2)
if md5_save1 != md5_save2:
raise virt_vm.VMMigrateStateMismatchError()
finally:
if clean:
if os.path.isfile(save1):
os.remove(save1)
if os.path.isfile(save2):
os.remove(save2)
finally:
# If we're doing remote migration and it's completed successfully,
# self points to a dead VM object
if not not_wait_for_migration:
if self.is_alive():
self.monitor.cmd("cont")
clone.destroy(gracefully=False)
if env:
env.unregister_vm("%s_clone" % self.name)
@error.context_aware
def reboot(self, session=None, method="shell", nic_index=0,
timeout=virt_vm.BaseVM.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 login or not (default is False).
:return: A new shell session object.
"""
def __reboot(session):
"""
Check guest rebooting.
"""
console = session.cmd("tty", ignore_all_errors=True)
serial = console and ("pts" not in console)
session.sendline(self.params.get("reboot_command"))
if serial:
patterns = [r".*[Rr]ebooting.*", r".*[Rr]estarting system.*",
r".*[Mm]achine restart.*", r".*Linux version.*"]
try:
timeout = go_down_timeout / 2
return session.read_until_any_line_matches(patterns,
timeout=timeout)
except Exception:
return False
return not session.is_responsive(timeout=self.CLOSE_SESSION_TIMEOUT)
error.base_context("rebooting '%s'" % self.name, logging.info)
error.context("before reboot")
error.context()
if method == "shell":
if not session:
if not serial:
session = self.login(nic_index=nic_index)
else:
session = self.serial_login()
error.context("waiting for guest to go down", logging.info)
go_down_timeout = timeout * 2 / 3
if not utils_misc.wait_for(lambda: __reboot(session),
timeout=go_down_timeout):
raise virt_vm.VMRebootError("Guest refuses to go down")
login_timeout = timeout / 2
elif method == "system_reset":
# Clear the event list of all QMP monitors
qmp_monitors = [m for m in self.monitors if m.protocol == "qmp"]
for m in qmp_monitors:
m.clear_events()
# Send a system_reset monitor command
self.monitor.cmd("system_reset")
# Look for RESET QMP events
time.sleep(1)
login_timeout = timeout - 1
for m in qmp_monitors:
if m.get_event("RESET"):
logging.info("RESET QMP event received")
else:
raise virt_vm.VMRebootError("RESET QMP event not received "
"after system_reset "
"(monitor '%s')" % m.name)
else:
raise virt_vm.VMRebootError("Unknown reboot method: %s" % method)
if self.params.get("mac_changeable") == "yes":
utils_net.update_mac_ip_address(self, self.params)
error.context("logging in after reboot", logging.info)
if serial:
return self.wait_for_serial_login(timeout=login_timeout)
return self.wait_for_login(nic_index=nic_index, timeout=login_timeout)
[docs] def send_key(self, keystr):
"""
Send a key event to the VM.
:param keystr: A key event string (e.g. "ctrl-alt-delete")
"""
# For compatibility with versions of QEMU that do not recognize all
# key names: replace keyname with the hex value from the dict, which
# QEMU will definitely accept
key_mapping = {"semicolon": "0x27",
"comma": "0x33",
"dot": "0x34",
"slash": "0x35"}
for key, value in key_mapping.items():
keystr = keystr.replace(key, value)
self.monitor.sendkey(keystr)
time.sleep(0.2)
# should this really be expected from VMs of all hypervisor types?
[docs] def screendump(self, filename, debug=True):
try:
if self.catch_monitor:
self.catch_monitor.screendump(filename=filename, debug=debug)
except qemu_monitor.MonitorError, e:
logging.warn(e)
[docs] def save_to_file(self, path):
"""
Override BaseVM save_to_file method
"""
self.verify_status('paused') # Throws exception if not
# Set high speed 1TB/S
self.monitor.migrate_set_speed(str(2 << 39))
self.monitor.migrate_set_downtime(self.MIGRATE_TIMEOUT)
logging.debug("Saving VM %s to %s" % (self.name, path))
# Can only check status if background migration
self.monitor.migrate("exec:cat>%s" % path, wait=False)
utils_misc.wait_for(
# no monitor.migrate-status method
lambda:
re.search("(status.*completed)",
str(self.monitor.info("migrate")), re.M),
self.MIGRATE_TIMEOUT, 2, 2,
"Waiting for save to %s to complete" % path)
# Restore the speed and downtime to default values
self.monitor.migrate_set_speed(str(32 << 20))
self.monitor.migrate_set_downtime(0.03)
# Base class defines VM must be off after a save
self.monitor.cmd("system_reset")
self.verify_status('paused') # Throws exception if not
[docs] def restore_from_file(self, path):
"""
Override BaseVM restore_from_file method
"""
self.verify_status('paused') # Throws exception if not
logging.debug("Restoring VM %s from %s" % (self.name, path))
# Rely on create() in incoming migration mode to do the 'right thing'
self.create(name=self.name, params=self.params, root_dir=self.root_dir,
timeout=self.MIGRATE_TIMEOUT, migration_mode="exec",
migration_exec_cmd="cat " + path, mac_source=self)
self.verify_status('running') # Throws exception if not
[docs] def savevm(self, tag_name):
"""
Override BaseVM savevm method
"""
self.verify_status('paused') # Throws exception if not
logging.debug("Saving VM %s to %s" % (self.name, tag_name))
self.monitor.send_args_cmd("savevm id=%s" % tag_name)
self.monitor.cmd("system_reset")
self.verify_status('paused') # Throws exception if not
[docs] def loadvm(self, tag_name):
"""
Override BaseVM loadvm method
"""
self.verify_status('paused') # Throws exception if not
logging.debug("Loading VM %s from %s" % (self.name, tag_name))
self.monitor.send_args_cmd("loadvm id=%s" % tag_name)
self.verify_status('paused') # Throws exception if not
[docs] def pause(self):
"""
Pause the VM operation.
"""
self.monitor.cmd("stop")
[docs] def resume(self):
"""
Resume the VM operation in case it's stopped.
"""
self.monitor.cmd("cont")
[docs] def set_link(self, netdev_name, up):
"""
Set link up/down.
:param name: Link name
:param up: Bool value, True=set up this link, False=Set down this link
"""
self.monitor.set_link(netdev_name, up)
[docs] def get_block_old(self, blocks_info, p_dict={}):
"""
Get specified block device from monitor's info block command.
The block device is defined by parameter in p_dict.
:param p_dict: Dictionary that contains parameters and its value used
to define specified block device.
:param blocks_info: the results of monitor command 'info block'
:return: Matched block device name, None when not find any device.
"""
if isinstance(blocks_info, str):
for block in blocks_info.splitlines():
match = True
for key, value in p_dict.iteritems():
if value is True:
check_str = "%s=1" % key
elif value is False:
check_str = "%s=0" % key
else:
check_str = "%s=%s" % (key, value)
if check_str not in block:
match = False
break
if match:
return block.split(":")[0]
else:
for block in blocks_info:
match = True
for key, value in p_dict.iteritems():
if isinstance(value, bool):
check_str = "u'%s': %s" % (key, value)
else:
check_str = "u'%s': u'%s'" % (key, value)
if check_str not in str(block):
match = False
break
if match:
return block['device']
return None
[docs] def process_info_block(self, blocks_info):
"""
Process the info block, so that can deal with the new and old
qemu format.
:param blocks_info: the output of qemu command
'info block'
"""
block_list = []
block_entry = []
for block in blocks_info.splitlines():
if block:
block_entry.append(block.strip())
else:
block_list.append(' '.join(block_entry))
block_entry = []
# don't forget the last one
block_list.append(' '.join(block_entry))
return block_list
[docs] def get_block(self, p_dict={}):
"""
Get specified block device from monitor's info block command.
The block device is defined by parameter in p_dict.
:param p_dict: Dictionary that contains parameters and its value used
to define specified block device.
:return: Matched block device name, None when not find any device.
"""
blocks_info = self.monitor.info("block")
block = self.get_block_old(blocks_info, p_dict)
if block:
return block
block_list = self.process_info_block(blocks_info)
for block in block_list:
for key, value in p_dict.iteritems():
# for new qemu we just deal with key = [removable,
# file,backing_file], for other types key, we should
# fixup later
logging.info("block = %s" % block)
if key == 'removable':
if value is False:
if 'Removable device' not in block:
return block.split(":")[0]
elif value is True:
if 'Removable device' in block:
return block.split(":")[0]
# file in key means both file and backing_file
if ('file' in key) and (value in block):
return block.split(":")[0]
return None
[docs] def check_block_locked(self, value):
"""
Check whether specified block device is locked or not.
Return True, if device is locked, else False.
:param vm: VM object
:param value: Parameter that can specify block device.
Can be any possible identification of a device,
Such as device name/image file name/...
:return: True if device is locked, False if device is unlocked.
"""
assert value, "Device identification not specified"
blocks_info = self.monitor.info("block")
assert value in str(blocks_info), \
"Device %s not listed in monitor's output" % value
if isinstance(blocks_info, str):
lock_str = "locked=1"
lock_str_new = "locked"
no_lock_str = "not locked"
for block in blocks_info.splitlines():
if (value in block) and (lock_str in block):
return True
# deal with new qemu
block_list = self.process_info_block(blocks_info)
for block_new in block_list:
if (value in block_new) and ("Removable device" in block_new):
if no_lock_str in block_new:
return False
elif lock_str_new in block_new:
return True
else:
for block in blocks_info:
if value in str(block):
return block['locked']
return False
[docs] def live_snapshot(self, base_file, snapshot_file,
snapshot_format="qcow2"):
"""
Take a live disk snapshot.
:param base_file: base file name
:param snapshot_file: snapshot file name
:param snapshot_format: snapshot file format
:return: File name of disk snapshot.
"""
device = self.get_block({"file": base_file})
output = self.monitor.live_snapshot(device, snapshot_file,
snapshot_format)
logging.debug(output)
device = self.get_block({"file": snapshot_file})
if device:
current_file = device
else:
current_file = None
return current_file
[docs] def block_stream(self, device, speed, base=None, correct=True):
"""
start to stream block device, aka merge snapshot;
:param device: device ID;
:param speed: limited speed, default unit B/s;
:param base: base file;
:param correct: auto correct cmd, correct by default
"""
cmd = self.params.get("block_stream_cmd", "block-stream")
return self.monitor.block_stream(device, speed, base,
cmd, correct=correct)
[docs] def block_mirror(self, device, target, speed, sync,
format, mode="absolute-paths", correct=True):
"""
Mirror block device to target file;
:param device: device ID
:param target: destination image file name;
:param speed: max limited speed, default unit is B/s;
:param sync: what parts of the disk image should be copied to the
destination;
:param mode: new image open mode
:param format: target image format
:param correct: auto correct cmd, correct by default
"""
cmd = self.params.get("block_mirror_cmd", "drive-mirror")
return self.monitor.block_mirror(device, target, speed, sync,
format, mode, cmd, correct=correct)
[docs] def block_reopen(self, device, new_image, format="qcow2", correct=True):
"""
Reopen a new image, no need to do this step in rhel7 host
:param device: device ID
:param new_image: new image filename
:param format: new image format
:param correct: auto correct cmd, correct by default
"""
cmd = self.params.get("block_reopen_cmd", "block-job-complete")
return self.monitor.block_reopen(device, new_image,
format, cmd, correct=correct)
[docs] def cancel_block_job(self, device, correct=True):
"""
cancel active job on the image_file
:param device: device ID
:param correct: auto correct cmd, correct by default
"""
cmd = self.params.get("block_job_cancel_cmd", "block-job-cancel")
return self.monitor.cancel_block_job(device, cmd, correct=correct)
[docs] def set_job_speed(self, device, speed="0", correct=True):
"""
set max speed of block job;
:param device: device ID
:param speed: max speed of block job
:param correct: auto correct cmd, correct by default
"""
cmd = self.params.get("set_block_job_speed", "block-job-set-speed")
return self.monitor.set_block_job_speed(device, speed,
cmd, correct=correct)
[docs] def get_job_status(self, device):
"""
get block job info;
:param device: device ID
"""
return self.monitor.query_block_job(device)
[docs] def eject_cdrom(self, device, force=False):
"""
Eject cdrom and open door of the CDROM;
:param device: device ID;
:param force: force eject or not;
"""
return self.monitor.eject_cdrom(device, force)