virt-manager/virtinst/guest.py

708 lines
23 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
"""
# For any other HV just let libvirt get us the default, these
# are the only ones we've tested.
if (not capsinfo.conn.is_test() and
not capsinfo.conn.is_qemu() and
not capsinfo.conn.is_xen()):
return None
if capsinfo.conn.is_xen() and len(capsinfo.machines):
return capsinfo.machines[0]
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"
return None
#################
# init handling #
#################
XML_NAME = "domain"
_XML_PROP_ORDER = ["type", "name", "uuid", "title", "description",
"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
######################
# 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)
xmlns_qemu = XMLChildProperty(DomainXMLNSQemu, is_single=True)
##############################
# osinfo related definitions #
##############################
2018-09-02 03:13:40 +03:00
def _set_osinfo(self, variant):
obj = OSDB.lookup_os(variant)
if not obj:
obj = OSDB.lookup_os("generic")
2018-09-02 03:13:40 +03:00
self.__osinfo = obj
def _get_osinfo(self):
if not self.__osinfo:
self._set_osinfo(None)
return self.__osinfo
osinfo = property(_get_osinfo)
def _get_os_variant(self):
2018-09-02 03:13:40 +03:00
return self.osinfo.name
def _set_os_variant(self, val):
if val:
val = val.lower()
if OSDB.lookup_os(val) is None:
raise ValueError(
_("Distro '%s' does not exist in our dictionary") % val)
logging.debug("Setting Guest.os_variant to '%s'", val)
2018-09-02 03:13:40 +03:00
self._set_osinfo(val)
os_variant = property(_get_os_variant, _set_os_variant)
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 set_uefi_default(self):
"""
Configure UEFI for the VM, but only if libvirt is advertising
a known UEFI binary path.
"""
self.set_capabilities_defaults()
domcaps = DomainCapabilities.build_from_guest(self)
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)
self.os.loader_ro = True
self.os.loader_type = "pflash"
self.os.loader = path
self.check_uefi_secure()
def check_uefi_secure(self):
"""
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 not self.os.is_x86():
return
if "secboot" not in self.os.loader:
return
self.features.smm = True
self.os.loader_secure = True
self.os.machine = "q35"
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
def lookup_capsinfo(self):
def _compare_to_capsinfo(capsinfo):
if not capsinfo:
return False
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
if not _compare_to_capsinfo(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 update_defaults(self):
# This is used only by virt-manager to reset any defaults that may have
# changed through manual intervention via the customize wizard.
# UEFI doesn't work with hyperv bits
if not self.hyperv_supported():
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.remove_timer(i)
def set_capabilities_defaults(self):
capsinfo = self.lookup_capsinfo()
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
self.set_capabilities_defaults()
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 _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 self.os.is_container():
return
if any([d.type == "usb" for d in self.devices.controller]):
return
usb2 = False
usb3 = False
if self.os.is_x86():
usb2 = True
elif (self.os.is_arm_machvirt() and
self.conn.check_support(
self.conn.SUPPORT_CONN_MACHVIRT_PCI_DEFAULT)):
usb3 = True
if not usb2 and not usb3:
return
if usb2:
if not self.conn.check_support(
self.conn.SUPPORT_CONN_DEFAULT_USB2):
return
for dev in DeviceController.get_usb2_controllers(self.conn):
self.add_device(dev)
if 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()