[libvirt] [RFC 08/15] qmp: Add 'supported-device-types' field to 'query-machines'

Eduardo Habkost ehabkost at redhat.com
Tue Nov 22 01:12:06 UTC 2016


The new field will return a list of device type names that are
compatible with the default bus configuration of the
machine-type. The list can be easily built using the
MachineClass::default_bus_types and
BusClass::supported_device_type fields.

The returned types can be used as the 'implements' parameter to
'qom-list-types', to find the list of devices that can be plugged
on the machine.

Note that some machine options may enable or disable some bus
types and affect the set of compatible device types.
Introspection of those options is out of the scope of this patch.

Includes a qtest test case that will validate the returned by
actually running each machine-type and checking the list of
available buses.

Cc: libvir-list at redhat.com
Cc: Laine Stump <laine at redhat.com>
Signed-off-by: Eduardo Habkost <ehabkost at redhat.com>
---
 qapi-schema.json          |   9 ++-
 tests/Makefile.include    |   2 +
 tests/qmp-machine-info.py | 138 ++++++++++++++++++++++++++++++++++++++++++++++
 vl.c                      |  11 ++++
 4 files changed, 159 insertions(+), 1 deletion(-)
 create mode 100755 tests/qmp-machine-info.py

diff --git a/qapi-schema.json b/qapi-schema.json
index f3e9bfc..6db397d 100644
--- a/qapi-schema.json
+++ b/qapi-schema.json
@@ -3178,12 +3178,19 @@
 #
 # @hotpluggable-cpus: cpu hotplug via -device is supported (since 2.7.0)
 #
+# @supported-device-types: List of QOM type names of devices that
+#                          can be plugged on the machine by default.
+#                          Note that some machine options may enable
+#                          or disable some bus types and affect the
+#                          set of compatible device types. (since 2.9)
+#
 # Since: 1.2.0
 ##
 { 'struct': 'MachineInfo',
   'data': { 'name': 'str', '*alias': 'str',
             '*is-default': 'bool', 'cpu-max': 'int',
-            'hotpluggable-cpus': 'bool'} }
+            'hotpluggable-cpus': 'bool',
+            'supported-device-types': [ 'str' ] } }
 
 ##
 # @query-machines:
diff --git a/tests/Makefile.include b/tests/Makefile.include
index eb1031b..7016737 100644
--- a/tests/Makefile.include
+++ b/tests/Makefile.include
@@ -315,6 +315,8 @@ check-qtest-s390x-y = tests/boot-serial-test$(EXESUF)
 
 check-qtest-generic-y += tests/qom-test$(EXESUF)
 
+check-simpleqtest-generic-y += $(SRC_PATH)/tests/qmp-machine-info.py
+
 qapi-schema += alternate-any.json
 qapi-schema += alternate-array.json
 qapi-schema += alternate-base.json
