All of lore.kernel.org
 help / color / mirror / Atom feed
From: "Dr. David Alan Gilbert" <dgilbert@redhat.com>
To: "Marc-André Lureau" <marcandre.lureau@redhat.com>
Cc: Laurent Vivier <lvivier@redhat.com>,
	Paolo Bonzini <pbonzini@redhat.com>,
	Thomas Huth <thuth@redhat.com>,
	qemu-devel@nongnu.org, Juan Quintela <quintela@redhat.com>
Subject: Re: [Qemu-devel] [PATCH v2 2/2] Add dbus-vmstate object
Date: Thu, 22 Aug 2019 11:55:58 +0100	[thread overview]
Message-ID: <20190822105558.GF3277@work-vm> (raw)
In-Reply-To: <20190808150325.21939-3-marcandre.lureau@redhat.com>

* Marc-André Lureau (marcandre.lureau@redhat.com) wrote:
> When instanciated, this object will connect to the given D-Bus
> bus. During migration, it will take the data from org.qemu.VMState1
> instances.
> 
> See documentation for further details.

I'll leave the main review to someone who understands the details of
DBUS, however from the migration side:

> Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>

<snip>

> +
> +static int
> +dbus_load_state(QEMUFile *f, void *opaque, int version_id)
> +{
> +    DBusVMState *self = DBUS_VMSTATE(opaque);
> +    uint8_t *data = NULL;
> +    int ret = -1;
> +    char id[256];
> +    unsigned int size;

Higher level question; do you actually need a separate ID, or does
the ID that's stored by the migration code in the section name
give you enough uniqueness?

> +    if (qemu_get_counted_string(f, id) == 0) {
> +        error_report("Invalid vmstate Id");
> +        goto end;

I generally prefer to include something telling me where the error has
come from, just to make it easier to track down if I see the error in
a log; e.g. use __func__

> +    }
> +
> +    if (g_strcmp0(id, self->id)) {
> +        error_report("Invalid vmstate Id: %s != %s", id, self->id);
> +        goto end;
> +    }
> +
> +    size = qemu_get_be32(f);
> +    if (size > DBUS_VMSTATE_SIZE_LIMIT) {
> +        error_report("Invalid vmstate size: %u", size);
> +        goto end;
> +    }
> +
> +    data = g_malloc(size);
> +    if (qemu_get_buffer(f, data, size) != size) {
> +        error_report("Failed to read %u bytes", size);
> +        goto end;
> +    }
> +
> +    if (dbus_load_state_proxy(self->proxy, data, size) < 0) {
> +        error_report("Failed to restore Id '%s'", id);
> +        goto end;
> +    }
> +
> +    ret = 0;
> +
> +end:
> +    g_clear_pointer(&data, g_free);
> +    return ret;
> +}
> +
> +static void
> +dbus_save_state(QEMUFile *f, void *opaque)
> +{
> +    DBusVMState *self = DBUS_VMSTATE(opaque);
> +    GVariant *result = NULL, *child = NULL;
> +    const uint8_t *data;
> +    size_t size;
> +    GError *err = NULL;
> +
> +    result = g_dbus_proxy_call_sync(self->proxy, "Save",
> +                                    NULL, G_DBUS_CALL_FLAGS_NO_AUTO_START,
> +                                    -1, NULL, &err);
> +    if (!result) {
> +        error_report("Failed to Save: %s", err->message);
> +        g_clear_error(&err);
> +        goto end;
> +    }
> +
> +    child = g_variant_get_child_value(result, 0);
> +    data = g_variant_get_fixed_array(child, &size, sizeof(char));
> +    if (!data) {
> +        error_report("Failed to Save: not a byte array");
> +        goto end;
> +    }
> +    if (size > DBUS_VMSTATE_SIZE_LIMIT) {
> +        error_report("Too much vmstate data to save: %zu", size);
> +        goto end;
> +    }
> +
> +    qemu_put_counted_string(f, self->id);
> +    qemu_put_be32(f, size);
> +    qemu_put_buffer(f, data, size);
> +
> +end:
> +    g_clear_pointer(&child, g_variant_unref);
> +    g_clear_pointer(&result, g_variant_unref);
> +}
> +
> +static const SaveVMHandlers savevm_handlers = {
> +    .save_state = dbus_save_state,
> +    .load_state = dbus_load_state,
> +};
> +
> +static void
> +dbus_vmstate_complete(UserCreatable *uc, Error **errp)
> +{
> +    DBusVMState *self = DBUS_VMSTATE(uc);
> +    GError *err = NULL;
> +    GDBusConnection *bus = NULL;
> +    GDBusProxy *proxy = NULL;
> +    char *idstr = NULL;
> +
> +    if (!self->dbus_addr) {
> +        error_setg(errp, QERR_MISSING_PARAMETER, "addr");
> +        return;
> +    }
> +
> +    bus = g_dbus_connection_new_for_address_sync(self->dbus_addr,
> +               G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT,
> +               NULL, NULL, &err);
> +    if (err) {
> +        error_setg(errp, "failed to connect to DBus: '%s'", err->message);
> +        g_clear_error(&err);
> +        return;
> +    }
> +
> +    self->bus = bus;
> +
> +    proxy = g_dbus_proxy_new_sync(bus,
> +                G_DBUS_PROXY_FLAGS_NONE,
> +                (GDBusInterfaceInfo *) &vmstate1_interface_info,
> +                NULL,
> +                "/org/qemu/VMState1",
> +                "org.qemu.VMState1",
> +                NULL, &err);
> +
> +    if (err) {
> +        error_setg(errp, "failed to create DBus proxy: '%s'", err->message);
> +        g_clear_error(&err);
> +        return;
> +    }
> +
> +    self->proxy = proxy;
> +
> +    self->id = dbus_proxy_get_id(proxy, &err);
> +    if (!self->id) {
> +        error_setg(errp, "failed to get DBus Id: '%s'", err->message);
> +        g_clear_error(&err);
> +        return;
> +    }
> +
> +    idstr = get_idstr(self);
> +    if (register_savevm_live(NULL, idstr, 0, 0,
> +                             &savevm_handlers, self) < 0) {
> +        error_setg(errp, "Failed to register savevm handler");
> +    }

Can you try and avoid register_savevm_live if possible and just wire a
vmsd onto the class like most other devices? We don't have many
register_savevm_live calls, and I'd like to get them down to just the
iterative devices.

Dave


> +    g_free(idstr);
> +}
> +
> +static void
> +dbus_vmstate_finalize(Object *o)
> +{
> +    DBusVMState *self = DBUS_VMSTATE(o);
> +    char *idstr = get_idstr(self);
> +
> +    unregister_savevm(NULL, idstr, self);
> +
> +    g_clear_object(&self->bus);
> +    g_clear_object(&self->proxy);
> +    g_free(self->dbus_addr);
> +    g_free(self->id);
> +    g_free(idstr);
> +}
> +
> +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 void
> +dbus_vmstate_class_init(ObjectClass *oc, void *data)
> +{
> +    UserCreatableClass *ucc = USER_CREATABLE_CLASS(oc);
> +
> +    ucc->complete = dbus_vmstate_complete;
> +
> +    object_class_property_add_str(oc, "addr",
> +                                  get_dbus_addr, set_dbus_addr,
> +                                  &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 },
> +        { }
> +    }
> +};
> +
> +static void
> +register_types(void)
> +{
> +    type_register_static(&dbus_vmstate_info);
> +}
> +
> +type_init(register_types);
> diff --git a/configure b/configure
> index 714e7fb6a1..e5b34f5ca7 100755
> --- a/configure
> +++ b/configure
> @@ -3665,10 +3665,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
> @@ -6830,6 +6836,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
> diff --git a/docs/interop/dbus-vmstate.rst b/docs/interop/dbus-vmstate.rst
> new file mode 100644
> index 0000000000..4a32a183fb
> --- /dev/null
> +++ b/docs/interop/dbus-vmstate.rst
> @@ -0,0 +1,63 @@
> +============
> +DBus VMState
> +============
> +
> +Introduction
> +============
> +
> +Helper processes may have their state migrated with the help of
> +QEMU "dbus-vmstate" objects.
> +
> +At this point, the connection to the helper is done in DBus
> +peer-to-peer mode (no initial Hello, and no bus name for
> +communication). The helper must be listening to the given address.
> +
> +Helper may save arbitrary data to be transferred in the migration
> +stream and restored/loaded on destination.
> +
> +The data amount to be transferred is limited to 1Mb. The state must be
> +saved quickly (a few seconds maximum). (DBus imposes a time limit on
> +reply anyway, and migration would fail if the data isn't given quickly
> +enough)
> +
> +Interface
> +=========
> +
> +On /org/qemu/VMState1 object path:
> +
> +.. 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 utf8 encoded string that identifies the helper uniquely.
> +Must be <256 bytes.
> +
> +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 load
> +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.
> diff --git a/docs/interop/index.rst b/docs/interop/index.rst
> index b4bfcab417..6bb173cfa6 100644
> --- a/docs/interop/index.rst
> +++ b/docs/interop/index.rst
> @@ -13,6 +13,7 @@ Contents:
>     :maxdepth: 2
>  
>     bitmaps
> +   dbus-vmstate
>     live-block-operations
>     pr-helper
>     vhost-user
> diff --git a/tests/Makefile.include b/tests/Makefile.include
> index fd7fdb8658..2c610086a7 100644
> --- a/tests/Makefile.include
> +++ b/tests/Makefile.include
> @@ -157,7 +157,9 @@ 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)
> -
> +ifneq ($(GDBUS_CODEGEN),)
> +check-qtest-pci-$(CONFIG_GIO) += tests/dbus-vmstate-test$(EXESUF)
> +endif
>  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)
> @@ -618,6 +620,18 @@ 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-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
> @@ -820,6 +834,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/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
> @@ -1172,6 +1187,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
> diff --git a/tests/dbus-vmstate-test.c b/tests/dbus-vmstate-test.c
> new file mode 100644
> index 0000000000..45d44916d2
> --- /dev/null
> +++ b/tests/dbus-vmstate-test.c
> @@ -0,0 +1,371 @@
> +#include "qemu/osdep.h"
> +#include <glib/gstdio.h>
> +#include <gio/gio.h>
> +#include "libqtest.h"
> +#include "qemu-common.h"
> +#include "dbus-vmstate1.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;
> +    GDBusObjectManagerServer *om;
> +    GDBusServer *server;
> +} TestServer;
> +
> +typedef struct Test {
> +    bool migrate_fail;
> +    TestServer srcA;
> +    TestServer dstA;
> +    TestServer srcB;
> +    TestServer dstB;
> +    GMainLoop *loop, *dbus_loop;
> +    QTestState *src_qemu;
> +} Test;
> +
> +GMutex mutex;
> +GCond cond;
> +
> +static gboolean
> +vmstate_load(VMState1 *object, GDBusMethodInvocation *invocation,
> +             const gchar *arg_data, gpointer user_data)
> +{
> +    TestServer *h = user_data;
> +    GVariant *args, *var;
> +    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_variant_unref(var);
> +
> +    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;
> +}
> +
> +static void
> +connection_closed(GDBusConnection *connection,
> +                  gboolean remote_peer_vanished,
> +                  GError *Error,
> +                  gpointer user_data)
> +{
> +    TestServer *h = user_data;
> +
> +    g_clear_object(&h->om);
> +    g_clear_object(&connection);
> +}
> +
> +static GDBusObjectManagerServer *
> +get_omserver(GDBusConnection *conn, gpointer user_data)
> +{
> +    TestServer *h = user_data;
> +    GDBusObjectManagerServer *om;
> +    GDBusObjectSkeleton *sk;
> +    VMState1 *v;
> +
> +    om = g_dbus_object_manager_server_new("/org/qemu");
> +    sk = g_dbus_object_skeleton_new("/org/qemu/VMState1");
> +
> +    v = vmstate1_skeleton_new();
> +    g_object_set(v, "id", h->id->name, NULL);
> +    g_signal_connect(v, "handle-load", G_CALLBACK(vmstate_load), user_data);
> +    g_signal_connect(v, "handle-save", G_CALLBACK(vmstate_save), user_data);
> +
> +    g_dbus_object_skeleton_add_interface(sk, G_DBUS_INTERFACE_SKELETON(v));
> +    g_dbus_object_manager_server_export(om, sk);
> +    g_dbus_object_manager_server_set_connection(om, conn);
> +
> +    g_clear_object(&v);
> +    g_clear_object(&sk);
> +
> +    return om;
> +}
> +
> +static gboolean
> +on_new_connection(GDBusServer *server,
> +                  GDBusConnection *connection,
> +                  gpointer user_data)
> +{
> +    TestServer *h = user_data;
> +
> +    g_object_ref(connection);
> +    g_signal_connect(connection, "closed",
> +                     G_CALLBACK(connection_closed), user_data);
> +    h->om = get_omserver(connection, user_data);
> +
> +    return TRUE;
> +}
> +
> +static gboolean
> +allow_mechanism_cb(GDBusAuthObserver *observer,
> +                   const gchar *mechanism,
> +                   gpointer user_data)
> +{
> +    return g_strcmp0(mechanism, "EXTERNAL") == 0;
> +}
> +
> +static gboolean
> +authorize_authenticated_peer_cb(GDBusAuthObserver *observer,
> +                                GIOStream *stream,
> +                                GCredentials *credentials,
> +                                gpointer user_data)
> +{
> +    gboolean authorized = FALSE;
> +
> +    if (credentials != NULL) {
> +        GCredentials *own_credentials = g_credentials_new();
> +
> +        if (g_credentials_is_same_user(credentials, own_credentials, NULL)) {
> +            authorized = TRUE;
> +        }
> +
> +        g_clear_object(&own_credentials);
> +    }
> +
> +    return authorized;
> +}
> +
> +static GDBusServer *
> +server_start(TestServer *h, const char *p, const TestServerId *id)
> +{
> +    GDBusAuthObserver *observer = NULL;
> +    GDBusServer *server = NULL;
> +    gchar *guid = NULL;
> +    GError *error = NULL;
> +    char *addr = NULL;
> +
> +    h->id = id;
> +    addr = g_strdup_printf("unix:path=%s/dbus-%s%s", workdir, p, h->id->name);
> +    guid = g_dbus_generate_guid();
> +    observer = g_dbus_auth_observer_new();
> +    g_signal_connect(observer, "allow-mechanism",
> +                     G_CALLBACK(allow_mechanism_cb), h);
> +    g_signal_connect(observer, "authorize-authenticated-peer",
> +                     G_CALLBACK(authorize_authenticated_peer_cb), h);
> +
> +    server = g_dbus_server_new_sync(addr,
> +                                    G_DBUS_SERVER_FLAGS_NONE,
> +                                    guid,
> +                                    observer,
> +                                    NULL, /* GCancellable */
> +                                    &error);
> +    g_dbus_server_start(server);
> +    g_clear_object(&observer);
> +    g_free(guid);
> +
> +    if (server == NULL) {
> +        g_printerr("Error creating server at address %s: %s\n",
> +                   addr, error->message);
> +        g_error_free(error);
> +        return NULL;
> +    }
> +
> +    g_signal_connect(server, "new-connection",
> +                     G_CALLBACK(on_new_connection), h);
> +
> +    g_free(addr);
> +    return server;
> +}
> +
> +
> +static gpointer
> +dbus_thread(gpointer p)
> +{
> +    Test *test = p;
> +    GMainContext *context = g_main_context_new();
> +    GMainLoop *loop = g_main_loop_new(context, FALSE);
> +
> +    g_main_context_push_thread_default(context);
> +
> +    g_mutex_lock(&mutex);
> +    test->srcA.server = server_start(&test->srcA, "src", &idA);
> +    test->srcB.server = server_start(&test->srcB, "src", &idB);
> +    test->dstA.server = server_start(&test->dstA, "dst", &idA);
> +    test->dstB.server = server_start(&test->dstB, "dst", &idB);
> +    test->dbus_loop = loop;
> +    g_cond_signal(&cond);
> +    g_mutex_unlock(&mutex);
> +
> +    g_main_loop_run(loop);
> +
> +    g_main_loop_unref(loop);
> +    g_main_context_unref(context);
> +
> +    g_mutex_lock(&mutex);
> +    g_clear_object(&test->srcA.server);
> +    g_clear_object(&test->srcB.server);
> +    g_clear_object(&test->dstA.server);
> +    g_clear_object(&test->dstB.server);
> +    g_mutex_unlock(&mutex);
> +
> +    return NULL;
> +}
> +
> +static gboolean
> +wait_for_migration_complete(gpointer user_data)
> +{
> +    Test *test = user_data;
> +    QDict *rsp_return;
> +    bool stop = false;
> +    const char *status;
> +
> +    qtest_qmp_send(test->src_qemu, "{ 'execute': 'query-migrate' }");
> +    rsp_return = qtest_qmp_receive_success(test->src_qemu, NULL, NULL);
> +    status = qdict_get_str(rsp_return, "status");
> +    if (g_str_equal(status, "completed") || g_str_equal(status, "failed")) {
> +        stop = true;
> +        g_assert_cmpstr(status, ==,
> +                        test->migrate_fail ? "failed" : "completed");
> +    }
> +    qobject_unref(rsp_return);
> +
> +    if (stop) {
> +        g_main_loop_quit(test->loop);
> +    }
> +    return stop ? G_SOURCE_REMOVE : G_SOURCE_CONTINUE;
> +}
> +
> +static void migrate(QTestState *who, const char *uri)
> +{
> +    QDict *args, *rsp;
> +
> +    args = qdict_new();
> +    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
> +test_dbus_vmstate(Test *test)
> +{
> +    QTestState *src_qemu = NULL, *dst_qemu = NULL;
> +    char *src_qemu_args = NULL, *dst_qemu_args = NULL;
> +    char *uri = g_strdup_printf("unix:%s/migsocket", workdir);
> +    GThread *t = g_thread_new("dbus", dbus_thread, test);
> +
> +    g_mutex_lock(&mutex);
> +    while (!test->dbus_loop) {
> +        g_cond_wait(&cond, &mutex);
> +    }
> +
> +    src_qemu_args =
> +        g_strdup_printf("-object dbus-vmstate,id=dvA,addr=%s "
> +                        "-object dbus-vmstate,id=dvB,addr=%s",
> +                        g_dbus_server_get_client_address(test->srcA.server),
> +                        g_dbus_server_get_client_address(test->srcB.server));
> +
> +
> +    dst_qemu_args =
> +        g_strdup_printf("-object dbus-vmstate,id=dvA,addr=%s "
> +                        "-object dbus-vmstate,id=dvB,addr=%s "
> +                        "-incoming %s",
> +                        g_dbus_server_get_client_address(test->dstA.server),
> +                        g_dbus_server_get_client_address(test->dstB.server),
> +                        uri);
> +
> +    src_qemu = qtest_init(src_qemu_args);
> +    dst_qemu = qtest_init(dst_qemu_args);
> +
> +    test->loop = g_main_loop_new(NULL, TRUE);
> +
> +    migrate(src_qemu, uri);
> +    test->src_qemu = src_qemu;
> +    g_timeout_add_seconds(1, wait_for_migration_complete, test);
> +
> +    g_main_loop_run(test->loop);
> +    g_main_loop_unref(test->loop);
> +
> +    g_free(uri);
> +    qtest_quit(dst_qemu);
> +    qtest_quit(src_qemu);
> +    g_free(dst_qemu_args);
> +    g_free(src_qemu_args);
> +
> +    g_main_loop_quit(test->dbus_loop);
> +    g_mutex_unlock(&mutex);
> +
> +    g_thread_join(t);
> +}
> +
> +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_migrate(void)
> +{
> +    Test test = { };
> +
> +    test_dbus_vmstate(&test);
> +
> +    check_migrated(&test.srcA, &test.dstA);
> +    check_migrated(&test.srcB, &test.dstB);
> +}
> +
> +int
> +main(int argc, char **argv)
> +{
> +    GError *err = NULL;
> +    int ret;
> +
> +    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);
> +    }
> +
> +    qtest_add_func("/dbus-vmstate/migrate",
> +                   test_dbus_vmstate_migrate);
> +
> +    ret = g_test_run();
> +
> +    rmdir(workdir);
> +    g_free(workdir);
> +
> +    return ret;
> +}
> diff --git a/tests/dbus-vmstate1.xml b/tests/dbus-vmstate1.xml
> new file mode 100644
> index 0000000000..cc8563be4c
> --- /dev/null
> +++ b/tests/dbus-vmstate1.xml
> @@ -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>
> -- 
> 2.23.0.rc1
> 
--
Dr. David Alan Gilbert / dgilbert@redhat.com / Manchester, UK


  parent reply	other threads:[~2019-08-22 10:59 UTC|newest]

