The new field will return a list MachineBusInfo structs,
containing information about the buses that are always created by
the machine (even if -nodefaults is used).

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

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

As a TYPE_SYSTEM_BUS bus is always created, add it to the default
list on TYPE_MACHINE.

Cc: libvir-l...@redhat.com
Cc: Laine Stump <la...@redhat.com>
Signed-off-by: Eduardo Habkost <ehabk...@redhat.com>
---
Changes series v1 -> v2:
* Replacing patches from v1:
  * machine: Add MachineClass::default_buses field
  * qmp: Add 'supported-device-types' field to 'query-machines'
* Replace 'supported-device-types' string list with
  'always-available-buses' MachineBusInfo list
* Make the new field optional, so "strict mode" will
  be always enabled when the field is present
* Don't include sysbus on TYPE_MACHINE, as not
  all machines will return the field
* Test code changes:
  * Update to use the new 'supported-device-types' field
  * Use unittest.main() instead of custom main() function
    * Add QTEST_LOG_LEVEL variable to control logging level
  * Include architecture on test case name
  * Simulate -nodefaults
  * Rewrote machine-type discovery hack
  * Run two test cases for each machine:
    using -nodefaults and without -nodefaults
  * Blacklist known machines that won't work with
    -nodefaults
  * Enable strict mode only when using -nodefaults
---
 hw/core/machine.c         |  33 ++++++++-
 include/hw/boards.h       |   7 ++
 qapi-schema.json          |  37 +++++++++-
 tests/Makefile.include    |   2 +
 tests/qmp-machine-info.py | 173 ++++++++++++++++++++++++++++++++++++++++++++++
 vl.c                      |   6 ++
 6 files changed, 256 insertions(+), 2 deletions(-)
 create mode 100755 tests/qmp-machine-info.py

diff --git a/hw/core/machine.c b/hw/core/machine.c
index b0fd91f..5be1297 100644
--- a/hw/core/machine.c
+++ b/hw/core/machine.c
@@ -19,6 +19,7 @@
 #include "sysemu/sysemu.h"
 #include "qemu/error-report.h"
 #include "qemu/cutils.h"
+#include "qapi/clone-visitor.h"
 
 static char *machine_get_accel(Object *obj, Error **errp)
 {
@@ -357,6 +358,32 @@ static void machine_init_notify(Notifier *notifier, void 
*data)
     foreach_dynamic_sysbus_device(error_on_sysbus_device, NULL);
 }
 