diff --git a/tests/qmp-machine-info.py b/tests/qmp-machine-info.py
new file mode 100755
index 0000000..5258434
--- /dev/null
+++ b/tests/qmp-machine-info.py
@@ -0,0 +1,138 @@
+#!/usr/bin/env python
+import sys, os
+sys.path.append(os.path.join(os.path.dirname(__file__), '..', 'scripts'))
+import qtest
+import unittest
+import logging
+import argparse
+
+logger = logging.getLogger('qemu.tests.machineinfo')
+
+# machines that we can't easily test because they can't run on all hosts:
+BLACKLIST = set(['xenpv', 'xenfv'])
+
+# architectures where machines are expected to report all available buses:
+STRICT_ARCHES = set()
+
+class QueryMachinesTest(unittest.TestCase):
+    def walkQOMTree(self, vm, path):
+        """Walk QOM tree recusrively, starting at path"""
+        children = vm.qmp('qom-list', path=path)['return']
+        for c in children:
+            logging.debug('walking %s. child: %s', path, c)
+            if not c['type'].startswith('child<'):
+                continue
+
+            cp = '%s/%s' % (path, c['name'])
+            yield cp
+
+            for gc in self.walkQOMTree(vm, cp):
+                yield gc
+
+    def findAllBuses(self, vm):
+        """Find all bus objects in the QOM tree"""
+        r = vm.qmp('qom-list-types', implements='bus')
+        bus_types = set([b['name'] for b in r['return']])
+        for cp in self.walkQOMTree(vm, '/machine'):
+            t = vm.qmp('qom-get', path=cp, property='type')['return']
+            if t in bus_types:
+                dt = vm.qmp('qom-get', path=cp, property='device-type')['return']
+                yield dict(path=cp, type=t, device_type=dt)
+
+    def validateSupportedDeviceTypes(self, machine):
+        """Validate 'supported-device-types' on 'query-machines'"""
+        if machine['name'] in BLACKLIST:
+            return
+
+        vm = qtest.QEMUQtestMachine(args=['-S', '-machine', machine['name']], logging=False)
+        vm.launch()
+        try:
+            buses = list(self.findAllBuses(vm))
+            bus_types = set([b['type'] for b in buses])
+            device_types = set([b['device_type'] for b in buses])
+            logger.debug("buses for machine %s: %s", machine['name'],
+                         ' '.join(bus_types))
+            logger.debug("device-type for machine %s: %s", machine['name'],
+                         ' '.join(device_types))
+            reported_types = set(machine['supported-device-types'])
+            extra_types = reported_types.difference(device_types)
+            missing_types = device_types.difference(reported_types)
+            # the machine MUST NOT report any types if the bus is not available
+            # by default (in other words, extra_types should be empty)
+            self.assertEquals(extra_types, set())
+            # missing_types, on the other hand, may be empty. sometimes
+            # a bus is available but the machine doesn't report it yet
+            if missing_types:
+                logger.info("extra device types present on machine %s: %s",
+                            machine['name'], ' '.join(missing_types))
+                if vm.qmp('query-target')['return']['arch'] in STRICT_ARCHES:
+                    self.fail("extra device types: %s" % (' '.join(missing_types)))
+        finally:
+            vm.shutdown()
+
+    @classmethod
+    def addMachineTest(klass, method, machine):
+        """Dynamically add a testMachine_<name>_<machine> method to the class"""
+        def testMachine(self):
+            return method(self, machine)
+        machine_name = machine['name'].replace('-', '_').replace('.', '_')
+        method_name = 'testMachine_%s_%s' % (method.__name__, machine_name)
+        setattr(klass, method_name, testMachine)
+
+    @classmethod
+    def discoverMachines(klass):
+        """Run query-machines
+
+        This method is run before test cases are started, so we
+        can dynamically add test cases for each machine supported
+        by the binary.
+        """
+        vm = qtest.QEMUQtestMachine(args=['-S', '-machine', 'none'], logging=False)
+        vm.launch()
+        try:
+            machines = vm.qmp('query-machines')['return']
+        finally:
+            vm.shutdown()
+        return machines
+
+    @classmethod
+    def addMachineTestCases(klass):
+        """Dynamically add test methods for each machine-type"""
+        machines = klass.discoverMachines()
+        for m in machines:
+            klass.addMachineTest(klass.validateSupportedDeviceTypes, m)
+
+def load_tests(loader, tests=None, pattern=None):
+    QueryMachinesTest.addMachineTestCases()
+    ts = unittest.TestSuite()
+    tests = loader.loadTestsFromTestCase(QueryMachinesTest)
+    ts.addTests(tests)
+    return ts
+
+def main(argv):
+    # we don't use unittest.main() because:
+    # 1) it doesn't configure logging level
+    # 2) it doesn't let us disable all output
+    parser = argparse.ArgumentParser()
+    parser.add_argument('--verbosity', help='Set verbosity',
+                        default=1, type=int)
+    parser.add_argument('--quiet', '-q', help='Run tests quietly',
+                        action='store_const', dest='verbosity', const=0)
+    parser.add_argument('--verbose', '-v', help='Run tests verbosely',
+                        action='store_const', dest='verbosity', const=2)
+    parser.add_argument('--debug', '-d', help='Debug output',
+                        action='store_const', dest='verbosity', const=4)
+    args = parser.parse_args(argv[1:])
+    if args.verbosity == 0:
+        output = open('/dev/null', 'w')
+    else:
+        output = sys.stderr
+    if args.verbosity >= 4:
+        logging.basicConfig(level=logging.DEBUG)
+    elif args.verbosity >= 2:
+        logging.basicConfig(level=logging.INFO)
+    tests = load_tests(unittest.TestLoader(), None, None)
+    unittest.TextTestRunner(stream=output, verbosity=args.verbosity).run(tests)
+
+if __name__ == '__main__':
+    main(sys.argv)
diff --git a/vl.c b/vl.c
index d77dd86..af862db 100644
--- a/vl.c
+++ b/vl.c
@@ -1541,6 +1541,8 @@ MachineInfoList *qmp_query_machines(Error **errp)
         MachineClass *mc = el->data;
         MachineInfoList *entry;
         MachineInfo *info;
+        GList *bus;
+        strList **next_dev;
 
         info = g_malloc0(sizeof(*info));
         if (mc->is_default) {
@@ -1557,6 +1559,15 @@ MachineInfoList *qmp_query_machines(Error **errp)
         info->cpu_max = !mc->max_cpus ? 1 : mc->max_cpus;
         info->hotpluggable_cpus = !!mc->query_hotpluggable_cpus;
 
+        next_dev = &info->supported_device_types;
+        for (bus = mc->default_buses; bus; bus = bus->next) {
+            BusClass *bc = BUS_CLASS(object_class_by_name(bus->data));
+            strList *new = g_new0(strList, 1);
+            new->value = g_strdup(bc->device_type);
+            *next_dev = new;
+            next_dev = &new->next;
+        }
+
         entry = g_malloc0(sizeof(*entry));
         entry->value = info;
         entry->next = mach_list;
-- 
2.7.4




More information about the libvir-list mailing list