From patchwork Fri Nov 25 22:05:43 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Eduardo Habkost X-Patchwork-Id: 9448161 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork.web.codeaurora.org (Postfix) with ESMTP id F0F3860235 for ; Fri, 25 Nov 2016 22:18:54 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id D4FF526220 for ; Fri, 25 Nov 2016 22:18:54 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id C9A0D2808F; Fri, 25 Nov 2016 22:18:54 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-6.9 required=2.0 tests=BAYES_00,RCVD_IN_DNSWL_HI autolearn=ham version=3.3.1 Received: from lists.gnu.org (lists.gnu.org [208.118.235.17]) (using TLSv1 with cipher AES256-SHA (256/256 bits)) (No client certificate requested) by mail.wl.linuxfoundation.org (Postfix) with ESMTPS id B04A626220 for ; Fri, 25 Nov 2016 22:18:53 +0000 (UTC) Received: from localhost ([::1]:48144 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1cAOpY-0007UV-6m for patchwork-qemu-devel@patchwork.kernel.org; Fri, 25 Nov 2016 17:18:52 -0500 Received: from eggs.gnu.org ([2001:4830:134:3::10]:50574) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1cAOdV-0006SD-90 for qemu-devel@nongnu.org; Fri, 25 Nov 2016 17:06:27 -0500 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1cAOdT-0008PT-DF for qemu-devel@nongnu.org; Fri, 25 Nov 2016 17:06:25 -0500 Received: from mx1.redhat.com ([209.132.183.28]:38716) by eggs.gnu.org with esmtps (TLS1.0:DHE_RSA_AES_256_CBC_SHA1:32) (Exim 4.71) (envelope-from ) id 1cAOdT-0008O7-4v for qemu-devel@nongnu.org; Fri, 25 Nov 2016 17:06:23 -0500 Received: from int-mx13.intmail.prod.int.phx2.redhat.com (int-mx13.intmail.prod.int.phx2.redhat.com [10.5.11.26]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mx1.redhat.com (Postfix) with ESMTPS id 616A13B731 for ; Fri, 25 Nov 2016 22:06:22 +0000 (UTC) Received: from localhost (ovpn-116-207.phx2.redhat.com [10.3.116.207]) by int-mx13.intmail.prod.int.phx2.redhat.com (8.14.4/8.14.4) with ESMTP id uAPM6LTJ026358; Fri, 25 Nov 2016 17:06:21 -0500 From: Eduardo Habkost To: qemu-devel@nongnu.org, Markus Armbruster , Marcel Apfelbaum , "Michael S. Tsirkin" Date: Fri, 25 Nov 2016 20:05:43 -0200 Message-Id: <1480111556-32586-8-git-send-email-ehabkost@redhat.com> In-Reply-To: <1480111556-32586-1-git-send-email-ehabkost@redhat.com> References: <1480111556-32586-1-git-send-email-ehabkost@redhat.com> X-Scanned-By: MIMEDefang 2.68 on 10.5.11.26 X-Greylist: Sender IP whitelisted, not delayed by milter-greylist-4.5.16 (mx1.redhat.com [10.5.110.30]); Fri, 25 Nov 2016 22:06:22 +0000 (UTC) X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.2.x-3.x [generic] [fuzzy] X-Received-From: 209.132.183.28 Subject: [Qemu-devel] [RFC v2 07/20] qmp: Add 'always-available-buses' field to 'query-machines' X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.21 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: libvir-list@redhat.com, Laine Stump Errors-To: qemu-devel-bounces+patchwork-qemu-devel=patchwork.kernel.org@nongnu.org Sender: "Qemu-devel" X-Virus-Scanned: ClamAV using ClamSMTP 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-list@redhat.com Cc: Laine Stump Signed-off-by: Eduardo Habkost --- 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___ 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;