Hey Dan, All good points. How does this look? I cut out the validation stuff, added tests and changed the comment about try/except/finally. (I'm ignoring the post-install-check and requiring-initrd comments for now ... as you say, they're problems with the existing code) Cheers, Mark.
Subject: Add CapabilitiesParser module From: Mark McLoughlin <markmc@xxxxxxxxxx> 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,162 @@ +#!/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): + def __init__(self, node = None): + # e.g. "i686" or "x86_64" + self.arch = None + + # e.g. FEATURE_HVM|FEATURE_ACPI + self.features = 0 + + if not node is None: + self.parseXML(node) + + 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): + def __init__(self, node = None): + # e.g. "xen" or "hvm" + self.os_type = None + + # e.g. "xen", "qemu", "kqemu" or "kvm" + self.hypervisor_type = None + + # e.g. "i686" or "x86_64" + self.arch = None + + # e.g. FEATURE_HVM|FEATURE_ACPI + self.features = 0 + + if not node is None: + self.parseXML(node) + + 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 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: + # try/except/finally is only available in python-2.5 + 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 Index: virtinst--devel/tests/capabilities-qemu.xml =================================================================== --- /dev/null +++ virtinst--devel/tests/capabilities-qemu.xml @@ -0,0 +1,71 @@ +<capabilities> + <host> + <cpu> + <arch>x86_64</arch> + </cpu> + </host> + + <guest> + <os_type>hvm</os_type> + <arch name="x86_64"> + <wordsize>64</wordsize> + <emulator>/usr/bin/qemu-system-x86_64</emulator> + <domain type="qemu"/> + <machine>pc</machine> + <machine>isapc</machine> + </arch> + </guest> + + <guest> + <os_type>hvm</os_type> + <arch name="i686"> + <wordsize>32</wordsize> + <emulator>/usr/bin/qemu</emulator> + <domain type="qemu"/> + <machine>pc</machine> + <machine>isapc</machine> + </arch> + </guest> + + <guest> + <os_type>hvm</os_type> + <arch name="mips"> + <wordsize>32</wordsize> + <emulator>/usr/bin/qemu-system-mips</emulator> + <domain type="qemu"/> + <machine>mips</machine> + </arch> + </guest> + + <guest> + <os_type>hvm</os_type> + <arch name="mipsel"> + <wordsize>32</wordsize> + <emulator>/usr/bin/qemu-system-mipsel</emulator> + <domain type="qemu"/> + <machine>mips</machine> + </arch> + </guest> + + <guest> + <os_type>hvm</os_type> + <arch name="sparc"> + <wordsize>32</wordsize> + <emulator>/usr/bin/qemu-system-sparc</emulator> + <domain type="qemu"/> + <machine>sun4m</machine> + </arch> + </guest> + + <guest> + <os_type>hvm</os_type> + <arch name="ppc"> + <wordsize>32</wordsize> + <emulator>/usr/bin/qemu-system-ppc</emulator> + <domain type="qemu"/> + <machine>g3bw</machine> + <machine>mac99</machine> + <machine>prep</machine> + </arch> + </guest> +</capabilities> Index: virtinst--devel/tests/capabilities-test.xml =================================================================== --- /dev/null +++ virtinst--devel/tests/capabilities-test.xml @@ -0,0 +1,23 @@ +<capabilities> + <host> + <cpu> + <arch>i686</arch> + <features> + <pae/> + <nonpae/> + </features> + </cpu> + </host> + + <guest> + <os_type>linux</os_type> + <arch name="i686"> + <wordsize>32</wordsize> + <domain type="test"/> + </arch> + <features> + <pae/> + <nonpae/> + </features> + </guest> +</capabilities> Index: virtinst--devel/tests/capabilities-xen.xml =================================================================== --- /dev/null +++ virtinst--devel/tests/capabilities-xen.xml @@ -0,0 +1,61 @@ +<capabilities> + <host> + <cpu> + <arch>x86_64</arch> + <features> + <vmx/> + </features> + </cpu> + </host> + + <guest> + <os_type>xen</os_type> + <arch name="x86_64"> + <wordsize>64</wordsize> + <domain type="xen"></domain> + </arch> + <features> + </features> + </guest> + + <guest> + <os_type>xen</os_type> + <arch name="i686"> + <wordsize>32</wordsize> + <domain type="xen"></domain> + </arch> + <features> + <pae/> + </features> + </guest> + + <guest> + <os_type>hvm</os_type> + <arch name="i686"> + <wordsize>32</wordsize> + <domain type="xen"></domain> + <emulator>/usr/lib/xen/bin/qemu-dm</emulator> + <machine>pc</machine> + <machine>isapc</machine> + <loader>/usr/lib/xen/boot/hvmloader</loader> + </arch> + <features> + <pae/> + <nonpae/> + </features> + </guest> + + <guest> + <os_type>hvm</os_type> + <arch name="x86_64"> + <wordsize>64</wordsize> + <domain type="xen"></domain> + <emulator>/usr/lib64/xen/bin/qemu-dm</emulator> + <machine>pc</machine> + <machine>isapc</machine> + <loader>/usr/lib/xen/boot/hvmloader</loader> + </arch> + <features> + </features> + </guest> +</capabilities> Index: virtinst--devel/tests/capabilities.py =================================================================== --- /dev/null +++ virtinst--devel/tests/capabilities.py @@ -0,0 +1,57 @@ +import os.path +import unittest +import virtinst.CapabilitiesParser as capabilities + +class TestCapabilities(unittest.TestCase): + + def _compareGuest(self, (arch, os_type, hypervisor_type, features), guest): + self.assertEqual(arch, guest.arch) + self.assertEqual(os_type, guest.os_type) + self.assertEqual(hypervisor_type, guest.hypervisor_type) + self.assertEqual(features, guest.features) + + def _testCapabilities(self, path, (host_arch, host_features), guests): + caps = capabilities.parse(file(os.path.join("tests", path)).read()) + + self.assertEqual(host_arch, caps.host.arch) + self.assertEqual(host_features, caps.host.features) + + map(self._compareGuest, guests, caps.guests) + + def testCapabilities1(self): + host = ( 'x86_64', capabilities.FEATURE_VMX ) + + guests = [ + ( 'x86_64', 'xen', 'xen', 0 ), + ( 'i686', 'xen', 'xen', capabilities.FEATURE_PAE ), + ( 'i686', 'hvm', 'xen', capabilities.FEATURE_PAE|capabilities.FEATURE_NONPAE ), + ( 'x86_64', 'hvm', 'xen', 0 ) + ] + + self._testCapabilities("capabilities-xen.xml", host, guests) + + def testCapabilities2(self): + host = ( 'x86_64', 0 ) + + guests = [ + ( 'x86_64', 'hvm', 'qemu', 0 ), + ( 'i686', 'hvm', 'qemu', 0 ), + ( 'mips', 'hvm', 'qemu', 0 ), + ( 'mipsel', 'hvm', 'qemu', 0 ), + ( 'sparc', 'hvm', 'qemu', 0 ), + ( 'ppc', 'hvm', 'qemu', 0 ), + ] + + self._testCapabilities("capabilities-qemu.xml", host, guests) + + def testCapabilities3(self): + host = ( 'i686', capabilities.FEATURE_PAE|capabilities.FEATURE_NONPAE ) + + guests = [ + ( 'i686', 'linux', 'test', capabilities.FEATURE_PAE|capabilities.FEATURE_NONPAE ), + ] + + self._testCapabilities("capabilities-test.xml", host, guests) + +if __name__ == "__main__": + unittest.main()