virt-manager/virtinst/guest.py

774 lines
26 KiB
Python
Raw Normal View History

#
# Common code for all guests
#
# Copyright 2006-2009, 2013, 2014, 2015 Red Hat, Inc.
# Jeremy Katz <katzj@redhat.com>
#
# This work is licensed under the GNU GPLv2 or later.
# See the COPYING file in the top-level directory.
import logging
import libvirt
from virtcli import CLIConfig
from . import util
from .devices import * # pylint: disable=wildcard-import
from .domain import * # pylint: disable=wildcard-import
from .domcapabilities import DomainCapabilities
from .osdict import OSDB
from .xmlbuilder import XMLBuilder, XMLProperty, XMLChildProperty
_ignore = Device
class _DomainDevices(XMLBuilder):
XML_NAME = "devices"
_XML_PROP_ORDER = ['disk', 'controller', 'filesystem', 'interface',
'smartcard', 'serial', 'parallel', 'console', 'channel',
'input', 'tpm', 'graphics', 'sound', 'video', 'hostdev',
'redirdev', 'watchdog', 'memballoon', 'rng', 'panic',
'memory']
disk = XMLChildProperty(DeviceDisk)
controller = XMLChildProperty(DeviceController)
filesystem = XMLChildProperty(DeviceFilesystem)
interface = XMLChildProperty(DeviceInterface)
smartcard = XMLChildProperty(DeviceSmartcard)
serial = XMLChildProperty(DeviceSerial)
parallel = XMLChildProperty(DeviceParallel)
console = XMLChildProperty(DeviceConsole)
channel = XMLChildProperty(DeviceChannel)
input = XMLChildProperty(DeviceInput)
tpm = XMLChildProperty(DeviceTpm)
graphics = XMLChildProperty(DeviceGraphics)
sound = XMLChildProperty(DeviceSound)
video = XMLChildProperty(DeviceVideo)
hostdev = XMLChildProperty(DeviceHostdev)
redirdev = XMLChildProperty(DeviceRedirdev)
watchdog = XMLChildProperty(DeviceWatchdog)
memballoon = XMLChildProperty(DeviceMemballoon)
rng = XMLChildProperty(DeviceRng)
panic = XMLChildProperty(DevicePanic)
memory = XMLChildProperty(DeviceMemory)
def get_all(self):
retlist = []
# pylint: disable=protected-access
devtypes = _DomainDevices._XML_PROP_ORDER
for devtype in devtypes:
retlist.extend(getattr(self, devtype))
return retlist
class Guest(XMLBuilder):
@staticmethod
def check_vm_collision(conn, name, do_remove):
"""
Remove the existing VM with the same name if requested, or error
if there is a collision.
"""
vm = None
try:
vm = conn.lookupByName(name)
except libvirt.libvirtError:
pass
if vm is None:
return
if not do_remove:
raise RuntimeError(_("Domain named %s already exists!") % name)
try:
logging.debug("Explicitly replacing guest '%s'", name)
if vm.ID() != -1:
logging.info("Destroying guest '%s'", name)
vm.destroy()
logging.info("Undefining guest '%s'", name)
vm.undefine()
except libvirt.libvirtError as e:
raise RuntimeError(_("Could not remove old vm '%s': %s") %
(str(e)))
@staticmethod
def validate_name(conn, name, check_collision=True, validate=True):
if validate:
util.validate_name(_("Guest"), name)
if not check_collision:
return
try:
conn.lookupByName(name)
except Exception:
return
raise ValueError(_("Guest name '%s' is already in use.") % name)
2018-09-07 03:04:49 +03:00
@staticmethod
def get_recommended_machine(capsinfo):
"""
Return the recommended machine type for the passed capsinfo.
We only return this for arch cases where there's a very clear
preference that's different from the default machine type
2018-09-07 03:04:49 +03:00
"""
def _qemu_machine():
if (capsinfo.arch in ["ppc64", "ppc64le"] and
"pseries" in capsinfo.machines):
return "pseries"
if capsinfo.arch in ["armv7l", "aarch64"]:
if "virt" in capsinfo.machines:
return "virt"
if "vexpress-a15" in capsinfo.machines:
return "vexpress-a15"
if capsinfo.arch in ["s390x"]:
if "s390-ccw-virtio" in capsinfo.machines:
return "s390-ccw-virtio"
if capsinfo.conn.is_qemu() or capsinfo.conn.is_test():
return _qemu_machine()
2018-09-07 03:04:49 +03:00
return None
#################
# init handling #
#################
XML_NAME = "domain"
_XML_PROP_ORDER = [
"type", "name", "uuid", "title", "description", "_metadata",
"hotplugmemorymax", "hotplugmemoryslots", "maxmemory", "_memory",
"blkiotune", "memtune", "memoryBacking",
"_vcpus", "curvcpus", "vcpu_placement",
"cpuset", "numatune", "resource", "sysinfo",
"bootloader", "os", "idmap", "features", "cpu", "clock",
"on_poweroff", "on_reboot", "on_crash",
"pm", "emulator", "devices", "seclabels"]
def __init__(self, *args, **kwargs):
XMLBuilder.__init__(self, *args, **kwargs)
2013-08-09 19:04:01 +04:00
# Allow virt-manager to override the default graphics type
self.default_graphics_type = CLIConfig.default_graphics
self.skip_default_console = False
self.skip_default_channel = False
self.skip_default_sound = False
self.skip_default_usbredir = False
self.skip_default_graphics = False
self.skip_default_rng = False
2014-02-05 22:51:53 +04:00
self.x86_cpu_default = self.cpu.SPECIAL_MODE_HOST_MODEL_ONLY
2018-09-02 03:13:40 +03:00
self.__osinfo = None
self._capsinfo = None
2018-10-04 19:22:22 +03:00
self._domcaps = None
######################
# Property accessors #
######################
name = XMLProperty("./name")
def _set_memory(self, val):
if val is not None:
val = int(val)
if self.maxmemory is None or self.maxmemory < val:
self.maxmemory = val
self._memory = val
def _get_memory(self):
return self._memory
_memory = XMLProperty("./currentMemory", is_int=True)
memory = property(_get_memory, _set_memory)
2013-09-19 21:27:30 +04:00
maxmemory = XMLProperty("./memory", is_int=True)
hotplugmemorymax = XMLProperty("./maxMemory", is_int=True)
hotplugmemoryslots = XMLProperty("./maxMemory/@slots", is_int=True)
def _set_vcpus(self, val):
if val is not None:
val = int(val)
# Don't force set curvcpus unless already specified
if self.curvcpus is not None and self.curvcpus > val:
self.curvcpus = val
self._vcpus = val
def _get_vcpus(self):
return self._vcpus
_vcpus = XMLProperty("./vcpu", is_int=True)
vcpus = property(_get_vcpus, _set_vcpus)
2013-09-19 21:27:30 +04:00
curvcpus = XMLProperty("./vcpu/@current", is_int=True)
vcpu_placement = XMLProperty("./vcpu/@placement")
cpuset = XMLProperty("./vcpu/@cpuset")
uuid = XMLProperty("./uuid")
2013-09-24 05:27:42 +04:00
id = XMLProperty("./@id", is_int=True)
type = XMLProperty("./@type")
2013-09-19 21:27:30 +04:00
bootloader = XMLProperty("./bootloader")
description = XMLProperty("./description")
title = XMLProperty("./title")
2013-09-19 21:27:30 +04:00
emulator = XMLProperty("./devices/emulator")
on_poweroff = XMLProperty("./on_poweroff")
on_reboot = XMLProperty("./on_reboot")
on_crash = XMLProperty("./on_crash")
on_lockfailure = XMLProperty("./on_lockfailure")
seclabels = XMLChildProperty(DomainSeclabel)
os = XMLChildProperty(DomainOs, is_single=True)
features = XMLChildProperty(DomainFeatures, is_single=True)
clock = XMLChildProperty(DomainClock, is_single=True)
cpu = XMLChildProperty(DomainCpu, is_single=True)
cputune = XMLChildProperty(DomainCputune, is_single=True)
numatune = XMLChildProperty(DomainNumatune, is_single=True)
pm = XMLChildProperty(DomainPm, is_single=True)
blkiotune = XMLChildProperty(DomainBlkiotune, is_single=True)
memtune = XMLChildProperty(DomainMemtune, is_single=True)
memoryBacking = XMLChildProperty(DomainMemoryBacking, is_single=True)
idmap = XMLChildProperty(DomainIdmap, is_single=True)
resource = XMLChildProperty(DomainResource, is_single=True)
sysinfo = XMLChildProperty(DomainSysinfo, is_single=True)
_metadata = XMLChildProperty(DomainMetadata, is_single=True)
xmlns_qemu = XMLChildProperty(DomainXMLNSQemu, is_single=True)
##############################
# osinfo related definitions #
##############################
2018-09-02 03:13:40 +03:00
def _get_osinfo(self):
if self.__osinfo:
return self.__osinfo
os_id = self._metadata.libosinfo.os_id
if os_id:
self.__osinfo = OSDB.lookup_os_by_full_id(os_id)
if not self.__osinfo:
logging.debug("XML had libosinfo os id=%s but we didn't "
"find any libosinfo object matching that", os_id)
2018-09-02 03:13:40 +03:00
if not self.__osinfo:
self.set_os_name("generic")
2018-09-02 03:13:40 +03:00
return self.__osinfo
osinfo = property(_get_osinfo)
def set_os_name(self, name):
obj = OSDB.lookup_os(name)
if obj is None:
raise ValueError(_("Unknown OS name '%s'. "
"See `osinfo-query os` for valid values.") % name)
logging.debug("Setting Guest osinfo %s", obj)
self.__osinfo = obj
self._metadata.libosinfo.os_id = self.__osinfo.full_id
def set_os_full_id(self, full_id):
obj = OSDB.lookup_os_by_full_id(full_id)
if obj is None:
raise ValueError(_("Unknown libosinfo ID '%s'") % full_id)
logging.debug("Setting Guest osinfo %s", obj)
self.__osinfo = obj
self._metadata.libosinfo.os_id = self.__osinfo.full_id
def _supports_virtio(self, os_support):
if not self.conn.is_qemu():
return False
# These _only_ support virtio so don't check the OS
if (self.os.is_arm_machvirt() or
self.os.is_s390x() or
self.os.is_pseries()):
return True
if not os_support:
return False
if self.os.is_x86():
return True
return False
def supports_virtionet(self):
return self._supports_virtio(self.osinfo.supports_virtionet())
def supports_virtiodisk(self):
return self._supports_virtio(self.osinfo.supports_virtiodisk())
2018-09-07 03:04:49 +03:00
def stable_defaults(self, *args, **kwargs):
return self.conn.stable_defaults(self.emulator, *args, **kwargs)
2018-09-07 03:04:49 +03:00
###############################
# Public XML APIs and helpers #
###############################
def add_device(self, dev):
self.devices.add_child(dev)
def remove_device(self, dev):
self.devices.remove_child(dev)
devices = XMLChildProperty(_DomainDevices, is_single=True)
def prefers_uefi(self):
"""
Return True if this config prefers UEFI. For example,
arm+machvirt prefers UEFI since it's required for traditional
install methods
"""
return self.os.is_arm_machvirt()
def get_uefi_path(self):
"""
If UEFI firmware path is found, return it, otherwise raise an error
"""
if not self.os.arch:
self.set_capabilities_defaults()
2018-10-04 19:22:22 +03:00
domcaps = self.lookup_domcaps()
if not domcaps.supports_uefi_xml():
raise RuntimeError(_("Libvirt version does not support UEFI."))
if not domcaps.arch_can_uefi():
raise RuntimeError(
_("Don't know how to setup UEFI for arch '%s'") %
self.os.arch)
path = domcaps.find_uefi_path_for_arch()
if not path:
raise RuntimeError(_("Did not find any UEFI binary path for "
"arch '%s'") % self.os.arch)
return path
def set_uefi_path(self, path):
"""
Configure UEFI for the VM, but only if libvirt is advertising
a known UEFI binary path.
"""
self.os.loader_ro = True
self.os.loader_type = "pflash"
self.os.loader = path
# If the firmware name contains "secboot" it is probably build
# with SMM feature required so we need to enable that feature,
# otherwise the firmware may fail to load. True secure boot is
# currently supported only on x86 architecture and with q35 with
# SMM feature enabled so change the machine to q35 as well.
# To actually enforce the secure boot for the guest if Secure Boot
# Mode is configured we need to enable loader secure feature.
if (self.os.is_x86() and
"secboot" in self.os.loader):
self.features.smm = True
self.os.loader_secure = True
self.os.machine = "q35"
# UEFI doesn't work with hyperv bits for some OS
if self.osinfo.broken_uefi_with_hyperv():
self.features.hyperv_relaxed = None
self.features.hyperv_vapic = None
self.features.hyperv_spinlocks = None
self.features.hyperv_spinlocks_retries = None
for i in self.clock.timers:
if i.name == "hypervclock":
self.clock.timers.remove(i)
2018-09-07 03:04:49 +03:00
def has_spice(self):
for gfx in self.devices.graphics:
if gfx.type == gfx.TYPE_SPICE:
return True
2018-09-07 03:04:49 +03:00
def has_gl(self):
for gfx in self.devices.graphics:
if gfx.gl:
return True
2018-09-07 03:04:49 +03:00
def has_listen_none(self):
for gfx in self.devices.graphics:
listen = gfx.get_first_listen_type()
if listen and listen == "none":
return True
return False
def is_full_os_container(self):
if not self.os.is_container():
return False
for fs in self.devices.filesystem:
if fs.target == "/":
return True
return False
def hyperv_supported(self):
if not self.osinfo.is_windows():
return False
if (self.os.loader_type == "pflash" and
self.osinfo.broken_uefi_with_hyperv()):
return False
return True
2018-10-04 19:22:22 +03:00
def lookup_domcaps(self):
# We need to regenerate domcaps cache if any of these values change
def _compare(domcaps):
if self.os.machine and self.os.machine != domcaps.machine:
return False
if self.type and self.type != domcaps.domain:
return False
if self.os.arch and self.os.arch != domcaps.arch:
return False
if self.emulator and self.emulator != domcaps.path:
return False
2018-10-04 19:22:22 +03:00
return True
if not self._domcaps or not _compare(self._domcaps):
self._domcaps = DomainCapabilities.build_from_guest(self)
return self._domcaps
def lookup_capsinfo(self):
# We need to regenerate capsinfo cache if any of these values change
def _compare(capsinfo):
if self.type and self.type != capsinfo.hypervisor_type:
return False
if self.os.os_type and self.os.os_type != capsinfo.os_type:
return False
if self.os.arch and self.os.arch != capsinfo.arch:
return False
if self.os.machine and self.os.machine not in capsinfo.machines:
return False
return True
2018-10-04 19:22:22 +03:00
if not self._capsinfo or not _compare(self._capsinfo):
self._capsinfo = self.conn.caps.guest_lookup(
2018-09-07 03:04:49 +03:00
os_type=self.os.os_type,
arch=self.os.arch,
typ=self.type,
machine=self.os.machine)
return self._capsinfo
2018-09-07 03:04:49 +03:00
def set_capabilities_defaults(self, capsinfo=None):
if capsinfo:
self._capsinfo = capsinfo
else:
capsinfo = self.lookup_capsinfo()
2018-09-07 03:04:49 +03:00
wants_default_type = not self.type and not self.os.os_type
self.type = capsinfo.hypervisor_type
self.os.os_type = capsinfo.os_type
self.os.arch = capsinfo.arch
if not self.os.loader:
self.os.loader = capsinfo.loader
if (not self.emulator and
not self.os.is_xenpv() and
not self.type == "vz"):
self.emulator = capsinfo.emulator
if not self.os.machine:
self.os.machine = Guest.get_recommended_machine(capsinfo)
if (wants_default_type and
self.conn.is_qemu() and
self.os.is_x86() and
not self.type == "kvm"):
logging.warning("KVM acceleration not available, using '%s'",
self.type)
def set_defaults(self, _guest):
if not self.uuid:
self.uuid = util.generate_uuid(self.conn)
if not self.vcpus:
self.vcpus = 1
2018-09-07 03:04:49 +03:00
self.set_capabilities_defaults()
self._set_default_machine()
self._set_default_uefi()
2018-09-07 03:04:49 +03:00
self._add_default_graphics()
self._add_default_video_device()
self._add_default_input_device()
self._add_default_console_device()
self._add_default_usb_controller()
self._add_default_channels()
self._add_default_rng()
self.clock.set_defaults(self)
self.cpu.set_defaults(self)
self.features.set_defaults(self)
for seclabel in self.seclabels:
seclabel.set_defaults(self)
self.pm.set_defaults(self)
self.os.set_defaults(self)
for dev in self.devices.get_all():
dev.set_defaults(self)
self._add_implied_controllers()
self._add_spice_devices()
########################
# Private xml routines #
########################
def _set_default_machine(self):
if self.os.machine:
return
if (self.os.is_x86() and
self.conn.is_qemu() and
self.osinfo.supports_chipset_q35()):
self.os.machine = "q35"
return
capsinfo = self.lookup_capsinfo()
default = capsinfo.machines and capsinfo.machines[0] or None
self.os.machine = default
def _set_default_uefi(self):
if (self.prefers_uefi() and
not self.os.kernel and
not self.os.loader and
self.os.loader_ro is None and
self.os.nvram is None):
try:
path = self.get_uefi_path()
self.set_uefi_path(path)
except RuntimeError as e:
logging.debug("Error setting UEFI default",
exc_info=True)
logging.warning("Couldn't configure UEFI: %s", e)
logging.warning("Your VM may not boot successfully.")
def _usb_disabled(self):
controllers = [c for c in self.devices.controller if
c.type == "usb"]
if not controllers:
return False
return all([c.model == "none" for c in controllers])
def _add_default_input_device(self):
if self.os.is_container():
return
if self.devices.input:
return
if not self.devices.graphics:
return
if self._usb_disabled():
return
usb_tablet = False
usb_keyboard = False
if self.os.is_x86() and not self.os.is_xenpv():
2018-09-02 03:13:40 +03:00
usb_tablet = self.osinfo.supports_usbtablet()
if self.os.is_arm_machvirt():
usb_tablet = True
usb_keyboard = True
if usb_tablet:
dev = DeviceInput(self.conn)
dev.type = "tablet"
dev.bus = "usb"
self.add_device(dev)
if usb_keyboard:
dev = DeviceInput(self.conn)
dev.type = "keyboard"
dev.bus = "usb"
self.add_device(dev)
def _add_default_console_device(self):
if self.skip_default_console:
return
if self.devices.console or self.devices.serial:
return
dev = DeviceConsole(self.conn)
dev.type = dev.TYPE_PTY
if self.os.is_s390x():
dev.target_type = "sclp"
self.add_device(dev)
def _add_default_video_device(self):
if self.os.is_container():
return
if self.devices.video:
return
if not self.devices.graphics:
return
self.add_device(DeviceVideo(self.conn))
def _add_default_usb_controller(self):
if any([d.type == "usb" for d in self.devices.controller]):
return
if not self.conn.is_qemu() and not self.conn.is_test():
return
qemu_usb3 = self.conn.check_support(self.conn.SUPPORT_CONN_QEMU_XHCI)
usb2 = False
usb3 = False
if self.os.is_x86():
if self.osinfo.supports_usb3() and qemu_usb3:
usb3 = True
else:
usb2 = True
elif self.os.is_arm_machvirt():
# For machvirt, we always assume OS supports usb3
if (qemu_usb3 and
self.conn.check_support(
self.conn.SUPPORT_CONN_MACHVIRT_PCI_DEFAULT)):
usb3 = True
elif self.os.is_pseries():
# For pseries, we always assume OS supports usb3
if qemu_usb3:
usb3 = True
if usb2:
for dev in DeviceController.get_usb2_controllers(self.conn):
self.add_device(dev)
elif usb3:
self.add_device(
DeviceController.get_usb3_controller(self.conn, self))
def _add_default_channels(self):
if self.skip_default_channel:
return
if self.devices.channel:
return
if self.os.is_s390x():
# Not wanted for s390 apparently
return
if (self.conn.is_qemu() and
2018-09-02 03:13:40 +03:00
self._supports_virtio(self.osinfo.supports_virtioserial()) and
self.conn.check_support(self.conn.SUPPORT_CONN_AUTOSOCKET)):
dev = DeviceChannel(self.conn)
dev.type = "unix"
dev.target_type = "virtio"
dev.target_name = dev.CHANNEL_NAME_QEMUGA
self.add_device(dev)
def _add_default_graphics(self):
if self.skip_default_graphics:
return
if self.devices.graphics:
return
if self.os.is_container() and not self.conn.is_vz():
return
if self.os.arch not in ["x86_64", "i686", "ppc64", "ppc64le"]:
return
self.add_device(DeviceGraphics(self.conn))
def _add_default_rng(self):
if self.skip_default_rng:
return
if self.devices.rng:
return
if not (self.os.is_x86() or
self.os.is_arm_machvirt() or
self.os.is_pseries()):
return
if (self.conn.is_qemu() and
2018-09-02 03:13:40 +03:00
self.osinfo.supports_virtiorng() and
self.conn.check_support(self.conn.SUPPORT_CONN_RNG_URANDOM)):
dev = DeviceRng(self.conn)
dev.type = "random"
dev.device = "/dev/urandom"
self.add_device(dev)
def _add_implied_controllers(self):
has_spapr_scsi = False
has_virtio_scsi = False
has_any_scsi = False
for dev in self.devices.controller:
if dev.type == "scsi":
has_any_scsi = True
if dev.address.type == "spapr-vio":
has_spapr_scsi = True
if dev.model == "virtio":
has_virtio_scsi = True
# Add spapr-vio controller if needed
if not has_spapr_scsi:
for dev in self.devices.disk:
if dev.address.type == "spapr-vio":
ctrl = DeviceController(self.conn)
ctrl.type = "scsi"
ctrl.address.set_addrstr("spapr-vio")
ctrl.set_defaults(self)
self.add_device(ctrl)
break
# Add virtio-scsi controller if needed
if ((self.os.is_arm_machvirt() or self.os.is_pseries()) and
not has_any_scsi and
not has_virtio_scsi):
for dev in self.devices.disk:
if dev.bus == "scsi":
ctrl = DeviceController(self.conn)
ctrl.type = "scsi"
ctrl.model = "virtio-scsi"
ctrl.set_defaults(self)
self.add_device(ctrl)
break
def _add_spice_channels(self):
if self.skip_default_channel:
return
for chn in self.devices.channel:
if chn.type == chn.TYPE_SPICEVMC:
return
dev = DeviceChannel(self.conn)
dev.type = DeviceChannel.TYPE_SPICEVMC
dev.set_defaults(self)
self.add_device(dev)
def _add_spice_sound(self):
if self.skip_default_sound:
return
if self.devices.sound:
return
if not self.os.is_hvm():
return
if not (self.os.is_x86() or
self.os.is_arm_machvirt):
return
dev = DeviceSound(self.conn)
dev.set_defaults(self)
self.add_device(dev)
def _add_spice_usbredir(self):
if self.skip_default_usbredir:
return
if self.devices.redirdev:
return
if not self.os.is_x86():
return
# If we use 4 devices here, we fill up all the emulated USB2 slots,
# and directly assigned devices are forced to fall back to USB1
# https://bugzilla.redhat.com/show_bug.cgi?id=1135488
for ignore in range(2):
dev = DeviceRedirdev(self.conn)
dev.bus = "usb"
dev.type = "spicevmc"
dev.set_defaults(self)
self.add_device(dev)
def _add_spice_devices(self):
if not self.has_spice():
return
if (self.features.vmport is None and
self.os.is_x86() and
self.conn.check_support(self.conn.SUPPORT_CONN_VMPORT)):
self.features.vmport = False
self._add_spice_channels()
self._add_spice_sound()
self._add_spice_usbredir()