# # Common code for all guests # # Copyright 2006-2009, 2013, 2014, 2015 Red Hat, Inc. # Jeremy Katz # # 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) @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) # 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 self.x86_cpu_default = self.cpu.SPECIAL_MODE_HOST_MODEL_ONLY 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) 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) curvcpus = XMLProperty("./vcpu/@current", is_int=True) vcpu_placement = XMLProperty("./vcpu/@placement") cpuset = XMLProperty("./vcpu/@cpuset") uuid = XMLProperty("./uuid") id = XMLProperty("./@id", is_int=True) type = XMLProperty("./@type") bootloader = XMLProperty("./bootloader") description = XMLProperty("./description") title = XMLProperty("./title") 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 # ############################## def _set_osinfo(self, variant): obj = OSDB.lookup_os(variant) if not obj: obj = OSDB.lookup_os("generic") 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): 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) 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()) def stable_defaults(self, *args, **kwargs): return self.conn.stable_defaults(self.emulator, *args, **kwargs) ############################### # 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" def has_spice(self): for gfx in self.devices.graphics: if gfx.type == gfx.TYPE_SPICE: return True def has_gl(self): for gfx in self.devices.graphics: if gfx.gl: return True 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( os_type=self.os.os_type, arch=self.os.arch, typ=self.type, machine=self.os.machine) return self._capsinfo 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(): 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 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 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()