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@xxxxxxxxxx Cc: Laine Stump <laine@xxxxxxxxxx> Signed-off-by: Eduardo Habkost <ehabkost@xxxxxxxxxx> --- 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 -- libvir-list mailing list libvir-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/libvir-list