Thread overview: 28+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2019-08-08 15:03 [Qemu-devel] [PATCH v2 0/2] Add dbus-vmstate Marc-André Lureau
2019-08-08 15:03 ` [Qemu-devel] [PATCH v2 1/2] qemu-file: move qemu_{get, put}_counted_string() declarations Marc-André Lureau
2019-08-09 18:32   ` Dr. David Alan Gilbert
2019-08-08 15:03 ` [Qemu-devel] [PATCH v2 2/2] Add dbus-vmstate object Marc-André Lureau
2019-08-08 15:07   ` Marc-André Lureau
2019-08-22 10:55   ` Dr. David Alan Gilbert [this message]
2019-08-22 11:35     ` Marc-André Lureau
2019-08-22 11:41       ` Dr. David Alan Gilbert
2019-08-22 11:57         ` Marc-André Lureau
2019-08-22 12:19           ` Dr. David Alan Gilbert
2019-08-22 12:38             ` Marc-André Lureau
2019-08-22 12:51               ` Dr. David Alan Gilbert
2019-08-23 11:20 ` [Qemu-devel] [PATCH v2 0/2] Add dbus-vmstate Daniel P. Berrangé
2019-08-23 11:31   ` Marc-André Lureau
2019-08-23 11:41     ` Daniel P. Berrangé
2019-08-23 11:47       ` Marc-André Lureau
2019-08-23 13:00       ` Dr. David Alan Gilbert
2019-08-23 13:48         ` Marc-André Lureau
2019-08-23 14:09           ` Daniel P. Berrangé
2019-08-23 14:09           ` Dr. David Alan Gilbert
2019-08-23 14:20             ` Daniel P. Berrangé
2019-08-23 14:26               ` Dr. David Alan Gilbert
2019-08-23 14:40                 ` Daniel P. Berrangé
2019-08-23 14:56                   ` Dr. David Alan Gilbert
2019-08-23 15:05                     ` Daniel P. Berrangé
2019-08-23 15:14                       ` Dr. David Alan Gilbert
2019-08-23 15:21                         ` Daniel P. Berrangé
2019-08-23 15:24                           ` Dr. David Alan Gilbert

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20190822105558.GF3277@work-vm \
    --to=dgilbert@redhat.com \
    --cc=lvivier@redhat.com \
    --cc=marcandre.lureau@redhat.com \
    --cc=pbonzini@redhat.com \
    --cc=qemu-devel@nongnu.org \
    --cc=quintela@redhat.com \
    --cc=thuth@redhat.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.