Add dbus-vmstate

Hi,
 
 With external processes or helpers participating to the VM support, it
 becomes necessary to handle their migration. Various options exist to
 transfer their state:
 1) as the VM memory, RAM or devices (we could say that's how
    vhost-user devices can be handled today, they are expected to
    restore from ring state)
 2) other "vmstate" (as with TPM emulator state blobs)
 3) left to be handled by management layer
 
 1) is not practical, since an external processes may legitimatelly
 need arbitrary state date to back a device or a service, or may not
 even have an associated device.
 
 2) needs ad-hoc code for each helper, but is simple and working
 
 3) is complicated for management layer, QEMU has the migration timing
 
 The proposed "dbus-vmstate" object will connect to a given D-Bus
 address, and save/load from org.qemu.VMState1 owners on migration.
 
 Thus helpers can easily have their state migrated with QEMU, without
 implementing ad-hoc support (such as done for TPM emulation)
 
 D-Bus is ubiquitous on Linux (it is systemd IPC), and can be made to
 work on various other OSes. There are several implementations and good
 bindings for various languages.  (the tests/dbus-vmstate-test.c is a
 good example of how simple the implementation of services can be, even
 in C)
 
 dbus-vmstate is put into use by the libvirt series "[PATCH 00/23] Use
 a slirp helper process".
 
 v2:
  - fix build with broken mingw-glib
 -----BEGIN PGP SIGNATURE-----
 
 iQJQBAABCAA6FiEEh6m9kz+HxgbSdvYt2ujhCXWWnOUFAl4TR5ccHG1hcmNhbmRy
 ZS5sdXJlYXVAcmVkaGF0LmNvbQAKCRDa6OEJdZac5R6EEACFTd4hDG8i/GnxCFut
 MGcTusJr+2IklIT/K0qpLf0axNUoIqycwv8m0T9QhoG8h+9lMykOd1YJpNetT5qK
 gifOF2gcPK/9WIdFbX7dLSUAWpzO6fG/RzKK65Nc1uJSnXlb8JV0BU/6FrfCE+3U
 Bg5PvVtxxtwejQfQPOI7bPxOqxr/SmjUGcbFgacMAMG0Lm/VG/92kdoC6Z4Xf/bd
 FcAeiO2CiPoGXG5zD4WF1emwxnSu65PgcFpSpqvvFlmDbYlTwoMt4VWxTfkAzbAM
 IES7j2IbhUEe3p0hvMTqmmsmds1QNCBgnQI/LtQiXPTnbfpBcZ0wT6QsSZXWvHz8
 ClA9OAimxyELblTGjD9vsi3G5m2DQS+NdfPOX7hfHouVQzDJJaS8jxDItpPgXwSO
 fZ9mUO8ps3N2YTakuKNBP/IzDOuyExrBg80GF+HbEc59Uhj8Yq/awyz1XsqjQzVP
 54+TUjwC8HZxVWgMeqiJ1njPTfRJo6uAnguLbfAXj8P9vaXLtsy/3JGsmKiziXXW
 XzvQDzhfOMjm7Uo7vN7Hp3X/UYJxnaQ3dViqZnv/gqG6yv+igVlqyrTx2IBhN2NW
 DZt3c7VqVUBYFShLgfy0zDjzM/s7mFkQKCFHUsBqIwODugYEc3TTdAa60QYjX5i9
 negngax45KM6nF3tq74fJpwWVw==
 =M4kD
 -----END PGP SIGNATURE-----

Merge remote-tracking branch 'remotes/elmarco/tags/dbus-vmstate7-pull-request' into staging

Add dbus-vmstate

Hi,

With external processes or helpers participating to the VM support, it
becomes necessary to handle their migration. Various options exist to
transfer their state:
1) as the VM memory, RAM or devices (we could say that's how
   vhost-user devices can be handled today, they are expected to
   restore from ring state)
2) other "vmstate" (as with TPM emulator state blobs)
3) left to be handled by management layer

1) is not practical, since an external processes may legitimatelly
need arbitrary state date to back a device or a service, or may not
even have an associated device.

2) needs ad-hoc code for each helper, but is simple and working

3) is complicated for management layer, QEMU has the migration timing

The proposed "dbus-vmstate" object will connect to a given D-Bus
address, and save/load from org.qemu.VMState1 owners on migration.

Thus helpers can easily have their state migrated with QEMU, without
implementing ad-hoc support (such as done for TPM emulation)

D-Bus is ubiquitous on Linux (it is systemd IPC), and can be made to
work on various other OSes. There are several implementations and good
bindings for various languages.  (the tests/dbus-vmstate-test.c is a
good example of how simple the implementation of services can be, even
in C)

dbus-vmstate is put into use by the libvirt series "[PATCH 00/23] Use
a slirp helper process".

v2:
 - fix build with broken mingw-glib

# gpg: Signature made Mon 06 Jan 2020 14:43:35 GMT
# gpg:                using RSA key 87A9BD933F87C606D276F62DDAE8E10975969CE5
# gpg:                issuer "marcandre.lureau@redhat.com"
# gpg: Good signature from "Marc-André Lureau <marcandre.lureau@redhat.com>" [full]
# gpg:                 aka "Marc-André Lureau <marcandre.lureau@gmail.com>" [full]
# Primary key fingerprint: 87A9 BD93 3F87 C606 D276  F62D DAE8 E109 7596 9CE5

* remotes/elmarco/tags/dbus-vmstate7-pull-request:
  tests: add dbus-vmstate-test
  tests: add migration-helpers unit
  dockerfiles: add dbus-daemon to some of latest distributions
  configure: add GDBUS_CODEGEN
  Add dbus-vmstate object
  util: add dbus helper unit
  docs: start a document to describe D-Bus usage
  vmstate: replace DeviceState with VMStateIf
  vmstate: add qom interface to get id

Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
Peter Maydell 2020-01-06 18:22:42 +00:00
commit c4d1069c25
43 changed files with 1660 additions and 205 deletions

View File

@ -2196,6 +2196,8 @@ Migration
M: Juan Quintela <quintela@redhat.com>
M: Dr. David Alan Gilbert <dgilbert@redhat.com>
S: Maintained
F: hw/core/vmstate-if.c
F: include/hw/vmstate-if.h
F: include/migration/
F: migration/
F: scripts/vmstate-static-checker.py
@ -2204,6 +2206,16 @@ F: tests/migration-test.c
F: docs/devel/migration.rst
F: qapi/migration.json
D-Bus
M: Marc-André Lureau <marcandre.lureau@redhat.com>
S: Maintained
F: backends/dbus-vmstate.c
F: tests/dbus-vmstate*
F: util/dbus.c
F: include/qemu/dbus.h
F: docs/interop/dbus.rst
F: docs/interop/dbus-vmstate.rst
Seccomp
M: Eduardo Otubo <otubo@redhat.com>
S: Supported

View File

@ -128,6 +128,7 @@ vhost-user-gpu-obj-y = contrib/vhost-user-gpu/
trace-events-subdirs =
trace-events-subdirs += accel/kvm
trace-events-subdirs += accel/tcg
trace-events-subdirs += backends
trace-events-subdirs += crypto
trace-events-subdirs += monitor
ifeq ($(CONFIG_USER_ONLY),y)

View File

@ -17,3 +17,7 @@ endif
common-obj-$(call land,$(CONFIG_VHOST_USER),$(CONFIG_VIRTIO)) += vhost-user.o
common-obj-$(CONFIG_LINUX) += hostmem-memfd.o
common-obj-$(CONFIG_GIO) += dbus-vmstate.o
dbus-vmstate.o-cflags = $(GIO_CFLAGS)
dbus-vmstate.o-libs = $(GIO_LIBS)

510
backends/dbus-vmstate.c Normal file
View File

