Add dbus-vmstate object

When instantiated, this object will connect to the given D-Bus bus
"addr". During migration, it will take/restore the data from
org.qemu.VMState1 instances. See documentation for details.

Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
This commit is contained in:
Marc-André Lureau 2019-12-16 11:48:53 +04:00
parent a5021d6991
commit 5010cec2bc
8 changed files with 604 additions and 0 deletions

View File

@ -2209,9 +2209,11 @@ F: qapi/migration.json
D-Bus
M: Marc-André Lureau <marcandre.lureau@redhat.com>
S: Maintained
F: backends/dbus-vmstate.c
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>

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"

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.

View File

@ -103,3 +103,8 @@ 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

@ -14,6 +14,7 @@ Contents:
bitmaps
dbus
dbus-vmstate
live-block-operations
pr-helper
qemu-ga