qemu/tests/qtest/device-introspect-test.c
Olaf Hering 9a709f06c8 piix: fix xenfv regression, add compat machine xenfv-4.2
With QEMU 4.0 an incompatible change was added to pc_piix, which makes it
practical impossible to migrate domUs started with qemu2 or qemu3 to
newer qemu versions. Commit 7fccf2a068
added and enabled a new member "smbus_no_migration_support". In commit
4ab2f2a8aa the vmstate_acpi got new
elements, which are conditionally filled. As a result, an incoming
migration expected smbus related data unless smbus migration was
disabled for a given MachineClass. Since first commit forgot to handle
'xenfv', domUs started with QEMU 4.x are incompatible with their QEMU
siblings.

Using other existing machine types, such as 'pc-i440fx-3.1', is not
possible because 'xenfv' creates the 'xen-platform' PCI device at
00:02.0, while all other variants to run a domU would create it at
00:04.0.

To cover both the existing and the broken case of 'xenfv' in a single
qemu binary, a new compatibility variant of 'xenfv-4.2' must be added
which targets domUs started with qemu 4.2. The existing 'xenfv' restores
compatibility of QEMU 5.x with qemu 3.1.

Host admins who started domUs with QEMU 4.x (preferrable QEMU 4.2)
have to use a wrapper script which appends '-machine xenfv-4.2' to
the device-model command line.  This is only required if there is no
maintenance window which allows to temporary shutdown the domU and
restart it with a fixed device-model.

The wrapper script is as simple as this:
  #!/bin/sh
  exec /usr/bin/qemu-system-i386 "$@" -machine xenfv-4.2

With xl this script will be enabled with device_model_override=, see
xl.cfg(5). To live migrate a domU, adjust the existing domU.cfg and pass
it to xl migrate or xl save/restore:
  xl migrate -C new-domU.cfg domU remote-host
  xl save domU CheckpointFile new-domU.cfg
  xl restore new-domU.cfg CheckpointFile

With libvirt this script will be enabled with the <emulator> element in
domU.xml. Use 'virsh edit' prior 'virsh migrate' to replace the existing
<emulator> element to point it to the wrapper script.

Signed-off-by: Olaf Hering <olaf@aepfle.de>
Message-Id: <20200327151841.13877-1-olaf@aepfle.de>
[Adjust tests for blacklisted machine types, simplifying the one in
 qom-test. - Paolo]
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
2020-04-11 08:49:11 -04:00

324 lines
8.5 KiB
C