@ -0,0 +1,510 @@
/*
* QEMU dbus-vmstate
*
* Copyright (C) 2019 Red Hat Inc
*
* Authors:
* Marc-André Lureau <marcandre.lureau@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.
*/
#include "qemu/osdep.h"
#include "qemu/units.h"
#include "qemu/dbus.h"
#include "qemu/error-report.h"
#include "qapi/error.h"
#include "qom/object_interfaces.h"
#include "qapi/qmp/qerror.h"
#include "migration/vmstate.h"
#include "trace.h"
typedef struct DBusVMState DBusVMState;
typedef struct DBusVMStateClass DBusVMStateClass;
#define TYPE_DBUS_VMSTATE "dbus-vmstate"
#define DBUS_VMSTATE(obj) \
OBJECT_CHECK(DBusVMState, (obj), TYPE_DBUS_VMSTATE)
#define DBUS_VMSTATE_GET_CLASS(obj) \
OBJECT_GET_CLASS(DBusVMStateClass, (obj), TYPE_DBUS_VMSTATE)
#define DBUS_VMSTATE_CLASS(klass) \
OBJECT_CLASS_CHECK(DBusVMStateClass, (klass), TYPE_DBUS_VMSTATE)
struct DBusVMStateClass {
ObjectClass parent_class;
};
struct DBusVMState {
Object parent;
GDBusConnection *bus;
char *dbus_addr;
char *id_list;
uint32_t data_size;
uint8_t *data;
};
static const GDBusPropertyInfo vmstate_property_info[] = {
{ -1, (char *) "Id", (char *) "s",
G_DBUS_PROPERTY_INFO_FLAGS_READABLE, NULL },
};
static const GDBusPropertyInfo * const vmstate_property_info_pointers[] = {
&vmstate_property_info[0],
NULL
};
static const GDBusInterfaceInfo vmstate1_interface_info = {
-1,
(char *) "org.qemu.VMState1",
(GDBusMethodInfo **) NULL,
(GDBusSignalInfo **) NULL,
(GDBusPropertyInfo **) &vmstate_property_info_pointers,
NULL,
};
#define DBUS_VMSTATE_SIZE_LIMIT (1 * MiB)
static GHashTable *
get_id_list_set(DBusVMState *self)
{
g_auto(GStrv) ids = NULL;
g_autoptr(GHashTable) set = NULL;
int i;
if (!self->id_list) {
return NULL;
}
ids = g_strsplit(self->id_list, ",", -1);
set = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
for (i = 0; ids[i]; i++) {
g_hash_table_add(set, ids[i]);
ids[i] = NULL;
}
return g_steal_pointer(&set);
}
static GHashTable *
dbus_get_proxies(DBusVMState *self, GError **err)
{
g_autoptr(GHashTable) proxies = NULL;
g_autoptr(GHashTable) ids = NULL;
g_auto(GStrv) names = NULL;
Error *error = NULL;
size_t i;
ids = get_id_list_set(self);
proxies = g_hash_table_new_full(g_str_hash, g_str_equal,
g_free, g_object_unref);
names = qemu_dbus_get_queued_owners(self->bus, "org.qemu.VMState1", &error);
if (!names) {
g_set_error(err, G_IO_ERROR, G_IO_ERROR_FAILED, "%s",
error_get_pretty(error));
error_free(error);
return NULL;
}
for (i = 0; names[i]; i++) {
g_autoptr(GDBusProxy) proxy = NULL;
g_autoptr(GVariant) result = NULL;
g_autofree char *id = NULL;
size_t size;
proxy = g_dbus_proxy_new_sync(self->bus, G_DBUS_PROXY_FLAGS_NONE,
(GDBusInterfaceInfo *) &vmstate1_interface_info,
names[i],
"/org/qemu/VMState1",
"org.qemu.VMState1",
NULL, err);
if (!proxy) {
return NULL;
}
result = g_dbus_proxy_get_cached_property(proxy, "Id");
if (!result) {
g_set_error_literal(err, G_IO_ERROR, G_IO_ERROR_FAILED,
"VMState Id property is missing.");
return NULL;
}
id = g_variant_dup_string(result, &size);
if (ids && !g_hash_table_remove(ids, id)) {
g_clear_pointer(&id, g_free);
g_clear_object(&proxy);
continue;
}
if (size == 0 || size >= 256) {
g_set_error(err, G_IO_ERROR, G_IO_ERROR_FAILED,
"VMState Id '%s' is invalid.", id);
return NULL;
}
if (!g_hash_table_insert(proxies, id, proxy)) {
g_set_error(err, G_IO_ERROR, G_IO_ERROR_FAILED,
"Duplicated VMState Id '%s'", id);
return NULL;
}
id = NULL;
proxy = NULL;
g_clear_pointer(&result, g_variant_unref);
}
if (ids) {
g_autofree char **left = NULL;
left = (char **)g_hash_table_get_keys_as_array(ids, NULL);
if (*left) {
g_autofree char *leftids = g_strjoinv(",", left);
g_set_error(err, G_IO_ERROR, G_IO_ERROR_FAILED,
"Required VMState Id are missing: %s", leftids);
return NULL;
}
}
return g_steal_pointer(&proxies);
}
static int
dbus_load_state_proxy(GDBusProxy *proxy, const uint8_t *data, size_t size)
{
g_autoptr(GError) err = NULL;
g_autoptr(GVariant) result = NULL;
g_autoptr(GVariant) value = NULL;
value = g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE,
data, size, sizeof(char));
result = g_dbus_proxy_call_sync(proxy, "Load",
g_variant_new("(@ay)",
g_steal_pointer(&value)),
G_DBUS_CALL_FLAGS_NO_AUTO_START,
-1, NULL, &err);
if (!result) {
error_report("%s: Failed to Load: %s", __func__, err->message);
return -1;
}
return 0;
}
static int dbus_vmstate_post_load(void *opaque, int version_id)
{
DBusVMState *self = DBUS_VMSTATE(opaque);
g_autoptr(GInputStream) m = NULL;
g_autoptr(GDataInputStream) s = NULL;
g_autoptr(GError) err = NULL;
g_autoptr(GHashTable) proxies = NULL;
uint32_t nelem;
trace_dbus_vmstate_post_load(version_id);
proxies = dbus_get_proxies(self, &err);
if (!proxies) {
error_report("%s: Failed to get proxies: %s", __func__, err->message);
return -1;
}
m = g_memory_input_stream_new_from_data(self->data, self->data_size, NULL);
s = g_data_input_stream_new(m);
g_data_input_stream_set_byte_order(s, G_DATA_STREAM_BYTE_ORDER_BIG_ENDIAN);
nelem = g_data_input_stream_read_uint32(s, NULL, &err);
if (err) {
goto error;
}
while (nelem > 0) {
GDBusProxy *proxy = NULL;
uint32_t len;
gsize bytes_read, avail;
char id[256];
len = g_data_input_stream_read_uint32(s, NULL, &err);
if (err) {
goto error;
}
if (len >= 256) {
error_report("%s: Invalid DBus vmstate proxy name %u",
__func__, len);
return -1;
}
if (!g_input_stream_read_all(G_INPUT_STREAM(s), id, len,
&bytes_read, NULL, &err)) {
goto error;
}
g_return_val_if_fail(bytes_read == len, -1);
id[len] = 0;
trace_dbus_vmstate_loading(id);
proxy = g_hash_table_lookup(proxies, id);
if (!proxy) {
error_report("%s: Failed to find proxy Id '%s'", __func__, id);
return -1;
}
len = g_data_input_stream_read_uint32(s, NULL, &err);
avail = g_buffered_input_stream_get_available(
G_BUFFERED_INPUT_STREAM(s));
if (len > DBUS_VMSTATE_SIZE_LIMIT || len > avail) {
error_report("%s: Invalid vmstate size: %u", __func__, len);
return -1;
}
if (dbus_load_state_proxy(proxy,
g_buffered_input_stream_peek_buffer(G_BUFFERED_INPUT_STREAM(s),
NULL),
len) < 0) {
error_report("%s: Failed to restore Id '%s'", __func__, id);
return -1;
}
if (!g_seekable_seek(G_SEEKABLE(s), len, G_SEEK_CUR, NULL, &err)) {
goto error;
}
nelem -= 1;
}
return 0;
error:
error_report("%s: Failed to read from stream: %s", __func__, err->message);
return -1;
}
static void
dbus_save_state_proxy(gpointer key,
gpointer value,
gpointer user_data)
{
GDataOutputStream *s = user_data;
const char *id = key;
GDBusProxy *proxy = value;
g_autoptr(GVariant) result = NULL;
g_autoptr(GVariant) child = NULL;
g_autoptr(GError) err = NULL;
const uint8_t *data;
gsize size;
trace_dbus_vmstate_saving(id);
result = g_dbus_proxy_call_sync(proxy, "Save",
NULL, G_DBUS_CALL_FLAGS_NO_AUTO_START,
-1, NULL, &err);
if (!result) {
error_report("%s: Failed to Save: %s", __func__, err->message);
return;
}
child = g_variant_get_child_value(result, 0);
data = g_variant_get_fixed_array(child, &size, sizeof(char));
if (!data) {
error_report("%s: Failed to Save: not a byte array", __func__);
return;
}
if (size > DBUS_VMSTATE_SIZE_LIMIT) {
error_report("%s: Too large vmstate data to save: %zu",
__func__, (size_t)size);
return;
}
if (!g_data_output_stream_put_uint32(s, strlen(id), NULL, &err) ||
!g_data_output_stream_put_string(s, id, NULL, &err) ||
!g_data_output_stream_put_uint32(s, size, NULL, &err) ||
!g_output_stream_write_all(G_OUTPUT_STREAM(s),
data, size, NULL, NULL, &err)) {
error_report("%s: Failed to write to stream: %s",
__func__, err->message);
}
}
static int dbus_vmstate_pre_save(void *opaque)
{
DBusVMState *self = DBUS_VMSTATE(opaque);
g_autoptr(GOutputStream) m = NULL;
g_autoptr(GDataOutputStream) s = NULL;
g_autoptr(GHashTable) proxies = NULL;
g_autoptr(GError) err = NULL;
trace_dbus_vmstate_pre_save();
proxies = dbus_get_proxies(self, &err);
if (!proxies) {
error_report("%s: Failed to get proxies: %s", __func__, err->message);
return -1;
}
m = g_memory_output_stream_new_resizable();
s = g_data_output_stream_new(m);
g_data_output_stream_set_byte_order(s, G_DATA_STREAM_BYTE_ORDER_BIG_ENDIAN);
if (!g_data_output_stream_put_uint32(s, g_hash_table_size(proxies),
NULL, &err)) {
error_report("%s: Failed to write to stream: %s",
__func__, err->message);
return -1;
}
g_hash_table_foreach(proxies, dbus_save_state_proxy, s);
if (g_memory_output_stream_get_size(G_MEMORY_OUTPUT_STREAM(m))
> UINT32_MAX) {
error_report("%s: DBus vmstate buffer is too large", __func__);
return -1;
}
if (!g_output_stream_close(G_OUTPUT_STREAM(m), NULL, &err)) {
error_report("%s: Failed to close stream: %s", __func__, err->message);
return -1;
}
g_free(self->data);
self->data_size =
g_memory_output_stream_get_size(G_MEMORY_OUTPUT_STREAM(m));
self->data =
g_memory_output_stream_steal_data(G_MEMORY_OUTPUT_STREAM(m));
return 0;
}
static const VMStateDescription dbus_vmstate = {
.name = TYPE_DBUS_VMSTATE,
.version_id = 0,
.pre_save = dbus_vmstate_pre_save,
.post_load = dbus_vmstate_post_load,
.fields = (VMStateField[]) {
VMSTATE_UINT32(data_size, DBusVMState),
VMSTATE_VBUFFER_ALLOC_UINT32(data, DBusVMState, 0, 0, data_size),
VMSTATE_END_OF_LIST()
}
};
static void
dbus_vmstate_complete(UserCreatable *uc, Error **errp)
{
DBusVMState *self = DBUS_VMSTATE(uc);
g_autoptr(GError) err = NULL;
if (!object_resolve_path_type("", TYPE_DBUS_VMSTATE, NULL)) {
error_setg(errp, "There is already an instance of %s",
TYPE_DBUS_VMSTATE);
return;
}
if (!self->dbus_addr) {
error_setg(errp, QERR_MISSING_PARAMETER, "addr");
return;
}
self->bus = g_dbus_connection_new_for_address_sync(self->dbus_addr,
G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT |
G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION,
NULL, NULL, &err);
if (err) {
error_setg(errp, "failed to connect to DBus: '%s'", err->message);
return;
}
if (vmstate_register(VMSTATE_IF(self), -1, &dbus_vmstate, self) < 0) {
error_setg(errp, "Failed to register vmstate");
}
}
static void
dbus_vmstate_finalize(Object *o)
{
DBusVMState *self = DBUS_VMSTATE(o);
vmstate_unregister(VMSTATE_IF(self), &dbus_vmstate, self);
g_clear_object(&self->bus);
g_free(self->dbus_addr);
g_free(self->id_list);
g_free(self->data);
}
static char *
get_dbus_addr(Object *o, Error **errp)
{
DBusVMState *self = DBUS_VMSTATE(o);
return g_strdup(self->dbus_addr);
}
static void
set_dbus_addr(Object *o, const char *str, Error **errp)
{
DBusVMState *self = DBUS_VMSTATE(o);
g_free(self->dbus_addr);
self->dbus_addr = g_strdup(str);
}
static char *
get_id_list(Object *o, Error **errp)
{
DBusVMState *self = DBUS_VMSTATE(o);
return g_strdup(self->id_list);
}
static void
set_id_list(Object *o, const char *str, Error **errp)
{
DBusVMState *self = DBUS_VMSTATE(o);
g_free(self->id_list);
self->id_list = g_strdup(str);
}
static char *
dbus_vmstate_get_id(VMStateIf *vmif)
{
return g_strdup(TYPE_DBUS_VMSTATE);
}
static void
dbus_vmstate_class_init(ObjectClass *oc, void *data)
{
UserCreatableClass *ucc = USER_CREATABLE_CLASS(oc);
VMStateIfClass *vc = VMSTATE_IF_CLASS(oc);
ucc->complete = dbus_vmstate_complete;
vc->get_id = dbus_vmstate_get_id;
object_class_property_add_str(oc, "addr",
get_dbus_addr, set_dbus_addr,
&error_abort);
object_class_property_add_str(oc, "id-list",
get_id_list, set_id_list,
&error_abort);
}
static const TypeInfo dbus_vmstate_info = {
.name = TYPE_DBUS_VMSTATE,
.parent = TYPE_OBJECT,
.instance_size = sizeof(DBusVMState),
.instance_finalize = dbus_vmstate_finalize,
.class_size = sizeof(DBusVMStateClass),
.class_init = dbus_vmstate_class_init,
.interfaces = (InterfaceInfo[]) {
{ TYPE_USER_CREATABLE },
{ TYPE_VMSTATE_IF },
{ }
}
};
static void
register_types(void)
{
type_register_static(&dbus_vmstate_info);
}
type_init(register_types);

