# # Some code for parsing libvirt's capabilities XML # # Copyright 2007, 2012-2014 Red Hat, Inc. # Mark McLoughlin # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, # MA 02110-1301 USA. import logging import os import re from .cpu import CPU as DomainCPU from .xmlbuilder import XMLBuilder, XMLChildProperty, XMLProperty ########################## # CPU model list objects # ########################## class _CPUMapModel(XMLBuilder): """ Single instance from cpu_map.xml """ _XML_ROOT_NAME = "model" name = XMLProperty("./@name") class _CPUMapArch(XMLBuilder): """ Single instance of valid CPU from cpu_map.xml """ _XML_ROOT_NAME = "arch" arch = XMLProperty("./@name") models = XMLChildProperty(_CPUMapModel) class _CPUMapFileValues(XMLBuilder): """ Fallback method to lists cpu models, parsed directly from libvirt's local cpu_map.xml """ # This is overwritten as part of the test suite _cpu_filename = "/usr/share/libvirt/cpu_map.xml" def __init__(self, conn): if os.path.exists(self._cpu_filename): xml = file(self._cpu_filename).read() else: xml = None logging.debug("CPU map file not found: %s", self._cpu_filename) XMLBuilder.__init__(self, conn, parsexml=xml) self._archmap = {} _cpuvalues = XMLChildProperty(_CPUMapArch) ############## # Public API # ############## def get_cpus(self, arch): if re.match(r'i[4-9]86', arch): arch = "x86" elif arch == "x86_64": arch = "x86" cpumap = self._archmap.get(arch) if not cpumap: for vals in self._cpuvalues: if vals.arch == arch: cpumap = vals if not cpumap: # Create a stub object cpumap = _CPUMapArch(self.conn) self._archmap[arch] = cpumap return [m.name for m in cpumap.models] class _CPUAPIValues(object): """ Lists valid values for cpu models obtained from libvirt's getCPUModelNames """ def __init__(self, conn): self.conn = conn self._cpus = None def get_cpus(self, arch): if self._cpus is not None: return self._cpus if self.conn.check_support(self.conn.SUPPORT_CONN_CPU_MODEL_NAMES): names = self.conn.getCPUModelNames(arch, 0) # Bindings were broke for a long time, so catch -1 if names != -1: self._cpus = names return self._cpus return [] ################################### # capabilities host parsing # ################################### class _CapsCPU(DomainCPU): arch = XMLProperty("./arch") # capabilities used to just expose these properties as bools _svm_bool = XMLProperty("./features/svm", is_bool=True) _vmx_bool = XMLProperty("./features/vmx", is_bool=True) ############## # Public API # ############## def has_feature(self, name): if name == "svm" and self._svm_bool: return True if name == "vmx" and self._vmx_bool: return True return name in [f.name for f in self.features] ########################### # Caps parsers # ########################### class _CapsTopologyCPU(XMLBuilder): _XML_ROOT_NAME = "cpu" id = XMLProperty("./@id") class _TopologyCell(XMLBuilder): _XML_ROOT_NAME = "cell" cpus = XMLChildProperty(_CapsTopologyCPU, relative_xpath="./cpus") class _CapsTopology(XMLBuilder): _XML_ROOT_NAME = "topology" cells = XMLChildProperty(_TopologyCell, relative_xpath="./cells") ###################################### # Caps and parsers # ###################################### class _CapsSecmodelBaselabel(XMLBuilder): _XML_ROOT_NAME = "baselabel" type = XMLProperty("./@type") content = XMLProperty(".") class _CapsSecmodel(XMLBuilder): _XML_ROOT_NAME = "secmodel" model = XMLProperty("./model") baselabels = XMLChildProperty(_CapsSecmodelBaselabel) class _CapsHost(XMLBuilder): _XML_ROOT_NAME = "host" secmodels = XMLChildProperty(_CapsSecmodel) cpu = XMLChildProperty(_CapsCPU, is_single=True) topology = XMLChildProperty(_CapsTopology, is_single=True) ################################ # and parsers # ################################ class _CapsMachine(XMLBuilder): _XML_ROOT_NAME = "machine" name = XMLProperty(".") canonical = XMLProperty("./@canonical") class _CapsDomain(XMLBuilder): def __init__(self, *args, **kwargs): XMLBuilder.__init__(self, *args, **kwargs) self.machines = [] for m in self._machines: self.machines.append(m.name) if m.canonical: self.machines.append(m.canonical) _XML_ROOT_NAME = "domain" hypervisor_type = XMLProperty("./@type") emulator = XMLProperty("./emulator") _machines = XMLChildProperty(_CapsMachine) class _CapsGuestFeatures(XMLBuilder): _XML_ROOT_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): def __init__(self, *args, **kwargs): XMLBuilder.__init__(self, *args, **kwargs) machines = [] for m in self._machines: machines.append(m.name) if m.canonical: machines.append(m.canonical) for d in self.domains: if not d.emulator: d.emulator = self._emulator if not d.machines: d.machines = machines _XML_ROOT_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 bestDomainType(self, dtype=None, machine=None): """ Return the recommended domain for use if the user does not explicitly request one. """ domains = [] for d in self.domains: if dtype and d.hypervisor_type != dtype.lower(): continue if machine and machine not in d.machines: 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 """ return bool(len(self.domains) > 0) def is_kvm_available(self): """ Return True if kvm guests can be installed """ if self.os_type != "hvm": return False 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, requested_machine): self.conn = conn self.guest = guest self.domain = domain self._requested_machine = requested_machine 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 self.machines = self.domain.machines[:] def get_recommended_machine(self): """ Return the recommended machine type. However, if the user already requested an explicit machine type, via guest_lookup, return that instead. """ if self._requested_machine: return self._requested_machine # For any other HV just let libvirt get us the default, these # are the only ones we've tested. if not self.conn.is_test() and not self.conn.is_qemu(): return None if (self.arch in ["ppc64", "ppc64le"] and "pseries" in self.machines): return "pseries" if self.arch in ["armv7l", "aarch64"]: if "virt" in self.machines: return "virt" if "vexpress-a15" in self.machines: return "vexpress-a15" if self.arch in ["s390x"]: if "s390-ccw-virtio" in self.machines: return "s390-ccw-virtio" return None class Capabilities(XMLBuilder): # Set by the test suite to force a particular code path _force_cpumap = False def __init__(self, *args, **kwargs): XMLBuilder.__init__(self, *args, **kwargs) self._cpu_values = None _XML_ROOT_NAME = "capabilities" host = XMLChildProperty(_CapsHost, is_single=True) guests = XMLChildProperty(_CapsGuest) ################### # Private helpers # ################### def _is_xen(self): for g in self.guests: if g.os_type != "xen": continue for d in g.domains: if d.hypervisor_type == "xen": return True return False ############## # Public API # ############## def get_cpu_values(self, arch): if not arch: return [] if self._cpu_values: return self._cpu_values.get_cpus(arch) order = [_CPUAPIValues, _CPUMapFileValues] if self._force_cpumap: order = [_CPUMapFileValues] # Iterate over the available methods until a set of CPU models is found for mode in order: cpu_values = mode(self.conn) cpus = cpu_values.get_cpus(arch) if len(cpus) > 0: self._cpu_values = cpu_values return cpus return [] ############################ # Public XML building APIs # ############################ def _guestForOSType(self, typ=None, arch=None): if self.host is None: return None archs = [arch] if arch is None: archs = [self.host.cpu.arch, None] for a in archs: for g in self.guests: if ((typ is None or g.os_type == typ) and (a is None or g.arch == a)): return g 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: archstr = _("for arch '%s'") % arch if not arch: archstr = "" osstr = _("virtualization type '%s'") % os_type if not os_type: osstr = _("any virtualization options") raise ValueError(_("Host does not support %(virttype)s %(arch)s") % {'virttype' : osstr, 'arch' : archstr}) domain = guest.bestDomainType(dtype=typ, machine=machine) if domain is None: machinestr = " with machine '%s'" % machine if not machine: machinestr = "" raise ValueError(_("Host does not support domain type %(domain)s" "%(machine)s for virtualization type " "'%(virttype)s' arch '%(arch)s'") % {'domain': typ, 'virttype': guest.os_type, 'arch': guest.arch, 'machine': machinestr}) capsinfo = _CapsInfo(self.conn, guest, domain, machine) return capsinfo def build_virtinst_guest(self, capsinfo): """ Fill in a new Guest() object from the results of guest_lookup """ from .guest import Guest gobj = Guest(self.conn) gobj.type = capsinfo.hypervisor_type gobj.os.os_type = capsinfo.os_type gobj.os.arch = capsinfo.arch gobj.os.loader = capsinfo.loader gobj.emulator = capsinfo.emulator gobj.os.machine = capsinfo.get_recommended_machine() gobj.capsinfo = capsinfo return gobj def lookup_virtinst_guest(self, *args, **kwargs): """ Call guest_lookup and pass the results to build_virtinst_guest. This is a shortcut for API users that don't need to do anything with the output from guest_lookup """ capsinfo = self.guest_lookup(*args, **kwargs) return self.build_virtinst_guest(capsinfo)