/*
* Device introspection test cases
*
* Copyright (c) 2015 Red Hat Inc.
*
* Authors:
* Markus Armbruster <armbru@redhat.com>,
*
* This work is licensed under the terms of the GNU GPL, version 2 or later.
* See the COPYING file in the top-level directory.
*/
/*
* Covers QMP device-list-properties and HMP device_add help. We
* currently don't check that their output makes sense, only that QEMU
* survives. Useful since we've had an astounding number of crash
* bugs around here.
*/
#include "qemu/osdep.h"
#include "qemu-common.h"
#include "qapi/qmp/qstring.h"
#include "qapi/qmp/qdict.h"
#include "qapi/qmp/qlist.h"
#include "libqtest.h"
const char common_args[] = "-nodefaults -machine none";
static QList *qom_list_types(QTestState * qts, const char *implements,
bool abstract)
{
QDict *resp;
QList *ret;
QDict *args = qdict_new();
qdict_put_bool(args, "abstract", abstract);
if (implements) {
qdict_put_str(args, "implements", implements);
}
resp = qtest_qmp(qts, "{'execute': 'qom-list-types', 'arguments': %p }",
args);
g_assert(qdict_haskey(resp, "return"));
ret = qdict_get_qlist(resp, "return");
qobject_ref(ret);
qobject_unref(resp);
return ret;
}
/* Build a name -> ObjectTypeInfo index from a ObjectTypeInfo list */
static QDict *qom_type_index(QList *types)
{
QDict *index = qdict_new();
QListEntry *e;
QLIST_FOREACH_ENTRY(types, e) {
QDict *d = qobject_to(QDict, qlist_entry_obj(e));
const char *name = qdict_get_str(d, "name");
qobject_ref(d);
qdict_put(index, name, d);
}
return index;
}
/* Check if @parent is present in the parent chain of @type */
static bool qom_has_parent(QDict *index, const char *type, const char *parent)
{
while (type) {
QDict *d = qdict_get_qdict(index, type);
const char *p = d && qdict_haskey(d, "parent") ?
qdict_get_str(d, "parent") :
NULL;
if (!strcmp(type, parent)) {
return true;
}
type = p;
}
return false;
}
/* Find an entry on a list returned by qom-list-types */
static QDict *type_list_find(QList *types, const char *name)
{
QListEntry *e;
QLIST_FOREACH_ENTRY(types, e) {
QDict *d = qobject_to(QDict, qlist_entry_obj(e));
const char *ename = qdict_get_str(d, "name");
if (!strcmp(ename, name)) {
return d;
}
}
return NULL;
}
static QList *device_type_list(QTestState *qts, bool abstract)
{
return qom_list_types(qts, "device", abstract);
}
static void test_one_device(QTestState *qts, const char *type)
{
QDict *resp;
char *help;
char *qom_tree_start, *qom_tree_end;
char *qtree_start, *qtree_end;
g_test_message("Testing device '%s'", type);
qom_tree_start = qtest_hmp(qts, "info qom-tree");
qtree_start = qtest_hmp(qts, "info qtree");
resp = qtest_qmp(qts, "{'execute': 'device-list-properties',"
" 'arguments': {'typename': %s}}",
type);
qobject_unref(resp);
help = qtest_hmp(qts, "device_add \"%s,help\"", type);
g_free(help);
/*
* Some devices leave dangling pointers in QOM behind.
* "info qom-tree" or "info qtree" have a good chance at crashing then.
* Also make sure that the tree did not change.
*/
qom_tree_end = qtest_hmp(qts, "info qom-tree");
g_assert_cmpstr(qom_tree_start, ==, qom_tree_end);
g_free(qom_tree_start);
g_free(qom_tree_end);
qtree_end = qtest_hmp(qts, "info qtree");
g_assert_cmpstr(qtree_start, ==, qtree_end);
g_free(qtree_start);
g_free(qtree_end);
}
static void test_device_intro_list(void)
{
QList *types;
char *help;
QTestState *qts;
qts = qtest_init(common_args);
types = device_type_list(qts, true);
qobject_unref(types);
help = qtest_hmp(qts, "device_add help");
g_free(help);
qtest_quit(qts);
}
/*
* Ensure all entries returned by qom-list-types implements=<parent>
* have <parent> as a parent.
*/
static void test_qom_list_parents(QTestState *qts, const char *parent)
{
QList *types;
QListEntry *e;
QDict *index;
types = qom_list_types(qts, parent, true);
index = qom_type_index(types);
QLIST_FOREACH_ENTRY(types, e) {
QDict *d = qobject_to(QDict, qlist_entry_obj(e));
const char *name = qdict_get_str(d, "name");
g_assert(qom_has_parent(index, name, parent));
}
qobject_unref(types);
qobject_unref(index);
}
static void test_qom_list_fields(void)
{
QList *all_types;
QList *non_abstract;
QListEntry *e;
QTestState *qts;
qts = qtest_init(common_args);
all_types = qom_list_types(qts, NULL, true);
non_abstract = qom_list_types(qts, NULL, false);
QLIST_FOREACH_ENTRY(all_types, e) {
QDict *d = qobject_to(QDict, qlist_entry_obj(e));
const char *name = qdict_get_str(d, "name");
bool abstract = qdict_haskey(d, "abstract") ?
qdict_get_bool(d, "abstract") :
false;
bool expected_abstract = !type_list_find(non_abstract, name);
g_assert(abstract == expected_abstract);
}
test_qom_list_parents(qts, "object");
test_qom_list_parents(qts, "device");
test_qom_list_parents(qts, "sys-bus-device");
qobject_unref(all_types);
qobject_unref(non_abstract);
qtest_quit(qts);
}
static void test_device_intro_none(void)
{
QTestState *qts = qtest_init(common_args);
test_one_device(qts, "nonexistent");
qtest_quit(qts);
}
static void test_device_intro_abstract(void)
{
QTestState *qts = qtest_init(common_args);
test_one_device(qts, "device");
qtest_quit(qts);
}
static void test_device_intro_concrete(const void *args)
{
QList *types;
QListEntry *entry;
const char *type;
QTestState *qts;
qts = qtest_init(args);
types = device_type_list(qts, false);
QLIST_FOREACH_ENTRY(types, entry) {
type = qdict_get_try_str(qobject_to(QDict, qlist_entry_obj(entry)),
"name");
g_assert(type);
test_one_device(qts, type);
}
qobject_unref(types);
qtest_quit(qts);
g_free((void *)args);
}
static void test_abstract_interfaces(void)
{
QList *all_types;
QListEntry *e;
QDict *index;
QTestState *qts;
qts = qtest_init(common_args);
all_types = qom_list_types(qts, "interface", true);
index = qom_type_index(all_types);
QLIST_FOREACH_ENTRY(all_types, e) {
QDict *d = qobject_to(QDict, qlist_entry_obj(e));
const char *name = qdict_get_str(d, "name");
/*
* qom-list-types implements=interface returns all types
* that implement _any_ interface (not just interface
* types), so skip the ones that don't have "interface"
* on the parent type chain.
*/
if (!qom_has_parent(index, name, "interface")) {
/* Not an interface type */
continue;
}
g_assert(qdict_haskey(d, "abstract") && qdict_get_bool(d, "abstract"));
}
qobject_unref(all_types);
qobject_unref(index);
qtest_quit(qts);
}
static void add_machine_test_case(const char *mname)
{
char *path, *args;
/* Ignore blacklisted machines */
if (!memcmp("xenfv", mname, 5) || g_str_equal("xenpv", mname)) {
return;
}
path = g_strdup_printf("device/introspect/concrete/defaults/%s", mname);
args = g_strdup_printf("-M %s", mname);
qtest_add_data_func(path, args, test_device_intro_concrete);
g_free(path);
path = g_strdup_printf("device/introspect/concrete/nodefaults/%s", mname);
args = g_strdup_printf("-nodefaults -M %s", mname);
qtest_add_data_func(path, args, test_device_intro_concrete);
g_free(path);
}
int main(int argc, char **argv)
{
g_test_init(&argc, &argv, NULL);
qtest_add_func("device/introspect/list", test_device_intro_list);
qtest_add_func("device/introspect/list-fields", test_qom_list_fields);
qtest_add_func("device/introspect/none", test_device_intro_none);
qtest_add_func("device/introspect/abstract", test_device_intro_abstract);
qtest_add_func("device/introspect/abstract-interfaces", test_abstract_interfaces);
if (g_test_quick()) {
qtest_add_data_func("device/introspect/concrete/defaults/none",
g_strdup(common_args), test_device_intro_concrete);
} else {
qtest_cb_for_every_machine(add_machine_test_case, true);
}
return g_test_run();
}