# # Some code for parsing libvirt's capabilities XML # # Copyright 2007, 2012-2014 Red Hat, Inc. # # This work is licensed under the GNU GPLv2 or later. # See the COPYING file in the top-level directory. import pwd from .logger import log from .xmlbuilder import XMLBuilder, XMLChildProperty, XMLProperty ################################### # capabilities host parsing # ################################### class _CapsCPU(XMLBuilder): XML_NAME = "cpu" arch = XMLProperty("./arch") model = XMLProperty("./model") ###################################### # Caps and parsers # ###################################### class _CapsSecmodelBaselabel(XMLBuilder): XML_NAME = "baselabel" type = XMLProperty("./@type") content = XMLProperty(".") class _CapsSecmodel(XMLBuilder): XML_NAME = "secmodel" model = XMLProperty("./model") baselabels = XMLChildProperty(_CapsSecmodelBaselabel) class _CapsHost(XMLBuilder): XML_NAME = "host" secmodels = XMLChildProperty(_CapsSecmodel) cpu = XMLChildProperty(_CapsCPU, is_single=True) def get_qemu_baselabel(self): for secmodel in self.secmodels: if secmodel.model != "dac": continue label = None for baselabel in secmodel.baselabels: if baselabel.type in ["qemu", "kvm"]: label = baselabel.content break if not label: continue # pragma: no cover # XML we are looking at is like: # # # dac # 0 # +107:+107 # +107:+107 # try: uid = int(label.split(":")[0].replace("+", "")) user = pwd.getpwuid(uid)[0] return user, uid except Exception: log.debug("Exception parsing qemu dac baselabel=%s", label, exc_info=True) return None, None ################################ # and parsers # ################################ class _CapsMachine(XMLBuilder): XML_NAME = "machine" name = XMLProperty(".") canonical = XMLProperty("./@canonical") class _CapsDomain(XMLBuilder): XML_NAME = "domain" hypervisor_type = XMLProperty("./@type") emulator = XMLProperty("./emulator") machines = XMLChildProperty(_CapsMachine) class _CapsGuestFeatures(XMLBuilder): XML_NAME = "features" pae = XMLProperty("./pae", is_bool=True) acpi = XMLProperty("./acpi/@default", is_onoff=True) apic = XMLProperty("./apic/@default", is_onoff=True) class _CapsGuest(XMLBuilder): XML_NAME = "guest" os_type = XMLProperty("./os_type") arch = XMLProperty("./arch/@name") loader = XMLProperty("./arch/loader") emulator = XMLProperty("./arch/emulator") domains = XMLChildProperty(_CapsDomain, relative_xpath="./arch") features = XMLChildProperty(_CapsGuestFeatures, is_single=True) machines = XMLChildProperty(_CapsMachine, relative_xpath="./arch") ############### # Public APIs # ############### def all_machine_names(self, domain): """ Return all machine string names, including canonical aliases for the guest+domain combo but avoiding duplicates """ mobjs = (domain and domain.machines) or self.machines ret = [] for m in mobjs: ret.append(m.name) if m.canonical and m.canonical not in ret: ret.append(m.canonical) return ret def is_machine_alias(self, domain, src, tgt): """ Determine if machine @src is an alias for machine @tgt """ mobjs = (domain and domain.machines) or self.machines for m in mobjs: if m.name == src and m.canonical == tgt: return True return False def is_kvm_available(self): """ Return True if kvm guests can be installed """ for d in self.domains: if d.hypervisor_type == "kvm": return True return False def supports_pae(self): """ Return True if capabilities report support for PAE """ return bool(self.features.pae) def supports_acpi(self): """ Return Tree if capabilities report support for ACPI """ return bool(self.features.acpi) def supports_apic(self): """ Return Tree if capabilities report support for APIC """ return bool(self.features.apic) ############################ # Main capabilities object # ############################ class _CapsInfo(object): """ Container object to hold the results of guest_lookup, so users don't need to juggle two objects """ def __init__(self, conn, guest, domain): self.conn = conn self.guest = guest self.domain = domain self.hypervisor_type = self.domain.hypervisor_type self.os_type = self.guest.os_type self.arch = self.guest.arch self.loader = self.guest.loader self.emulator = self.domain.emulator or self.guest.emulator self.machines = self.guest.all_machine_names(self.domain) def is_machine_alias(self, src, tgt): return self.guest.is_machine_alias(self.domain, src, tgt) class Capabilities(XMLBuilder): def __init__(self, *args, **kwargs): XMLBuilder.__init__(self, *args, **kwargs) self._cpu_models_cache = {} XML_NAME = "capabilities" host = XMLChildProperty(_CapsHost, is_single=True) guests = XMLChildProperty(_CapsGuest) ############################ # Public XML building APIs # ############################ def _guestForOSType(self, os_type, arch): archs = [arch] if arch is None: archs = [self.host.cpu.arch, None] for a in archs: for g in self.guests: if ((os_type is None or g.os_type == os_type) and (a is None or g.arch == a)): return g def _bestDomainType(self, guest, dtype, machine): """ Return the recommended domain for use if the user does not explicitly request one. """ domains = [] for d in guest.domains: if dtype and d.hypervisor_type != dtype.lower(): continue if machine and machine not in guest.all_machine_names(d): continue domains.append(d) if not domains: return None priority = ["kvm", "xen", "qemu"] for t in priority: for d in domains: if d.hypervisor_type == t: return d # Fallback, just return last item in list return domains[-1] def has_install_options(self): """ Return True if there are any install options available """ for guest in self.guests: if guest.domains: return True return False def guest_lookup(self, os_type=None, arch=None, typ=None, machine=None): """ Simple virtualization availability lookup Convenience function for looking up 'Guest' and 'Domain' capabilities objects for the desired virt type. If type, arch, or os_type are none, we return the default virt type associated with those values. These are typically: - os_type : hvm, then xen - typ : kvm over plain qemu - arch : host arch over all others Otherwise the default will be the first listed in the capabilities xml. This function throws C{ValueError}s if any of the requested values are not found. :param typ: Virtualization type ('hvm', 'xen', ...) :param arch: Guest architecture ('x86_64', 'i686' ...) :param os_type: Hypervisor name ('qemu', 'kvm', 'xen', ...) :param machine: Optional machine type to emulate :returns: A _CapsInfo object containing the found guest and domain """ # F22 libxl xen still puts type=linux in the XML, so we need # to handle it for caps lookup if os_type == "linux": os_type = "xen" guest = self._guestForOSType(os_type, arch) if not guest: if arch and os_type: msg = (_("Host does not support virtualization type " "'%(virttype)s' for architecture '%(arch)s'") % {'virttype': os_type, 'arch': arch}) elif arch: msg = (_("Host does not support any virtualization options " "for architecture '%(arch)s'") % {'arch': arch}) elif os_type: msg = (_("Host does not support virtualization type " "'%(virttype)s'") % {'virttype': os_type}) else: msg = _("Host does not support any virtualization options") raise ValueError(msg) domain = self._bestDomainType(guest, typ, machine) if domain is None: if machine: msg = (_("Host does not support domain type %(domain)s with " "machine '%(machine)s' for virtualization type " "'%(virttype)s' with architecture '%(arch)s'") % {'domain': typ, 'virttype': guest.os_type, 'arch': guest.arch, 'machine': machine}) else: msg = (_("Host does not support domain type %(domain)s for " "virtualization type '%(virttype)s' with " "architecture '%(arch)s'") % {'domain': typ, 'virttype': guest.os_type, 'arch': guest.arch}) raise ValueError(msg) capsinfo = _CapsInfo(self.conn, guest, domain) return capsinfo