+
+/* Add an item to always_available_bus list
+ *
+ * The accepted_device_types field is automatically filled using
+ * BusClass::device_type.
+ */
+MachineBusInfo *machine_class_add_always_available_bus(MachineClass *mc,
+                                                       const char *bus_id,
+                                                       const char *bus_type)
+{
+    BusClass *bc = BUS_CLASS(object_class_by_name(bus_type));
+    MachineBusInfo *bi = g_new0(MachineBusInfo, 1);
+    MachineBusInfoList *bl = g_new0(MachineBusInfoList, 1);
+
+    bi->bus_id = g_strdup(bus_id);
+    bi->bus_type = g_strdup(bus_type);
+    bi->accepted_device_types = g_new0(strList, 1);
+    bi->accepted_device_types->value = g_strdup(bc->device_type);
+
+    bl->value = bi;
+    bl->next = mc->always_available_buses;
+    mc->always_available_buses = bl;
+
+    return bi;
+}
+
 static void machine_class_init(ObjectClass *oc, void *data)
 {
     MachineClass *mc = MACHINE_CLASS(oc);
@@ -466,13 +493,17 @@ static void machine_class_init(ObjectClass *oc, void 
*data)
 
 static void machine_class_base_init(ObjectClass *oc, void *data)
 {
+    MachineClass *mc = MACHINE_CLASS(oc);
+
     if (!object_class_is_abstract(oc)) {
-        MachineClass *mc = MACHINE_CLASS(oc);
         const char *cname = object_class_get_name(oc);
         assert(g_str_has_suffix(cname, TYPE_MACHINE_SUFFIX));
         mc->name = g_strndup(cname,
                             strlen(cname) - strlen(TYPE_MACHINE_SUFFIX));
     }
+
+    mc->always_available_buses =
+        QAPI_CLONE(MachineBusInfoList, mc->always_available_buses);
 }
 
 static void machine_initfn(Object *obj)
diff --git a/include/hw/boards.h b/include/hw/boards.h
index a51da9c..915a46d 100644
--- a/include/hw/boards.h
+++ b/include/hw/boards.h
@@ -42,6 +42,10 @@ bool machine_dump_guest_core(MachineState *machine);
 bool machine_mem_merge(MachineState *machine);
 void machine_register_compat_props(MachineState *machine);
 
+MachineBusInfo *machine_class_add_always_available_bus(MachineClass *mc,
+                                                       const char *bus_id,
+                                                       const char *bus_type);
+
 /**
  * CPUArchId:
  * @arch_id - architecture-dependent CPU ID of present or possible CPU
@@ -92,6 +96,8 @@ typedef struct {
  *    size than the target architecture's minimum. (Attempting to create
  *    such a CPU will fail.) Note that changing this is a migration
  *    compatibility break for the machine.
+ * @default_buses:
+ *    List of typenames of buses that are created by default by the machine.
  */
 struct MachineClass {
     /*< private >*/
@@ -131,6 +137,7 @@ struct MachineClass {
     bool option_rom_has_mr;
     bool rom_file_has_mr;
     int minimum_page_bits;
+    MachineBusInfoList *always_available_buses;
 
     HotplugHandler *(*get_hotplug_handler)(MachineState *machine,
                                            DeviceState *dev);
diff --git a/qapi-schema.json b/qapi-schema.json
index f3e9bfc..807c5a8 100644
--- a/qapi-schema.json
+++ b/qapi-schema.json
@@ -3163,6 +3163,29 @@
 { 'command': 'closefd', 'data': {'fdname': 'str'} }
 
 ##
+# @MachineBusInfo
+#
+# Information about a bus present on a machine.
+#
+# @bus-id: Path of the bus. Can be a partial path, as long
+#          as it is not ambiguous and can be used as
+#          the @path argument of @qom-get and @bus argument
+#          of @device_add
+#
+# @bus-type: Type name of the bus
+#
+# @accepted-device-types: List of device types accepted by the bus.
+#                         The type names can be used as the @implements
+#                         parameter of @qom-list-types to find the full
+#                         list of device types accepted by the bus.
+#
+# Since: 2.9.0
+##
+{ 'struct': 'MachineBusInfo',
+  'data': { 'bus-id': 'str', 'bus-type': 'str',
+            'accepted-device-types': [ 'str' ] } }
+
+##
 # @MachineInfo:
 #
 # Information describing a machine.
@@ -3178,12 +3201,24 @@
 #
 # @hotpluggable-cpus: cpu hotplug via -device is supported (since 2.7.0)
 #
+# @always-available-buses: Information on buses that are always available
+#                          on the machine. The buses present on this list
+#                          will be available on the machine even if the
+#                          "-nodefaults" option is present on the command-line.
+#                          The field is not provided by all machines, but if
+#                          it is present software must make no assumptions
+#                          about the buses on the machine, and their IDs, and
+#                          should use this field to build -device arguments.
+#                          configuration options.
+#                          (since 2.9.0)
+#
 # Since: 1.2.0
 ##
 { 'struct': 'MachineInfo',
   'data': { 'name': 'str', '*alias': 'str',
             '*is-default': 'bool', 'cpu-max': 'int',
-            'hotpluggable-cpus': 'bool'} }
+            'hotpluggable-cpus': 'bool',
+            '*always-available-buses': [ 'MachineBusInfo' ] } }
 
 ##
 # @query-machines:
diff --git a/tests/Makefile.include b/tests/Makefile.include
index 63c4347..feb65ea 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..b113eeb
--- /dev/null
+++ b/tests/qmp-machine-info.py
@@ -0,0 +1,173 @@
+#!/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'])
+
+# machines known to be broken when using -nodefaults:
+NODEFAULTS_BLACKLIST = set([
+    'cubieboard',      # segfaults
+    'petalogix-ml605', # segfaults
+    'or32-sim',        # segfaults
+    'virtex-ml507',    # segfaults
+    'Niagara',         # segfaults
+    'akita',           # "qemu: missing SecureDigital device"
+    'borzoi',          # "qemu: missing SecureDigital device"
+    'cheetah',         # "qemu: missing SecureDigital device"
+    'connex',          # "qemu: missing SecureDigital device"
+    'mainstone',       # "qemu: missing SecureDigital device"
+    'n800',            # "qemu: missing SecureDigital device"
+    'n810',            # "qemu: missing SecureDigital device"
+    'spitz',           # "qemu: missing SecureDigital device"
+    'sx1',             # "qemu: missing SecureDigital device"
+    'sx1-v1',          # "qemu: missing SecureDigital device"
+    'terrier',         # "qemu: missing SecureDigital device"
+    'tosa',            # "qemu: missing SecureDigital device"
+    'verdex',          # "qemu: missing SecureDigital device"
+    'z2',              # "qemu: missing SecureDigital device"
+])
+
+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='accepted-device-types').get('return')
+                yield dict(path=cp, type=t, accepted_device_types=dt)
+
+    def checkBuses(self, machine, extra_args=[], strict_mode=False):
+        """Validate 'supported-device-types' on 'query-machines'"""
+        if machine['name'] in BLACKLIST:
+            self.skipTest("machine %s on BLACKLIST" % (machine['name']))
+
+        if not machine.has_key('always-available-buses'):
+            self.skipTest('machine %s has no always-available-buses field' %
+                          (machine['name']))
+
+        args = ['-S', '-machine', machine['name']]
+        args.extend(extra_args)
+        logger.debug('QEMU args: %s', ' '.join(args))
+        vm = qtest.QEMUQtestMachine(args=args, logging=False)
+        vm.launch()
+        try:
+            found_buses = set()
+            for b in machine['always-available-buses']:
+                bus_id = b['bus-id']
+                btype = vm.qmp('qom-get', path=bus_id, 
property='type').get('return')
+                self.assertEquals(btype, b['bus-type'], "bus-type mismatch for 
%s" % (bus_id))
+                devtypes = vm.qmp('qom-get', path=bus_id, 
property='accepted-device-types').get('return')
+                self.assertEquals(set(devtypes), 
set(b['accepted-device-types']), "device-type msimatch for %s" % (bus_id))
+
+                found_buses.add(bus_id)
+
+            all_buses = list(self.findAllBuses(vm))
+            missing_buses = []
+            for b in all_buses:
+                full_path = b['path']
+                short_name = full_path.split('/')[-1]
+                if full_path in found_buses or short_name in found_buses:
+                    found_buses.discard(full_path)
+                    found_buses.discard(short_name)
+                    logger.debug("bus %s was found", full_path)
+                    continue
+                missing_buses.append(full_path)
+
+            if found_buses:
+                self.fail("Unexpected inconsistency: some buses were found 
using qom-get, but not on the device tree: %r", found_buses)
+
+            if missing_buses:
+                logger.info("missing buses on machine %s: %s",
+                            machine['name'], ' '.join(missing_buses))
+                if strict_mode:
+                    self.fail("missing buses: %s" % (' '.join(missing_buses)))
+        finally:
+            vm.shutdown()
+
+    def machineTestDefaultBuses(self, machine):
+        self.checkBuses(machine, [], False)
+
+    def machineTestNodefaultsBuses(self, machine):
+        if machine['name'] in NODEFAULTS_BLACKLIST:
+            self.skipTest("machine %s on NODEFAULTS_BLACKLIST" % 
(machine['name']))
+
+        self.checkBuses(machine, ['-nodefaults'], True)
+
+    @classmethod
+    def addMachineTest(klass, method_name, machine):
+        """Dynamically add a testMachine_<arch>_<name>_<machine> method to the 
class"""
+        method = getattr(klass, method_name)
+        def testMachine(self):
+            return method(self, machine)
+        machine_name = machine['name'].replace('-', '_').replace('.', '_')
+        method_name = 'test_%s_%s_%s' % (method_name, machine['arch'], 
machine_name)
+        setattr(klass, method_name, testMachine)
+        return method_name
+
+
+    @classmethod
+    def discoverMachines(klass, binary):
+        """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(binary=binary, args=['-S', '-machine', 
'none'], logging=False)
+        vm.launch()
+        try:
+            arch = vm.qmp('query-target')['return']['arch']
+            machines = vm.qmp('query-machines')['return']
+            for m in machines:
+                m['arch'] = arch
+        finally:
+            vm.shutdown()
+        return machines
+
+    @classmethod
+    def addMachineTests(klass, binary):
+        """Dynamically add test methods for each machine found on QEMU binary
+
+        Look for all methods with "machineTest" prefix, and add
+        custom test methods that will test them, for each machine-type
+        found on QEMU binary 'binary'.
+        """
+        method_names = unittest.loader.getTestCaseNames(klass, 
prefix='machineTest')
+        machines = klass.discoverMachines(binary)
+        for machine in machines:
+            for mname in method_names:
+                klass.addMachineTest(mname, machine)
+
+
+if os.getenv('QTEST_QEMU_BINARY'):
+    QueryMachinesTest.addMachineTests(os.getenv('QTEST_QEMU_BINARY'))
+
+if __name__ == '__main__':
+    if os.getenv('QTEST_LOG_LEVEL'):
+        logging.basicConfig(level=int(os.getenv('QTEST_LOG_LEVEL')))
+    else:
+        logging.basicConfig(level=logging.WARN)
+    unittest.main()
diff --git a/vl.c b/vl.c
index d77dd86..dc4d825 100644
--- a/vl.c
+++ b/vl.c
@@ -123,6 +123,7 @@ int main(int argc, char **argv)
 #include "sysemu/replay.h"
 #include "qapi/qmp/qerror.h"
 #include "sysemu/iothread.h"
+#include "qapi/clone-visitor.h"
 
 #define MAX_VIRTIO_CONSOLES 1
 #define MAX_SCLP_CONSOLES 1
@@ -1556,6 +1557,11 @@ MachineInfoList *qmp_query_machines(Error **errp)
         info->name = g_strdup(mc->name);
         info->cpu_max = !mc->max_cpus ? 1 : mc->max_cpus;
         info->hotpluggable_cpus = !!mc->query_hotpluggable_cpus;
+        if (mc->always_available_buses) {
+            info->always_available_buses =
+                QAPI_CLONE(MachineBusInfoList, mc->always_available_buses);
+            info->has_always_available_buses = true;
+        }
 
         entry = g_malloc0(sizeof(*entry));
         entry->value = info;
-- 
2.7.4


Reply via email to