This adds a simple module for parsing libvirt's getCapabilities() XML. It might seem more straightforward to just use XPath for this, but in the code to support arbitrary system images, you iterate over the various ways that the image can be booted and the various ways in which a hypervisor can boot a guest and find the best match. For that kind of use, the API below is much easier to use. Note, this parser rejects things like architectures it doesn't know about, which needs to be fixed in order to have forward compatibility. Signed-off-by: Mark McLoughlin <markmc@xxxxxxxxxx> Index: virtinst--devel/virtinst/CapabilitiesParser.py =================================================================== --- /dev/null +++ virtinst--devel/virtinst/CapabilitiesParser.py @@ -0,0 +1,245 @@ +#!/usr/bin/python -tt + +# Some code for parsing libvirt's capabilities XML +# +# Copyright 2007 Red Hat, Inc. +# Mark McLoughlin <markmc@xxxxxxxxxx> +# +# This software may be freely redistributed under the terms of the GNU +# general public license. +# +# 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., 675 Mass Ave, Cambridge, MA 02139, USA. + +import libxml2 + +class CapabilitiesParserException(Exception): + def __init__(self, msg): + Exception.__init__(self, msg) + +FEATURE_ACPI = 0x01 +FEATURE_APIC = 0x02 +FEATURE_PAE = 0x04 +FEATURE_NONPAE = 0x08 +FEATURE_VMX = 0x10 +FEATURE_SVM = 0x20 +FEATURE_IA64_BE = 0x40 + +features_map = { + "acpi" : FEATURE_ACPI, + "apic" : FEATURE_APIC, + "pae" : FEATURE_PAE, + "nonpae" : FEATURE_NONPAE, + "vmx" : FEATURE_VMX, + "svm" : FEATURE_SVM, + "ia64_be" : FEATURE_IA64_BE +} + +NUM_FEATURES = len(features_map) + +def _parse_features(self, node): + features = 0 + + child = node.children + while child: + if child.name in features_map: + features |= features_map[child.name] + + child = child.next + + return features + +class Host(object): + ARCHES = [ "i686", "x86_64" ] + + def __init__(self, node = None): + self._arch = None + self._features = 0 + + if not node is None: + self.parseXML(node) + + def get_arch(self): + return self._arch + def set_arch(self, arch): + if not arch in self.ARCHES: + raise CapabilitiesParserException("'%s' is not a supported architecture" % arch) + self._arch = arch + arch = property(get_arch, set_arch) + + def get_features(self): + return self._features + def set_features(self, features): + if (features >> NUM_FEATURES) != 0: + raise CapabilitiesParserException("Invalid feature set '0x%x'" % features) + self._features = features + features = property(get_features, set_features) + + def parseXML(self, node): + child = node.children + while child: + if child.name != "cpu": + child = child.next + continue + + n = child.children + while n: + if n.name == "arch": + self.arch = n.content + elif n.name == "features": + self.features |= _parse_features(self, n) + n = n.next + + child = child.next + +class Guest(object): + OS_TYPES = [ "xen", "hvm" ] + HYPERVISOR_TYPES = [ "xen", "qemu", "kqemu", "kvm" ] + ARCHES = [ "i686", "x86_64", "mips", "mipsel", "sparc", "ppc" ] + + def __init__(self, node = None): + self._os_type = None + self._hypervisor_type = None + self._arch = None + self._features = 0 + + if not node is None: + self.parseXML(node) + + def get_os_type(self): + return self._os_type + def set_os_type(self, os_type): + if not os_type in self.OS_TYPES: + raise CapabilitiesParserException("'%s' is not a supported OS type" % os_type) + self._os_type = os_type + os_type = property(get_os_type, set_os_type) + + def get_hypervisor_type(self): + return self._hypervisor_type + def set_hypervisor_type(self, hypervisor_type): + if not hypervisor_type in self.HYPERVISOR_TYPES: + raise CapabilitiesParserException("'%s' is not a supported hypervisor type" % hypervisor_type) + self._hypervisor_type = hypervisor_type + hypervisor_type = property(get_hypervisor_type, set_hypervisor_type) + + def get_arch(self): + return self._arch + def set_arch(self, arch): + if not arch in self.ARCHES: + raise CapabilitiesParserException("'%s' is not a supported architecture" % arch) + self._arch = arch + arch = property(get_arch, set_arch) + + def get_features(self): + return self._features + def set_features(self, features): + if (features >> NUM_FEATURES) != 0: + raise CapabilitiesParserException("Invalid feature set '0x%x'" % features) + self._features = features + features = property(get_features, set_features) + + def parseXML(self, node): + child = node.children + while child: + if child.name == "os_type": + self.os_type = child.content + elif child.name == "features": + self.features |= _parse_features(self, child) + elif child.name == "arch": + self.arch = child.prop("name") + n = child.children + while n: + # NB. for now, ignoring the rest of arch e.g. wordsize etc. + if n.name == "domain": + self.hypervisor_type = n.prop("type") + n = n.next + + child = child.next + +class Capabilities(object): + def __init__(self, node = None): + self._host = None + self.guests = [] + + if not node is None: + self.parseXML(node) + + def get_host(self): + return self._host + def set_host(self, host): + if not self._host is None: + raise CapabilitiesParserException("Only a single <host> element is allowed") + self._host = host + host = property(get_host, set_host) + + def parseXML(self, node): + child = node.children + while child: + if child.name == "host": + self.host = Host(child) + elif child.name == "guest": + self.guests.append(Guest(child)) + child = child.next + +def parse(xml): + class ErrorHandler: + def __init__(self): + self.msg = "" + def handler(self, ctx, str): + self.msg += str + error = ErrorHandler() + libxml2.registerErrorHandler(error.handler, None) + + try: + # + # FIXME: when we can rely on python-2.5 and its support for + # try ... except ... finally, remove this inner try + # + try: + doc = libxml2.readMemory(xml, len(xml), + None, None, + libxml2.XML_PARSE_NOBLANKS) + except (libxml2.parserError, libxml2.treeError), e: + raise CapabilitiesParserException("%s\n%s" % (e, error.msg)) + finally: + libxml2.registerErrorHandler(None, None) + + try: + root = doc.getRootElement() + if root.name != "capabilities": + raise CapabilitiesParserException("Root element is not 'capabilties'") + + capabilities = Capabilities(root) + finally: + doc.freeDoc() + + return capabilities + +if __name__ == "__main__": + import libvirt + + for uri in (None, "qemu:///system"): + cnx = libvirt.open(uri) + + caps = parse(cnx.getCapabilities()) + + print "host arch: %s" % caps.host.arch + if caps.host.features: + print "host features:" + for feature in features_map: + if caps.host.features & features_map[feature]: + print " " + feature + + for guest in caps.guests: + print + print "guest arch: %s" % guest.arch + print "guest os type: %s" % guest.os_type + print "guest hypervisor type: %s" % guest.hypervisor_type + if guest.features: + print "guest features:" + for feature in features_map: + if guest.features & features_map[feature]: + print " " + feature + + print --