virt-manager/virtinst/capabilities.py

530 lines
15 KiB
Python
Raw Normal View History

#
# Some code for parsing libvirt's capabilities XML
#
# Copyright 2007, 2012-2014 Red Hat, Inc.
# Mark McLoughlin <markmc@redhat.com>
#
# 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 <model> instance from cpu_map.xml
"""
_XML_ROOT_NAME = "model"
name = XMLProperty("./@name")
class _CPUMapArch(XMLBuilder):
"""
Single <arch> 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 <cpu> 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 <topology> 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 <host> and <secmodel> 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)
################################
# <guest> and <domain> 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)