7
backends/trace-events Normal file
View File

@ -0,0 +1,7 @@
# See docs/devel/tracing.txt for syntax documentation.
# dbus-vmstate.c
dbus_vmstate_pre_save(void)
dbus_vmstate_post_load(int version_id) "version_id: %d"
dbus_vmstate_loading(const char *id) "id: %s"
dbus_vmstate_saving(const char *id) "id: %s"

7
configure vendored
View File

@ -3701,10 +3701,16 @@ if $pkg_config --atleast-version=$glib_req_ver gio-2.0; then
gio=yes
gio_cflags=$($pkg_config --cflags gio-2.0)
gio_libs=$($pkg_config --libs gio-2.0)
gdbus_codegen=$($pkg_config --variable=gdbus_codegen gio-2.0)
else
gio=no
fi
if $pkg_config --atleast-version=$glib_req_ver gio-unix-2.0; then
gio_cflags="$gio_cflags $($pkg_config --cflags gio-unix-2.0)"
gio_libs="$gio_libs $($pkg_config --libs gio-unix-2.0)"
fi
# Sanity check that the current size_t matches the
# size that glib thinks it should be. This catches
# problems on multi-arch where people try to build
@ -6904,6 +6910,7 @@ if test "$gio" = "yes" ; then
echo "CONFIG_GIO=y" >> $config_host_mak
echo "GIO_CFLAGS=$gio_cflags" >> $config_host_mak
echo "GIO_LIBS=$gio_libs" >> $config_host_mak
echo "GDBUS_CODEGEN=$gdbus_codegen" >> $config_host_mak
fi
echo "CONFIG_TLS_PRIORITY=\"$tls_priority\"" >> $config_host_mak
if test "$gnutls" = "yes" ; then

View File

@ -0,0 +1,74 @@
=============
D-Bus VMState
=============
Introduction
============
The QEMU dbus-vmstate object's aim is to migrate helpers' data running
on a QEMU D-Bus bus. (refer to the :doc:`dbus` document for
some recommendations on D-Bus usage)
Upon migration, QEMU will go through the queue of
``org.qemu.VMState1`` D-Bus name owners and query their ``Id``. It
must be unique among the helpers.
It will then save arbitrary data of each Id to be transferred in the
migration stream and restored/loaded at the corresponding destination
helper.
For now, the data amount to be transferred is arbitrarily limited to
1Mb. The state must be saved quickly (a fraction of a second). (D-Bus
imposes a time limit on reply anyway, and migration would fail if data
isn't given quickly enough.)
dbus-vmstate object can be configured with the expected list of
helpers by setting its ``id-list`` property, with a comma-separated
``Id`` list.
Interface
=========
On object path ``/org/qemu/VMState1``, the following
``org.qemu.VMState1`` interface should be implemented:
.. code:: xml
<interface name="org.qemu.VMState1">
<property name="Id" type="s" access="read"/>
<method name="Load">
<arg type="ay" name="data" direction="in"/>
</method>
<method name="Save">
<arg type="ay" name="data" direction="out"/>
</method>
</interface>
"Id" property
-------------
A string that identifies the helper uniquely. (maximum 256 bytes
including terminating NUL byte)
.. note::
The helper ID namespace is a separate namespace. In particular, it is not
related to QEMU "id" used in -object/-device objects.
Load(in u8[] bytes) method
--------------------------
The method called on destination with the state to restore.
The helper may be initially started in a waiting state (with
an --incoming argument for example), and it may resume on success.
An error may be returned to the caller.
Save(out u8[] bytes) method
---------------------------
The method called on the source to get the current state to be
migrated. The helper should continue to run normally.
An error may be returned to the caller.

110
docs/interop/dbus.rst Normal file
View File

@ -0,0 +1,110 @@
=====
D-Bus
=====
Introduction
============
QEMU may be running with various helper processes involved:
- vhost-user* processes (gpu, virtfs, input, etc...)
- TPM emulation (or other devices)
- user networking (slirp)
- network services (DHCP/DNS, samba/ftp etc)
- background tasks (compression, streaming etc)
- client UI
- admin & cli
Having several processes allows stricter security rules, as well as
greater modularity.
While QEMU itself uses QMP as primary IPC (and Spice/VNC for remote
display), D-Bus is the de facto IPC of choice on Unix systems. The
wire format is machine friendly, good bindings exist for various
languages, and there are various tools available.
Using a bus, helper processes can discover and communicate with each
other easily, without going through QEMU. The bus topology is also
easier to apprehend and debug than a mesh. However, it is wise to
consider the security aspects of it.
Security
========
A QEMU D-Bus bus should be private to a single VM. Thus, only
cooperative tasks are running on the same bus to serve the VM.
D-Bus, the protocol and standard, doesn't have mechanisms to enforce
security between peers once the connection is established. Peers may
have additional mechanisms to enforce security rules, based for
example on UNIX credentials.
The daemon can control which peers can send/recv messages using
various metadata attributes, however, this is alone is not generally
sufficient to make the deployment secure. The semantics of the actual
methods implemented using D-Bus are just as critical. Peers need to
carefully validate any information they received from a peer with a
different trust level.
dbus-daemon policy
------------------
dbus-daemon can enforce various policies based on the UID/GID of the
processes that are connected to it. It is thus a good idea to run
helpers as different UID from QEMU and set appropriate policies.
Depending on the use case, you may choose different scenarios:
- Everything the same UID
- Convenient for developers
- Improved reliability - crash of one part doens't take
out entire VM
- No security benefit over traditional QEMU, unless additional
unless additional controls such as SELinux or AppArmor are
applied
- Two UIDs, one for QEMU, one for dbus & helpers
- Moderately improved user based security isolation
- Many UIDs, one for QEMU one for dbus and one for each helpers
- Best user based security isolation
- Complex to manager distinct UIDs needed for each VM
For example, to allow only ``qemu`` user to talk to ``qemu-helper``
``org.qemu.Helper1`` service, a dbus-daemon policy may contain:
.. code:: xml
<policy user="qemu">
<allow send_destination="org.qemu.Helper1"/>
<allow receive_sender="org.qemu.Helper1"/>
</policy>
<policy user="qemu-helper">
<allow own="org.qemu.Helper1"/>
</policy>
dbus-daemon can also perfom SELinux checks based on the security
context of the source and the target. For example, ``virtiofs_t``
could be allowed to send a message to ``svirt_t``, but ``virtiofs_t``
wouldn't be allowed to send a message to ``virtiofs_t``.
See dbus-daemon man page for details.
Guidelines
==========
When implementing new D-Bus interfaces, it is recommended to follow
the "D-Bus API Design Guidelines":
https://dbus.freedesktop.org/doc/dbus-api-design.html
The "org.qemu.*" prefix is reserved for services implemented &
distributed by the QEMU project.
QEMU Interfaces
===============
:doc:`dbus-vmstate`

View File

@ -13,6 +13,8 @@ Contents:
:maxdepth: 2
bitmaps
dbus
dbus-vmstate
live-block-operations
pr-helper
qemu-ga

View File

@ -822,7 +822,7 @@ static void onenand_realize(DeviceState *dev, Error **errp)
onenand_mem_setup(s);
sysbus_init_irq(sbd, &s->intr);
sysbus_init_mmio(sbd, &s->container);
vmstate_register(dev,
vmstate_register(VMSTATE_IF(dev),
((s->shift & 0x7f) << 24)
| ((s->id.man & 0xff) << 16)
| ((s->id.dev & 0xff) << 8)

View File

@ -9,6 +9,7 @@ common-obj-y += hotplug.o
common-obj-$(CONFIG_SOFTMMU) += nmi.o
common-obj-$(CONFIG_SOFTMMU) += vm-change-state-handler.o
common-obj-y += cpu.o
common-obj-y += vmstate-if.o
common-obj-$(CONFIG_EMPTY_SLOT) += empty_slot.o
common-obj-$(CONFIG_XILINX_AXI) += stream.o

View File

@ -889,7 +889,8 @@ static void device_set_realized(Object *obj, bool value, Error **errp)
dev->canonical_path = object_get_canonical_path(OBJECT(dev));
if (qdev_get_vmsd(dev)) {
if (vmstate_register_with_alias_id(dev, -1, qdev_get_vmsd(dev), dev,
if (vmstate_register_with_alias_id(VMSTATE_IF(dev),
-1, qdev_get_vmsd(dev), dev,
dev->instance_id_alias,
dev->alias_required_for_version,
&local_err) < 0) {
@ -923,7 +924,7 @@ static void device_set_realized(Object *obj, bool value, Error **errp)
local_err ? NULL : &local_err);
}
if (qdev_get_vmsd(dev)) {
vmstate_unregister(dev, qdev_get_vmsd(dev), dev);
vmstate_unregister(VMSTATE_IF(dev), qdev_get_vmsd(dev), dev);
}
if (dc->unrealize) {
dc->unrealize(dev, local_err ? NULL : &local_err);
@ -947,7 +948,7 @@ child_realize_fail:
}
if (qdev_get_vmsd(dev)) {
vmstate_unregister(dev, qdev_get_vmsd(dev), dev);
vmstate_unregister(VMSTATE_IF(dev), qdev_get_vmsd(dev), dev);
}
post_realize_fail:
@ -1087,9 +1088,18 @@ static void device_unparent(Object *obj)
}
}
static char *
device_vmstate_if_get_id(VMStateIf *obj)
{
DeviceState *dev = DEVICE(obj);
return qdev_get_dev_path(dev);
}
static void device_class_init(ObjectClass *class, void *data)
{
DeviceClass *dc = DEVICE_CLASS(class);
VMStateIfClass *vc = VMSTATE_IF_CLASS(class);
class->unparent = device_unparent;
@ -1101,6 +1111,7 @@ static void device_class_init(ObjectClass *class, void *data)
*/
dc->hotpluggable = true;
dc->user_creatable = true;
vc->get_id = device_vmstate_if_get_id;
}
void device_class_set_parent_reset(DeviceClass *dc,
@ -1158,6 +1169,10 @@ static const TypeInfo device_type_info = {
.class_init = device_class_init,
.abstract = true,
.class_size = sizeof(DeviceClass),
.interfaces = (InterfaceInfo[]) {
{ TYPE_VMSTATE_IF },
{ }
}
};
static void qdev_register_types(void)

23
hw/core/vmstate-if.c Normal file
View File

@ -0,0 +1,23 @@
/*
* VMState interface
*
* Copyright (c) 2009-2019 Red Hat Inc
* This work is licensed under the terms of the GNU GPL, version 2 or later.
* See the COPYING file in the top-level directory.
*/
#include "qemu/osdep.h"
#include "hw/vmstate-if.h"
static const TypeInfo vmstate_if_info = {
.name = TYPE_VMSTATE_IF,
.parent = TYPE_INTERFACE,
.class_size = sizeof(VMStateIfClass),
};
static void vmstate_register_types(void)
{
type_register_static(&vmstate_if_info);
}
type_init(vmstate_register_types);

View File

@ -302,7 +302,7 @@ static void pci_cmd646_ide_realize(PCIDevice *dev, Error **errp)
}
g_free(irq);
vmstate_register(DEVICE(dev), 0, &vmstate_ide_pci, d);
vmstate_register(VMSTATE_IF(dev), 0, &vmstate_ide_pci, d);
qemu_register_reset(cmd646_reset, d);
}

View File

@ -75,7 +75,7 @@ static void isa_ide_realizefn(DeviceState *dev, Error **errp)
ide_init_ioport(&s->bus, isadev, s->iobase, s->iobase2);
isa_init_irq(isadev, &s->irq, s->isairq);
ide_init2(&s->bus, s->irq);
vmstate_register(dev, 0, &vmstate_ide_isa, s);
vmstate_register(VMSTATE_IF(dev), 0, &vmstate_ide_isa, s);
ide_register_restart_cb(&s->bus);
}

View File

@ -156,7 +156,7 @@ static void pci_piix_ide_realize(PCIDevice *dev, Error **errp)
bmdma_setup_bar(d);
pci_register_bar(dev, 4, PCI_BASE_ADDRESS_SPACE_IO, &d->bmdma_bar);
vmstate_register(DEVICE(dev), 0, &vmstate_ide_pci, d);
vmstate_register(VMSTATE_IF(dev), 0, &vmstate_ide_pci, d);
pci_piix_init_ports(d);
}

View File

@ -190,7 +190,7 @@ static void via_ide_realize(PCIDevice *dev, Error **errp)
bmdma_setup_bar(d);
pci_register_bar(dev, 4, PCI_BASE_ADDRESS_SPACE_IO, &d->bmdma_bar);
vmstate_register(DEVICE(dev), 0, &vmstate_ide_pci, d);
vmstate_register(VMSTATE_IF(dev), 0, &vmstate_ide_pci, d);
for (i = 0; i < 2; i++) {
ide_bus_new(&d->bus[i], sizeof(d->bus[i]), DEVICE(d), i, 2);

View File

@ -146,7 +146,7 @@ static int max111x_init(SSISlave *d, int inputs)
s->input[7] = 0x80;
s->com = 0;
vmstate_register(dev, -1, &vmstate_max111x, s);
vmstate_register(VMSTATE_IF(dev), -1, &vmstate_max111x, s);
return 0;
}

View File

@ -1815,7 +1815,7 @@ static void pci_nic_uninit(PCIDevice *pci_dev)
{
EEPRO100State *s = DO_UPCAST(EEPRO100State, dev, pci_dev);
vmstate_unregister(&pci_dev->qdev, s->vmstate, s);
vmstate_unregister(VMSTATE_IF(&pci_dev->qdev), s->vmstate, s);
g_free(s->vmstate);
eeprom93xx_free(&pci_dev->qdev, s->eeprom);
qemu_del_nic(s->nic);
@ -1874,7 +1874,7 @@ static void e100_nic_realize(PCIDevice *pci_dev, Error **errp)
s->vmstate = g_memdup(&vmstate_eepro100, sizeof(vmstate_eepro100));
s->vmstate->name = qemu_get_queue(s->nic)->model;
vmstate_register(&pci_dev->qdev, -1, s->vmstate, s);
vmstate_register(VMSTATE_IF(&pci_dev->qdev), -1, s->vmstate, s);
}
static void eepro100_instance_init(Object *obj)

View File

@ -2853,7 +2853,8 @@ static void virtio_net_handle_migration_primary(VirtIONet *n,
if (migration_in_setup(s) && !should_be_hidden) {
if (failover_unplug_primary(n)) {
vmstate_unregister(n->primary_dev, qdev_get_vmsd(n->primary_dev),
vmstate_unregister(VMSTATE_IF(n->primary_dev),
qdev_get_vmsd(n->primary_dev),
n->primary_dev);
qapi_event_send_unplug_primary(n->primary_device_id);
atomic_set(&n->primary_should_be_hidden, true);

View File

@ -321,7 +321,7 @@ eeprom_t *eeprom93xx_new(DeviceState *dev, uint16_t nwords)
/* Output DO is tristate, read results in 1. */
eeprom->eedo = 1;
logout("eeprom = 0x%p, nwords = %u\n", eeprom, nwords);
vmstate_register(dev, 0, &vmstate_eeprom, eeprom);
vmstate_register(VMSTATE_IF(dev), 0, &vmstate_eeprom, eeprom);
return eeprom;
}
@ -329,7 +329,7 @@ void eeprom93xx_free(DeviceState *dev, eeprom_t *eeprom)
{
/* Destroy EEPROM. */
logout("eeprom = 0x%p\n", eeprom);
vmstate_unregister(dev, &vmstate_eeprom, eeprom);
vmstate_unregister(VMSTATE_IF(dev), &vmstate_eeprom, eeprom);
g_free(eeprom);
}

View File

@ -511,7 +511,7 @@ static void realize(DeviceState *d, Error **errp)
error_propagate(errp, err);
return;
}
vmstate_register(DEVICE(drc), spapr_drc_index(drc), &vmstate_spapr_drc,
vmstate_register(VMSTATE_IF(drc), spapr_drc_index(drc), &vmstate_spapr_drc,
drc);
trace_spapr_drc_realize_complete(spapr_drc_index(drc));
}
@ -523,7 +523,7 @@ static void unrealize(DeviceState *d, Error **errp)
gchar *name;
trace_spapr_drc_unrealize(spapr_drc_index(drc));
vmstate_unregister(DEVICE(drc), &vmstate_spapr_drc, drc);
vmstate_unregister(VMSTATE_IF(drc), &vmstate_spapr_drc, drc);
root_container = container_get(object_get_root(), DRC_CONTAINER_PATH);
name = g_strdup_printf("%x", spapr_drc_index(drc));
object_property_del(root_container, name, errp);
@ -619,7 +619,8 @@ static void realize_physical(DeviceState *d, Error **errp)
return;
}
vmstate_register(DEVICE(drcp), spapr_drc_index(SPAPR_DR_CONNECTOR(drcp)),
vmstate_register(VMSTATE_IF(drcp),
spapr_drc_index(SPAPR_DR_CONNECTOR(drcp)),
&vmstate_spapr_drc_physical, drcp);
qemu_register_reset(drc_physical_reset, drcp);
}
@ -635,7 +636,7 @@ static void unrealize_physical(DeviceState *d, Error **errp)
return;
}
vmstate_unregister(DEVICE(drcp), &vmstate_spapr_drc_physical, drcp);
vmstate_unregister(VMSTATE_IF(drcp), &vmstate_spapr_drc_physical, drcp);
qemu_unregister_reset(drc_physical_reset, drcp);
}

View File

@ -317,7 +317,7 @@ static void spapr_tce_table_realize(DeviceState *dev, Error **errp)
QLIST_INSERT_HEAD(&spapr_tce_tables, tcet, list);
vmstate_register(DEVICE(tcet), tcet->liobn, &vmstate_spapr_tce_table,
vmstate_register(VMSTATE_IF(tcet), tcet->liobn, &vmstate_spapr_tce_table,
tcet);
}
@ -420,7 +420,7 @@ static void spapr_tce_table_unrealize(DeviceState *dev, Error **errp)
{
SpaprTceTable *tcet = SPAPR_TCE_TABLE(dev);
vmstate_unregister(DEVICE(tcet), &vmstate_spapr_tce_table, tcet);
vmstate_unregister(VMSTATE_IF(tcet), &vmstate_spapr_tce_table, tcet);
QLIST_REMOVE(tcet, list);

View File

@ -392,7 +392,7 @@ static inline void s390_skeys_set_migration_enabled(Object *obj, bool value,
register_savevm_live(TYPE_S390_SKEYS, 0, 1,
&savevm_s390_storage_keys, ss);
} else {
unregister_savevm(DEVICE(ss), TYPE_S390_SKEYS, ss);
unregister_savevm(VMSTATE_IF(ss), TYPE_S390_SKEYS, ss);
}
}

40
include/hw/vmstate-if.h Normal file
View File

@ -0,0 +1,40 @@
/*
* VMState interface
*
* Copyright (c) 2009-2019 Red Hat Inc
* This work is licensed under the terms of the GNU GPL, version 2 or later.
* See the COPYING file in the top-level directory.
*/
#ifndef VMSTATE_IF_H
#define VMSTATE_IF_H
#include "qom/object.h"
#define TYPE_VMSTATE_IF "vmstate-if"
#define VMSTATE_IF_CLASS(klass) \
OBJECT_CLASS_CHECK(VMStateIfClass, (klass), TYPE_VMSTATE_IF)
#define VMSTATE_IF_GET_CLASS(obj) \
OBJECT_GET_CLASS(VMStateIfClass, (obj), TYPE_VMSTATE_IF)
#define VMSTATE_IF(obj) \
INTERFACE_CHECK(VMStateIf, (obj), TYPE_VMSTATE_IF)
typedef struct VMStateIf VMStateIf;
typedef struct VMStateIfClass {
InterfaceClass parent_class;
char * (*get_id)(VMStateIf *obj);
} VMStateIfClass;
static inline char *vmstate_if_get_id(VMStateIf *vmif)
{
if (!vmif) {
return NULL;
}
return VMSTATE_IF_GET_CLASS(vmif)->get_id(vmif);
}
#endif /* VMSTATE_IF_H */

View File

@ -14,6 +14,8 @@
#ifndef MIGRATION_REGISTER_H
#define MIGRATION_REGISTER_H
#include "hw/vmstate-if.h"
typedef struct SaveVMHandlers {
/* This runs inside the iothread lock. */
SaveStateHandler *save_state;
@ -74,6 +76,6 @@ int register_savevm_live(const char *idstr,
const SaveVMHandlers *ops,
void *opaque);
void unregister_savevm(DeviceState *dev, const char *idstr, void *opaque);
void unregister_savevm(VMStateIf *obj, const char *idstr, void *opaque);
#endif

View File

@ -27,6 +27,8 @@
#ifndef QEMU_VMSTATE_H
#define QEMU_VMSTATE_H
#include "hw/vmstate-if.h"
typedef struct VMStateInfo VMStateInfo;
typedef struct VMStateField VMStateField;
@ -1156,22 +1158,22 @@ int vmstate_save_state_v(QEMUFile *f, const VMStateDescription *vmsd,
bool vmstate_save_needed(const VMStateDescription *vmsd, void *opaque);
/* Returns: 0 on success, -1 on failure */
int vmstate_register_with_alias_id(DeviceState *dev, int instance_id,
int vmstate_register_with_alias_id(VMStateIf *obj, int instance_id,
const VMStateDescription *vmsd,
void *base, int alias_id,
int required_for_version,
Error **errp);
/* Returns: 0 on success, -1 on failure */
static inline int vmstate_register(DeviceState *dev, int instance_id,
static inline int vmstate_register(VMStateIf *obj, int instance_id,
const VMStateDescription *vmsd,
void *opaque)
{
return vmstate_register_with_alias_id(dev, instance_id, vmsd,
return vmstate_register_with_alias_id(obj, instance_id, vmsd,
opaque, -1, 0, NULL);
}
void vmstate_unregister(DeviceState *dev, const VMStateDescription *vmsd,
void vmstate_unregister(VMStateIf *obj, const VMStateDescription *vmsd,
void *opaque);
struct MemoryRegion;

19
include/qemu/dbus.h Normal file
View File

@ -0,0 +1,19 @@
/*
* Helpers for using D-Bus
*
* Copyright (C) 2019 Red Hat, Inc.
*
* This work is licensed under the terms of the GNU GPL, version 2. See
* the COPYING file in the top-level directory.
*/
#ifndef DBUS_H
#define DBUS_H
#include <gio/gio.h>
GStrv qemu_dbus_get_queued_owners(GDBusConnection *connection,
const char *name,
Error **errp);
#endif /* DBUS_H */

View File

@ -760,17 +760,17 @@ int register_savevm_live(const char *idstr,
return 0;
}
void unregister_savevm(DeviceState *dev, const char *idstr, void *opaque)
void unregister_savevm(VMStateIf *obj, const char *idstr, void *opaque)
{
SaveStateEntry *se, *new_se;
char id[256] = "";
if (dev) {
char *path = qdev_get_dev_path(dev);
if (path) {
pstrcpy(id, sizeof(id), path);
if (obj) {
char *oid = vmstate_if_get_id(obj);
if (oid) {
pstrcpy(id, sizeof(id), oid);
pstrcat(id, sizeof(id), "/");
g_free(path);
g_free(oid);
}
}
pstrcat(id, sizeof(id), idstr);
@ -784,7 +784,7 @@ void unregister_savevm(DeviceState *dev, const char *idstr, void *opaque)
}
}
int vmstate_register_with_alias_id(DeviceState *dev, int instance_id,
int vmstate_register_with_alias_id(VMStateIf *obj, int instance_id,
const VMStateDescription *vmsd,
void *opaque, int alias_id,
int required_for_version,
@ -802,8 +802,8 @@ int vmstate_register_with_alias_id(DeviceState *dev, int instance_id,
se->vmsd = vmsd;
se->alias_id = alias_id;
if (dev) {
char *id = qdev_get_dev_path(dev);
if (obj) {
char *id = vmstate_if_get_id(obj);
if (id) {
if (snprintf(se->idstr, sizeof(se->idstr), "%s/", id) >=
sizeof(se->idstr)) {
@ -834,7 +834,7 @@ int vmstate_register_with_alias_id(DeviceState *dev, int instance_id,
return 0;
}
void vmstate_unregister(DeviceState *dev, const VMStateDescription *vmsd,
void vmstate_unregister(VMStateIf *obj, const VMStateDescription *vmsd,
void *opaque)
{
SaveStateEntry *se, *new_se;

View File

@ -3,7 +3,7 @@
const VMStateDescription vmstate_dummy = {};
int vmstate_register_with_alias_id(DeviceState *dev,
int vmstate_register_with_alias_id(VMStateIf *obj,
int instance_id,
const VMStateDescription *vmsd,
void *base, int alias_id,
@ -13,7 +13,7 @@ int vmstate_register_with_alias_id(DeviceState *dev,
return 0;
}
void vmstate_unregister(DeviceState *dev,
void vmstate_unregister(VMStateIf *obj,
const VMStateDescription *vmsd,
void *opaque)
{

View File

@ -158,12 +158,17 @@ check-qtest-generic-$(CONFIG_MODULES) += tests/modules-test$(EXESUF)
check-qtest-generic-y += tests/device-introspect-test$(EXESUF)
check-qtest-generic-y += tests/cdrom-test$(EXESUF)
DBUS_DAEMON := $(shell which dbus-daemon 2>/dev/null)
ifneq ($(GDBUS_CODEGEN),)
ifneq ($(DBUS_DAEMON),)
check-qtest-pci-$(CONFIG_GIO) += tests/dbus-vmstate-test$(EXESUF)
endif
endif
check-qtest-pci-$(CONFIG_RTL8139_PCI) += tests/rtl8139-test$(EXESUF)
check-qtest-pci-$(CONFIG_VGA) += tests/display-vga-test$(EXESUF)
check-qtest-pci-$(CONFIG_HDA) += tests/intel-hda-test$(EXESUF)
check-qtest-pci-$(CONFIG_IVSHMEM_DEVICE) += tests/ivshmem-test$(EXESUF)
check-qtest-i386-$(CONFIG_ISA_TESTDEV) = tests/endianness-test$(EXESUF)
check-qtest-i386-y += tests/fdc-test$(EXESUF)
check-qtest-i386-y += tests/ide-test$(EXESUF)
@ -579,6 +584,7 @@ tests/test-qdev-global-props$(EXESUF): tests/test-qdev-global-props.o \
hw/core/irq.o \
hw/core/fw-path-provider.o \
hw/core/reset.o \
hw/core/vmstate-if.o \
$(test-qapi-obj-y)
tests/test-vmstate$(EXESUF): tests/test-vmstate.o \
migration/vmstate.o migration/vmstate-types.o migration/qemu-file.o \
@ -633,6 +639,19 @@ tests/qapi-schema/doc-good.test.texi: $(SRC_PATH)/tests/qapi-schema/doc-good.jso
@mv tests/qapi-schema/doc-good-qapi-doc.texi $@
@rm -f tests/qapi-schema/doc-good-qapi-*.[ch] tests/qapi-schema/doc-good-qmp-*.[ch]
tests/dbus-vmstate1.h tests/dbus-vmstate1.c: tests/dbus-vmstate1-gen-timestamp ;
tests/dbus-vmstate1-gen-timestamp: $(SRC_PATH)/tests/dbus-vmstate1.xml
$(call quiet-command,$(GDBUS_CODEGEN) $< \
--interface-prefix org.qemu --generate-c-code tests/dbus-vmstate1, \
"GEN","$(@:%-timestamp=%)")
@>$@
tests/dbus-vmstate-test.o-cflags := -DSRCDIR="$(SRC_PATH)"
tests/dbus-vmstate1.o-cflags := $(GIO_CFLAGS)
tests/dbus-vmstate1.o-libs := $(GIO_LIBS)
tests/dbus-vmstate-test.o: tests/dbus-vmstate1.h
tests/test-string-output-visitor$(EXESUF): tests/test-string-output-visitor.o $(test-qapi-obj-y)
tests/test-string-input-visitor$(EXESUF): tests/test-string-input-visitor.o $(test-qapi-obj-y)
tests/test-qmp-event$(EXESUF): tests/test-qmp-event.o $(test-qapi-obj-y) tests/test-qapi-events.o
@ -826,7 +845,7 @@ tests/usb-hcd-uhci-test$(EXESUF): tests/usb-hcd-uhci-test.o $(libqos-usb-obj-y)
tests/usb-hcd-ehci-test$(EXESUF): tests/usb-hcd-ehci-test.o $(libqos-usb-obj-y)
tests/usb-hcd-xhci-test$(EXESUF): tests/usb-hcd-xhci-test.o $(libqos-usb-obj-y)
tests/cpu-plug-test$(EXESUF): tests/cpu-plug-test.o
tests/migration-test$(EXESUF): tests/migration-test.o
tests/migration-test$(EXESUF): tests/migration-test.o tests/migration-helpers.o
tests/qemu-iotests/socket_scm_helper$(EXESUF): tests/qemu-iotests/socket_scm_helper.o
tests/test-qemu-opts$(EXESUF): tests/test-qemu-opts.o $(test-util-obj-y)
tests/test-keyval$(EXESUF): tests/test-keyval.o $(test-util-obj-y) $(test-qapi-obj-y)
@ -836,6 +855,7 @@ tests/test-filter-mirror$(EXESUF): tests/test-filter-mirror.o $(qtest-obj-y)
tests/test-filter-redirector$(EXESUF): tests/test-filter-redirector.o $(qtest-obj-y)
tests/test-x86-cpuid-compat$(EXESUF): tests/test-x86-cpuid-compat.o $(qtest-obj-y)
tests/ivshmem-test$(EXESUF): tests/ivshmem-test.o contrib/ivshmem-server/ivshmem-server.o $(libqos-pc-obj-y) $(libqos-spapr-obj-y)
tests/dbus-vmstate-test$(EXESUF): tests/dbus-vmstate-test.o tests/migration-helpers.o tests/dbus-vmstate1.o $(libqos-pc-obj-y) $(libqos-spapr-obj-y)
tests/vhost-user-bridge$(EXESUF): tests/vhost-user-bridge.o $(test-util-obj-y) libvhost-user.a
tests/test-uuid$(EXESUF): tests/test-uuid.o $(test-util-obj-y)
tests/test-arm-mptimer$(EXESUF): tests/test-arm-mptimer.o
@ -1194,6 +1214,7 @@ check-clean:
rm -rf $(check-unit-y) tests/*.o $(QEMU_IOTESTS_HELPERS-y)
rm -rf $(sort $(foreach target,$(SYSEMU_TARGET_LIST), $(check-qtest-$(target)-y)) $(check-qtest-generic-y))
rm -f tests/test-qapi-gen-timestamp
rm -f tests/dbus-vmstate1-gen-timestamp
rm -rf $(TESTS_VENV_DIR) $(TESTS_RESULTS_DIR)
clean: check-clean

95
tests/dbus-vmstate-daemon.sh Executable file
View File

@ -0,0 +1,95 @@
#!/bin/sh
# dbus-daemon wrapper script for dbus-vmstate testing
#
# This script allows to tweak the dbus-daemon policy during the test
# to test different configurations.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, see <http://www.gnu.org/licenses/>.
#
# Copyright (C) 2019 Red Hat, Inc.
write_config()
{
CONF="$1"
cat > "$CONF" <<EOF
<busconfig>
<type>session</type>
<listen>unix:tmpdir=$DBUS_VMSTATE_TEST_TMPDIR</listen>
<policy context="default">
<!-- Holes must be punched in service configuration files for
name ownership and sending method calls -->
<deny own="*"/>
<deny send_type="method_call"/>
<!-- Signals and reply messages (method returns, errors) are allowed
by default -->
<allow send_type="signal"/>
<allow send_requested_reply="true" send_type="method_return"/>
<allow send_requested_reply="true" send_type="error"/>
<!-- All messages may be received by default -->
<allow receive_type="method_call"/>
<allow receive_type="method_return"/>
<allow receive_type="error"/>
<allow receive_type="signal"/>
<!-- Allow anyone to talk to the message bus -->
<allow send_destination="org.freedesktop.DBus"
send_interface="org.freedesktop.DBus" />
<allow send_destination="org.freedesktop.DBus"
send_interface="org.freedesktop.DBus.Introspectable"/>
<allow send_destination="org.freedesktop.DBus"
send_interface="org.freedesktop.DBus.Properties"/>
<!-- But disallow some specific bus services -->
<deny send_destination="org.freedesktop.DBus"
send_interface="org.freedesktop.DBus"
send_member="UpdateActivationEnvironment"/>
<deny send_destination="org.freedesktop.DBus"
send_interface="org.freedesktop.DBus.Debug.Stats"/>
<deny send_destination="org.freedesktop.DBus"
send_interface="org.freedesktop.systemd1.Activator"/>
<allow own="org.qemu.VMState1"/>
<allow send_destination="org.qemu.VMState1"/>
<allow receive_sender="org.qemu.VMState1"/>
</policy>
<include if_selinux_enabled="yes"
selinux_root_relative="yes">contexts/dbus_contexts</include>
</busconfig>
EOF
}
ARGS=
for arg in "$@"
do
case $arg in
--config-file=*)
CONF="${arg#*=}"
write_config "$CONF"
ARGS="$ARGS $1"
shift
;;
*)
ARGS="$ARGS $1"
shift
;;
esac
done
exec dbus-daemon $ARGS

382
tests/dbus-vmstate-test.c Normal file
View File

@ -0,0 +1,382 @@
#include "qemu/osdep.h"
#include <glib/gstdio.h>
#include <gio/gio.h>
#include "libqtest.h"
#include "qemu-common.h"
#include "dbus-vmstate1.h"
#include "migration-helpers.h"
static char *workdir;
typedef struct TestServerId {
const char *name;
const char *data;
size_t size;
} TestServerId;
static const TestServerId idA = {
"idA", "I'am\0idA!", sizeof("I'am\0idA!")
};
static const TestServerId idB = {
"idB", "I'am\0idB!", sizeof("I'am\0idB!")
};
typedef struct TestServer {
const TestServerId *id;
bool save_called;
bool load_called;
} TestServer;
typedef struct Test {
const char *id_list;
bool migrate_fail;
bool without_dst_b;
TestServer srcA;
TestServer dstA;
TestServer srcB;
TestServer dstB;
GMainLoop *loop;
QTestState *src_qemu;
} Test;
static gboolean
vmstate_load(VMState1 *object, GDBusMethodInvocation *invocation,
const gchar *arg_data, gpointer user_data)
{
TestServer *h = user_data;
g_autoptr(GVariant) var = NULL;
GVariant *args;
const uint8_t *data;
size_t size;
args = g_dbus_method_invocation_get_parameters(invocation);
var = g_variant_get_child_value(args, 0);
data = g_variant_get_fixed_array(var, &size, sizeof(char));
g_assert_cmpuint(size, ==, h->id->size);
g_assert(!memcmp(data, h->id->data, h->id->size));
h->load_called = true;
g_dbus_method_invocation_return_value(invocation, g_variant_new("()"));
return TRUE;
}
static gboolean
vmstate_save(VMState1 *object, GDBusMethodInvocation *invocation,
gpointer user_data)
{
TestServer *h = user_data;
GVariant *var;
var = g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE,
h->id->data, h->id->size, sizeof(char));
g_dbus_method_invocation_return_value(invocation,
g_variant_new("(@ay)", var));
h->save_called = true;
return TRUE;
}
typedef struct WaitNamed {
GMainLoop *loop;
bool named;
} WaitNamed;
static void
named_cb(GDBusConnection *connection,
const gchar *name,
gpointer user_data)
{
WaitNamed *t = user_data;
t->named = true;
g_main_loop_quit(t->loop);
}
static GDBusConnection *
get_connection(Test *test, guint *ownid)
{
g_autofree gchar *addr = NULL;
WaitNamed *wait;
GError *err = NULL;
GDBusConnection *c;
wait = g_new0(WaitNamed, 1);
wait->loop = test->loop;
addr = g_dbus_address_get_for_bus_sync(G_BUS_TYPE_SESSION, NULL, &err);
g_assert_no_error(err);
c = g_dbus_connection_new_for_address_sync(
addr,
G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION |
G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT,
NULL, NULL, &err);
g_assert_no_error(err);
*ownid = g_bus_own_name_on_connection(c, "org.qemu.VMState1",
G_BUS_NAME_OWNER_FLAGS_NONE,
named_cb, named_cb, wait, g_free);
if (!wait->named) {
g_main_loop_run(wait->loop);
}
return c;
}
static GDBusObjectManagerServer *
get_server(GDBusConnection *conn, TestServer *s, const TestServerId *id)
{
g_autoptr(GDBusObjectSkeleton) sk = NULL;
g_autoptr(VMState1Skeleton) v = NULL;
GDBusObjectManagerServer *os;
s->id = id;
os = g_dbus_object_manager_server_new("/org/qemu");
sk = g_dbus_object_skeleton_new("/org/qemu/VMState1");
v = VMSTATE1_SKELETON(vmstate1_skeleton_new());
g_object_set(v, "id", id->name, NULL);
g_signal_connect(v, "handle-load", G_CALLBACK(vmstate_load), s);
g_signal_connect(v, "handle-save", G_CALLBACK(vmstate_save), s);
g_dbus_object_skeleton_add_interface(sk, G_DBUS_INTERFACE_SKELETON(v));
g_dbus_object_manager_server_export(os, sk);
g_dbus_object_manager_server_set_connection(os, conn);
return os;
}
static void
set_id_list(Test *test, QTestState *s)
{
if (!test->id_list) {
return;
}
g_assert(!qmp_rsp_is_err(qtest_qmp(s,
"{ 'execute': 'qom-set', 'arguments': "
"{ 'path': '/objects/dv', 'property': 'id-list', 'value': %s } }",
test->id_list)));
}
static gpointer
dbus_vmstate_thread(gpointer data)
{
GMainLoop *loop = data;
g_main_loop_run(loop);
return NULL;
}
static void
test_dbus_vmstate(Test *test)
{
g_autofree char *src_qemu_args = NULL;
g_autofree char *dst_qemu_args = NULL;
g_autoptr(GTestDBus) srcbus = NULL;
g_autoptr(GTestDBus) dstbus = NULL;
g_autoptr(GDBusConnection) srcconnA = NULL;
g_autoptr(GDBusConnection) srcconnB = NULL;
g_autoptr(GDBusConnection) dstconnA = NULL;
g_autoptr(GDBusConnection) dstconnB = NULL;
g_autoptr(GDBusObjectManagerServer) srcserverA = NULL;
g_autoptr(GDBusObjectManagerServer) srcserverB = NULL;
g_autoptr(GDBusObjectManagerServer) dstserverA = NULL;
g_autoptr(GDBusObjectManagerServer) dstserverB = NULL;
g_auto(GStrv) srcaddr = NULL;
g_auto(GStrv) dstaddr = NULL;
g_autoptr(GThread) thread = NULL;
g_autoptr(GMainLoop) loop = NULL;
g_autofree char *uri = NULL;
QTestState *src_qemu = NULL, *dst_qemu = NULL;
guint ownsrcA, ownsrcB, owndstA, owndstB;
uri = g_strdup_printf("unix:%s/migsocket", workdir);
loop = g_main_loop_new(NULL, FALSE);
test->loop = loop;
srcbus = g_test_dbus_new(G_TEST_DBUS_NONE);
g_test_dbus_up(srcbus);
srcconnA = get_connection(test, &ownsrcA);
srcserverA = get_server(srcconnA, &test->srcA, &idA);
srcconnB = get_connection(test, &ownsrcB);
srcserverB = get_server(srcconnB, &test->srcB, &idB);
/* remove ,guid=foo part */
srcaddr = g_strsplit(g_test_dbus_get_bus_address(srcbus), ",", 2);
src_qemu_args =
g_strdup_printf("-object dbus-vmstate,id=dv,addr=%s", srcaddr[0]);
dstbus = g_test_dbus_new(G_TEST_DBUS_NONE);
g_test_dbus_up(dstbus);
dstconnA = get_connection(test, &owndstA);
dstserverA = get_server(dstconnA, &test->dstA, &idA);
if (!test->without_dst_b) {
dstconnB = get_connection(test, &owndstB);
dstserverB = get_server(dstconnB, &test->dstB, &idB);
}
dstaddr = g_strsplit(g_test_dbus_get_bus_address(dstbus), ",", 2);
dst_qemu_args =
g_strdup_printf("-object dbus-vmstate,id=dv,addr=%s -incoming %s",
dstaddr[0], uri);
src_qemu = qtest_init(src_qemu_args);
dst_qemu = qtest_init(dst_qemu_args);
set_id_list(test, src_qemu);
set_id_list(test, dst_qemu);
thread = g_thread_new("dbus-vmstate-thread", dbus_vmstate_thread, loop);
migrate_qmp(src_qemu, uri, "{}");
test->src_qemu = src_qemu;
if (test->migrate_fail) {
wait_for_migration_fail(src_qemu, true);
qtest_set_expected_status(dst_qemu, 1);
} else {
wait_for_migration_complete(src_qemu);
}
qtest_quit(dst_qemu);
qtest_quit(src_qemu);
g_bus_unown_name(ownsrcA);
g_bus_unown_name(ownsrcB);
g_bus_unown_name(owndstA);
if (!test->without_dst_b) {
g_bus_unown_name(owndstB);
}
g_main_loop_quit(test->loop);
}
static void
check_not_migrated(TestServer *s, TestServer *d)
{
assert(!s->save_called);
assert(!s->load_called);
assert(!d->save_called);
assert(!d->load_called);
}
static void
check_migrated(TestServer *s, TestServer *d)
{
assert(s->save_called);
assert(!s->load_called);
assert(!d->save_called);
assert(d->load_called);
}
static void
test_dbus_vmstate_without_list(void)
{
Test test = { 0, };
test_dbus_vmstate(&test);
check_migrated(&test.srcA, &test.dstA);
check_migrated(&test.srcB, &test.dstB);
}
static void
test_dbus_vmstate_with_list(void)
{
Test test = { .id_list = "idA,idB" };
test_dbus_vmstate(&test);
check_migrated(&test.srcA, &test.dstA);
check_migrated(&test.srcB, &test.dstB);
}
static void
test_dbus_vmstate_only_a(void)
{
Test test = { .id_list = "idA" };
test_dbus_vmstate(&test);
check_migrated(&test.srcA, &test.dstA);
check_not_migrated(&test.srcB, &test.dstB);
}
static void
test_dbus_vmstate_missing_src(void)
{
Test test = { .id_list = "idA,idC", .migrate_fail = true };
/* run in subprocess to silence QEMU error reporting */
if (g_test_subprocess()) {
test_dbus_vmstate(&test);
check_not_migrated(&test.srcA, &test.dstA);
check_not_migrated(&test.srcB, &test.dstB);
return;
}
g_test_trap_subprocess(NULL, 0, 0);
g_test_trap_assert_passed();
}
static void
test_dbus_vmstate_missing_dst(void)
{
Test test = { .id_list = "idA,idB",
.without_dst_b = true,
.migrate_fail = true };
/* run in subprocess to silence QEMU error reporting */
if (g_test_subprocess()) {
test_dbus_vmstate(&test);
assert(test.srcA.save_called);
assert(test.srcB.save_called);
assert(!test.dstB.save_called);
return;
}
g_test_trap_subprocess(NULL, 0, 0);
g_test_trap_assert_passed();
}
int
main(int argc, char **argv)
{
GError *err = NULL;
g_autofree char *dbus_daemon = NULL;
int ret;
dbus_daemon = g_build_filename(G_STRINGIFY(SRCDIR),
"tests",
"dbus-vmstate-daemon.sh",
NULL);
g_setenv("G_TEST_DBUS_DAEMON", dbus_daemon, true);
g_test_init(&argc, &argv, NULL);
workdir = g_dir_make_tmp("dbus-vmstate-test-XXXXXX", &err);
if (!workdir) {
g_error("Unable to create temporary dir: %s\n", err->message);
exit(1);
}
g_setenv("DBUS_VMSTATE_TEST_TMPDIR", workdir, true);
qtest_add_func("/dbus-vmstate/without-list",
test_dbus_vmstate_without_list);
qtest_add_func("/dbus-vmstate/with-list",
test_dbus_vmstate_with_list);
qtest_add_func("/dbus-vmstate/only-a",
test_dbus_vmstate_only_a);
qtest_add_func("/dbus-vmstate/missing-src",
test_dbus_vmstate_missing_src);
qtest_add_func("/dbus-vmstate/missing-dst",
test_dbus_vmstate_missing_dst);
ret = g_test_run();
rmdir(workdir);
g_free(workdir);
return ret;
}

12
tests/dbus-vmstate1.xml Normal file
View File

@ -0,0 +1,12 @@
<?xml version="1.0"?>
<node name="/" xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd">
<interface name="org.qemu.VMState1">
<property name="Id" type="s" access="read"/>
<method name="Load">
<arg type="ay" name="data" direction="in"/>
</method>
<method name="Save">
<arg type="ay" name="data" direction="out"/>
</method>
</interface>
</node>

View File

@ -8,6 +8,7 @@ ENV PACKAGES \
bzip2-devel \
ccache \
csnappy-devel \
dbus-daemon \
flex \
gcc-c++ \
gcc \

View File

@ -21,6 +21,7 @@ RUN apt update && \
build-essential \
ca-certificates \
clang \
dbus \
flex \
gettext \
git \

View File

@ -8,6 +8,7 @@ ENV PACKAGES \
ccache \
clang \
cyrus-sasl-devel \
dbus-daemon \
device-mapper-multipath-devel \
findutils \
flex \

View File

@ -13,6 +13,7 @@ FROM ubuntu:19.04
ENV PACKAGES flex bison \
ccache \
clang \
dbus \
gcc \
gettext \
git \

167
tests/migration-helpers.c Normal file
View File

@ -0,0 +1,167 @@
/*
* QTest migration helpers
*
* Copyright (c) 2016-2018 Red Hat, Inc. and/or its affiliates
* based on the vhost-user-test.c that is:
* Copyright (c) 2014 Virtual Open Systems Sarl.
*
* This work is licensed under the terms of the GNU GPL, version 2 or later.
* See the COPYING file in the top-level directory.
*
*/
#include "qemu/osdep.h"
#include "qapi/qmp/qjson.h"
#include "migration-helpers.h"
bool got_stop;
static void stop_cb(void *opaque, const char *name, QDict *data)
{
if (!strcmp(name, "STOP")) {
got_stop = true;
}
}
/*
* Events can get in the way of responses we are actually waiting for.
*/
QDict *wait_command_fd(QTestState *who, int fd, const char *command, ...)
{
va_list ap;
va_start(ap, command);
qtest_qmp_vsend_fds(who, &fd, 1, command, ap);
va_end(ap);
return qtest_qmp_receive_success(who, stop_cb, NULL);
}
/*
* Events can get in the way of responses we are actually waiting for.
*/
QDict *wait_command(QTestState *who, const char *command, ...)
{
va_list ap;
va_start(ap, command);
qtest_qmp_vsend(who, command, ap);
va_end(ap);
return qtest_qmp_receive_success(who, stop_cb, NULL);
}
/*
* Send QMP command "migrate".
* Arguments are built from @fmt... (formatted like
* qobject_from_jsonf_nofail()) with "uri": @uri spliced in.
*/
void migrate_qmp(QTestState *who, const char *uri, const char *fmt, ...)
{
va_list ap;
QDict *args, *rsp;
va_start(ap, fmt);
args = qdict_from_vjsonf_nofail(fmt, ap);
va_end(ap);
g_assert(!qdict_haskey(args, "uri"));
qdict_put_str(args, "uri", uri);
rsp = qtest_qmp(who, "{ 'execute': 'migrate', 'arguments': %p}", args);
g_assert(qdict_haskey(rsp, "return"));
qobject_unref(rsp);
}
/*
* Note: caller is responsible to free the returned object via
* qobject_unref() after use
*/
QDict *migrate_query(QTestState *who)
{
return wait_command(who, "{ 'execute': 'query-migrate' }");
}
/*
* Note: caller is responsible to free the returned object via
* g_free() after use
*/
static gchar *migrate_query_status(QTestState *who)
{
QDict *rsp_return = migrate_query(who);
gchar *status = g_strdup(qdict_get_str(rsp_return, "status"));
g_assert(status);
qobject_unref(rsp_return);
return status;
}
static bool check_migration_status(QTestState *who, const char *goal,
const char **ungoals)
{
bool ready;
char *current_status;
const char **ungoal;
current_status = migrate_query_status(who);
ready = strcmp(current_status, goal) == 0;
if (!ungoals) {
g_assert_cmpstr(current_status, !=, "failed");
/*
* If looking for a state other than completed,
* completion of migration would cause the test to
* hang.
*/
if (strcmp(goal, "completed") != 0) {
g_assert_cmpstr(current_status, !=, "completed");
}
} else {
for (ungoal = ungoals; *ungoal; ungoal++) {
g_assert_cmpstr(current_status, !=, *ungoal);
}
}
g_free(current_status);
return ready;
}
void wait_for_migration_status(QTestState *who,
const char *goal, const char **ungoals)
{
while (!check_migration_status(who, goal, ungoals)) {
usleep(1000);
}
}
void wait_for_migration_complete(QTestState *who)
{
wait_for_migration_status(who, "completed", NULL);
}
void wait_for_migration_fail(QTestState *from, bool allow_active)
{
QDict *rsp_return;
char *status;
bool failed;
do {
status = migrate_query_status(from);
bool result = !strcmp(status, "setup") || !strcmp(status, "failed") ||
(allow_active && !strcmp(status, "active"));
if (!result) {
fprintf(stderr, "%s: unexpected status status=%s allow_active=%d\n",
__func__, status, allow_active);
}
g_assert(result);
failed = !strcmp(status, "failed");
g_free(status);
} while (!failed);
/* Is the machine currently running? */
rsp_return = wait_command(from, "{ 'execute': 'query-status' }");
g_assert(qdict_haskey(rsp_return, "running"));
g_assert(qdict_get_bool(rsp_return, "running"));
qobject_unref(rsp_return);
}

37
tests/migration-helpers.h Normal file
View File

@ -0,0 +1,37 @@
/*
* QTest migration helpers
*
* Copyright (c) 2016-2018 Red Hat, Inc. and/or its affiliates
* based on the vhost-user-test.c that is:
* Copyright (c) 2014 Virtual Open Systems Sarl.
*
* This work is licensed under the terms of the GNU GPL, version 2 or later.
* See the COPYING file in the top-level directory.
*
*/
#ifndef MIGRATION_HELPERS_H_
#define MIGRATION_HELPERS_H_
#include "libqtest.h"
extern bool got_stop;
GCC_FMT_ATTR(3, 4)
QDict *wait_command_fd(QTestState *who, int fd, const char *command, ...);
GCC_FMT_ATTR(2, 3)
QDict *wait_command(QTestState *who, const char *command, ...);
GCC_FMT_ATTR(3, 4)
void migrate_qmp(QTestState *who, const char *uri, const char *fmt, ...);
QDict *migrate_query(QTestState *who);
void wait_for_migration_status(QTestState *who,
const char *goal, const char **ungoals);
void wait_for_migration_complete(QTestState *who);
void wait_for_migration_fail(QTestState *from, bool allow_active);
#endif /* MIGRATION_HELPERS_H_ */

View File

@ -14,7 +14,6 @@
#include "libqtest.h"
#include "qapi/qmp/qdict.h"
#include "qapi/qmp/qjson.h"
#include "qemu/module.h"
#include "qemu/option.h"
#include "qemu/range.h"
@ -24,6 +23,7 @@
#include "qapi/qobject-input-visitor.h"
#include "qapi/qobject-output-visitor.h"
#include "migration-helpers.h"
#include "migration/migration-test.h"
/* TODO actually test the results and get rid of this */
@ -31,7 +31,6 @@
unsigned start_address;
unsigned end_address;
bool got_stop;
static bool uffd_feature_thread_id;
#if defined(__linux__)
@ -157,67 +156,6 @@ static void wait_for_serial(const char *side)
} while (true);
}
static void stop_cb(void *opaque, const char *name, QDict *data)
{
if (!strcmp(name, "STOP")) {
got_stop = true;
}
}
/*
* Events can get in the way of responses we are actually waiting for.
*/
GCC_FMT_ATTR(3, 4)
static QDict *wait_command_fd(QTestState *who, int fd, const char *command, ...)
{
va_list ap;
va_start(ap, command);
qtest_qmp_vsend_fds(who, &fd, 1, command, ap);
va_end(ap);
return qtest_qmp_receive_success(who, stop_cb, NULL);
}
/*
* Events can get in the way of responses we are actually waiting for.
*/
GCC_FMT_ATTR(2, 3)
static QDict *wait_command(QTestState *who, const char *command, ...)
{
va_list ap;
va_start(ap, command);
qtest_qmp_vsend(who, command, ap);
va_end(ap);
return qtest_qmp_receive_success(who, stop_cb, NULL);
}
/*
* Note: caller is responsible to free the returned object via
* qobject_unref() after use
*/
static QDict *migrate_query(QTestState *who)
{
return wait_command(who, "{ 'execute': 'query-migrate' }");
}
/*
* Note: caller is responsible to free the returned object via
* g_free() after use
*/
static gchar *migrate_query_status(QTestState *who)
{
QDict *rsp_return = migrate_query(who);
gchar *status = g_strdup(qdict_get_str(rsp_return, "status"));
g_assert(status);
qobject_unref(rsp_return);
return status;
}
/*
* It's tricky to use qemu's migration event capability with qtest,
* events suddenly appearing confuse the qmp()/hmp() responses.
@ -265,48 +203,6 @@ static void read_blocktime(QTestState *who)
qobject_unref(rsp_return);
}
static bool check_migration_status(QTestState *who, const char *goal,
const char **ungoals)
{
bool ready;
char *current_status;
const char **ungoal;
current_status = migrate_query_status(who);
ready = strcmp(current_status, goal) == 0;
if (!ungoals) {
g_assert_cmpstr(current_status, !=, "failed");
/*
* If looking for a state other than completed,
* completion of migration would cause the test to
* hang.
*/
if (strcmp(goal, "completed") != 0) {
g_assert_cmpstr(current_status, !=, "completed");
}
} else {
for (ungoal = ungoals; *ungoal; ungoal++) {
g_assert_cmpstr(current_status, !=, *ungoal);
}
}
g_free(current_status);
return ready;
}
static void wait_for_migration_status(QTestState *who,
const char *goal,
const char **ungoals)
{
while (!check_migration_status(who, goal, ungoals)) {
usleep(1000);
}
}
static void wait_for_migration_complete(QTestState *who)
{
wait_for_migration_status(who, "completed", NULL);
}
static void wait_for_migration_pass(QTestState *who)
{
uint64_t initial_pass = get_migration_pass(who);
@ -506,30 +402,6 @@ static void migrate_set_capability(QTestState *who, const char *capability,
qobject_unref(rsp);
}
/*
* Send QMP command "migrate".
* Arguments are built from @fmt... (formatted like
* qobject_from_jsonf_nofail()) with "uri": @uri spliced in.
*/
GCC_FMT_ATTR(3, 4)
static void migrate(QTestState *who, const char *uri, const char *fmt, ...)
{
va_list ap;
QDict *args, *rsp;
va_start(ap, fmt);
args = qdict_from_vjsonf_nofail(fmt, ap);
va_end(ap);
g_assert(!qdict_haskey(args, "uri"));
qdict_put_str(args, "uri", uri);
rsp = qtest_qmp(who, "{ 'execute': 'migrate', 'arguments': %p}", args);
g_assert(qdict_haskey(rsp, "return"));
qobject_unref(rsp);
}
static void migrate_postcopy_start(QTestState *from, QTestState *to)
{
QDict *rsp;
@ -800,7 +672,7 @@ static int migrate_postcopy_prepare(QTestState **from_ptr,
/* Wait for the first serial output from the source */
wait_for_serial("src_serial");
migrate(from, uri, "{}");
migrate_qmp(from, uri, "{}");
g_free(uri);
wait_for_migration_pass(from);
@ -891,7 +763,7 @@ static void test_postcopy_recovery(void)
wait_for_migration_status(from, "postcopy-paused",
(const char * []) { "failed", "active",
"completed", NULL });
migrate(from, uri, "{'resume': true}");
migrate_qmp(from, uri, "{'resume': true}");
g_free(uri);
/* Restore the postcopy bandwidth to unlimited */
@ -900,32 +772,6 @@ static void test_postcopy_recovery(void)
migrate_postcopy_complete(from, to);
}
static void wait_for_migration_fail(QTestState *from, bool allow_active)
{
QDict *rsp_return;
char *status;
bool failed;
do {
status = migrate_query_status(from);
bool result = !strcmp(status, "setup") || !strcmp(status, "failed") ||
(allow_active && !strcmp(status, "active"));
if (!result) {
fprintf(stderr, "%s: unexpected status status=%s allow_active=%d\n",
__func__, status, allow_active);
}
g_assert(result);
failed = !strcmp(status, "failed");
g_free(status);
} while (!failed);
/* Is the machine currently running? */
rsp_return = wait_command(from, "{ 'execute': 'query-status' }");
g_assert(qdict_haskey(rsp_return, "running"));
g_assert(qdict_get_bool(rsp_return, "running"));
qobject_unref(rsp_return);
}
static void test_baddest(void)
{
MigrateStart *args = migrate_start_new();
@ -936,7 +782,7 @@ static void test_baddest(void)
if (test_migrate_start(&from, &to, "tcp:0:0", args)) {
return;
}
migrate(from, "tcp:0:0", "{}");
migrate_qmp(from, "tcp:0:0", "{}");
wait_for_migration_fail(from, false);
test_migrate_end(from, to, false);
}
@ -963,7 +809,7 @@ static void test_precopy_unix(void)
/* Wait for the first serial output from the source */
wait_for_serial("src_serial");
migrate(from, uri, "{}");
migrate_qmp(from, uri, "{}");
wait_for_migration_pass(from);
@ -1000,7 +846,7 @@ static void test_ignore_shared(void)
/* Wait for the first serial output from the source */
wait_for_serial("src_serial");
migrate(from, uri, "{}");
migrate_qmp(from, uri, "{}");
wait_for_migration_pass(from);
@ -1047,7 +893,7 @@ static void test_xbzrle(const char *uri)
/* Wait for the first serial output from the source */
wait_for_serial("src_serial");
migrate(from, uri, "{}");
migrate_qmp(from, uri, "{}");
wait_for_migration_pass(from);
@ -1098,7 +944,7 @@ static void test_precopy_tcp(void)
uri = migrate_get_socket_address(to, "socket-address");
migrate(from, uri, "{}");
migrate_qmp(from, uri, "{}");
wait_for_migration_pass(from);
@ -1167,7 +1013,7 @@ static void test_migrate_fd_proto(void)
close(pair[1]);
/* Start migration to the 2nd socket*/
migrate(from, "fd:fd-mig", "{}");
migrate_qmp(from, "fd:fd-mig", "{}");
wait_for_migration_pass(from);
@ -1222,7 +1068,7 @@ static void do_test_validate_uuid(MigrateStart *args, bool should_fail)
/* Wait for the first serial output from the source */
wait_for_serial("src_serial");
migrate(from, uri, "{}");
migrate_qmp(from, uri, "{}");
if (should_fail) {
qtest_set_expected_status(to, 1);
@ -1316,7 +1162,7 @@ static void test_migrate_auto_converge(void)
/* Wait for the first serial output from the source */
wait_for_serial("src_serial");
migrate(from, uri, "{}");
migrate_qmp(from, uri, "{}");
/* Wait for throttling begins */
percentage = 0;

View File

@ -56,3 +56,6 @@ util-obj-$(call lnot,$(CONFIG_INOTIFY1)) += filemonitor-stub.o
util-obj-$(CONFIG_LINUX) += vfio-helpers.o
util-obj-$(CONFIG_POSIX) += drm.o
util-obj-y += guest-random.o
util-obj-$(CONFIG_GIO) += dbus.o
dbus.o-cflags = $(GIO_CFLAGS)
dbus.o-libs = $(GIO_LIBS)

57
util/dbus.c Normal file
View File

@ -0,0 +1,57 @@
/*
* Helpers for using D-Bus
*
* Copyright (C) 2019 Red Hat, Inc.
*
* This work is licensed under the terms of the GNU GPL, version 2. See
* the COPYING file in the top-level directory.
*/
#include "qemu/osdep.h"
#include "qemu/dbus.h"
#include "qemu/error-report.h"
#include "qapi/error.h"
/*
* qemu_dbus_get_queued_owners() - return the list of queued unique names
* @connection: A GDBusConnection
* @name: a service name
*
* Return: a GStrv of unique names, or NULL on failure.
*/
GStrv
qemu_dbus_get_queued_owners(GDBusConnection *connection, const char *name,
Error **errp)
{
g_autoptr(GDBusProxy) proxy = NULL;
g_autoptr(GVariant) result = NULL;
g_autoptr(GVariant) child = NULL;
g_autoptr(GError) err = NULL;
proxy = g_dbus_proxy_new_sync(connection, G_DBUS_PROXY_FLAGS_NONE, NULL,
"org.freedesktop.DBus",
"/org/freedesktop/DBus",
"org.freedesktop.DBus",
NULL, &err);
if (!proxy) {
error_setg(errp, "Failed to create DBus proxy: %s", err->message);
return NULL;
}
result = g_dbus_proxy_call_sync(proxy, "ListQueuedOwners",
g_variant_new("(s)", name),
G_DBUS_CALL_FLAGS_NO_AUTO_START,
-1, NULL, &err);
if (!result) {
if (g_error_matches(err,
G_DBUS_ERROR,
G_DBUS_ERROR_NAME_HAS_NO_OWNER)) {
return g_new0(char *, 1);
}
error_setg(errp, "Failed to call ListQueuedOwners: %s", err->message);
return NULL;
}
child = g_variant_get_child_value(result, 0);
return g_variant_dup_strv(child, NULL);
}