 19e566162c
			
		
	
	
		19e566162c
		
	
	
	
	
		
			
			Select all the code and hit tab. I'll be moving functions around quite a lot in the next patches, so make sure all indentation is correct now. Add parentheses around some expressions to preserve readability. Reviewed-by: Daniel P. Berrangé <berrange@redhat.com> Signed-off-by: Fabiano Rosas <farosas@suse.de> Message-ID: <20241113194630.3385-2-farosas@suse.de> Signed-off-by: Philippe Mathieu-Daudé <philmd@linaro.org>
		
			
				
	
	
		
			531 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			531 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*
 | |
|  * 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 "qemu/ctype.h"
 | |
| #include "qapi/qmp/qjson.h"
 | |
| #include "qapi/qapi-visit-sockets.h"
 | |
| #include "qapi/qobject-input-visitor.h"
 | |
| #include "qapi/error.h"
 | |
| #include "qapi/qmp/qlist.h"
 | |
| #include "qemu/cutils.h"
 | |
| #include "qemu/memalign.h"
 | |
| 
 | |
| #include "migration-helpers.h"
 | |
| 
 | |
| /*
 | |
|  * Number of seconds we wait when looking for migration
 | |
|  * status changes, to avoid test suite hanging forever
 | |
|  * when things go wrong. Needs to be higher enough to
 | |
|  * avoid false positives on loaded hosts.
 | |
|  */
 | |
| #define MIGRATION_STATUS_WAIT_TIMEOUT 120
 | |
| 
 | |
| static char *SocketAddress_to_str(SocketAddress *addr)
 | |
| {
 | |
|     switch (addr->type) {
 | |
|     case SOCKET_ADDRESS_TYPE_INET:
 | |
|         return g_strdup_printf("tcp:%s:%s",
 | |
|                                addr->u.inet.host,
 | |
|                                addr->u.inet.port);
 | |
|     case SOCKET_ADDRESS_TYPE_UNIX:
 | |
|         return g_strdup_printf("unix:%s",
 | |
|                                addr->u.q_unix.path);
 | |
|     case SOCKET_ADDRESS_TYPE_FD:
 | |
|         return g_strdup_printf("fd:%s", addr->u.fd.str);
 | |
|     case SOCKET_ADDRESS_TYPE_VSOCK:
 | |
|         return g_strdup_printf("vsock:%s:%s",
 | |
|                                addr->u.vsock.cid,
 | |
|                                addr->u.vsock.port);
 | |
|     default:
 | |
|         return g_strdup("unknown address type");
 | |
|     }
 | |
| }
 | |
| 
 | |
| static QDict *SocketAddress_to_qdict(SocketAddress *addr)
 | |
| {
 | |
|     QDict *dict = qdict_new();
 | |
| 
 | |
|     switch (addr->type) {
 | |
|     case SOCKET_ADDRESS_TYPE_INET:
 | |
|         qdict_put_str(dict, "type", "inet");
 | |
|         qdict_put_str(dict, "host", addr->u.inet.host);
 | |
|         qdict_put_str(dict, "port", addr->u.inet.port);
 | |
|         break;
 | |
|     case SOCKET_ADDRESS_TYPE_UNIX:
 | |
|         qdict_put_str(dict, "type", "unix");
 | |
|         qdict_put_str(dict, "path", addr->u.q_unix.path);
 | |
|         break;
 | |
|     case SOCKET_ADDRESS_TYPE_FD:
 | |
|         qdict_put_str(dict, "type", "fd");
 | |
|         qdict_put_str(dict, "str", addr->u.fd.str);
 | |
|         break;
 | |
|     case SOCKET_ADDRESS_TYPE_VSOCK:
 | |
|         qdict_put_str(dict, "type", "vsock");
 | |
|         qdict_put_str(dict, "cid", addr->u.vsock.cid);
 | |
|         qdict_put_str(dict, "port", addr->u.vsock.port);
 | |
|         break;
 | |
|     default:
 | |
|         g_assert_not_reached();
 | |
|     }
 | |
| 
 | |
|     return dict;
 | |
| }
 | |
| 
 | |
| static SocketAddressList *migrate_get_socket_address(QTestState *who)
 | |
| {
 | |
|     QDict *rsp;
 | |
|     SocketAddressList *addrs;
 | |
|     Visitor *iv = NULL;
 | |
|     QObject *object;
 | |
| 
 | |
|     rsp = migrate_query(who);
 | |
|     object = qdict_get(rsp, "socket-address");
 | |
| 
 | |
|     iv = qobject_input_visitor_new(object);
 | |
|     visit_type_SocketAddressList(iv, NULL, &addrs, &error_abort);
 | |
|     visit_free(iv);
 | |
| 
 | |
|     qobject_unref(rsp);
 | |
|     return addrs;
 | |
| }
 | |
| 
 | |
| static char *
 | |
| migrate_get_connect_uri(QTestState *who)
 | |
| {
 | |
|     SocketAddressList *addrs;
 | |
|     char *connect_uri;
 | |
| 
 | |
|     addrs = migrate_get_socket_address(who);
 | |
|     connect_uri = SocketAddress_to_str(addrs->value);
 | |
| 
 | |
|     qapi_free_SocketAddressList(addrs);
 | |
|     return connect_uri;
 | |
| }
 | |
| 
 | |
| static QDict *
 | |
| migrate_get_connect_qdict(QTestState *who)
 | |
| {
 | |
|     SocketAddressList *addrs;
 | |
|     QDict *connect_qdict;
 | |
| 
 | |
|     addrs = migrate_get_socket_address(who);
 | |
|     connect_qdict = SocketAddress_to_qdict(addrs->value);
 | |
| 
 | |
|     qapi_free_SocketAddressList(addrs);
 | |
|     return connect_qdict;
 | |
| }
 | |
| 
 | |
| static void migrate_set_ports(QTestState *to, QList *channel_list)
 | |
| {
 | |
|     QDict *addr;
 | |
|     QListEntry *entry;
 | |
|     const char *addr_port = NULL;
 | |
| 
 | |
|     addr = migrate_get_connect_qdict(to);
 | |
| 
 | |
|     QLIST_FOREACH_ENTRY(channel_list, entry) {
 | |
|         QDict *channel = qobject_to(QDict, qlist_entry_obj(entry));
 | |
|         QDict *addrdict = qdict_get_qdict(channel, "addr");
 | |
| 
 | |
|         if (qdict_haskey(addrdict, "port") &&
 | |
|             qdict_haskey(addr, "port") &&
 | |
|             (strcmp(qdict_get_str(addrdict, "port"), "0") == 0)) {
 | |
|             addr_port = qdict_get_str(addr, "port");
 | |
|             qdict_put_str(addrdict, "port", addr_port);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     qobject_unref(addr);
 | |
| }
 | |
| 
 | |
| bool migrate_watch_for_events(QTestState *who, const char *name,
 | |
|                               QDict *event, void *opaque)
 | |
| {
 | |
|     QTestMigrationState *state = opaque;
 | |
| 
 | |
|     if (g_str_equal(name, "STOP")) {
 | |
|         state->stop_seen = true;
 | |
|         return true;
 | |
|     } else if (g_str_equal(name, "SUSPEND")) {
 | |
|         state->suspend_seen = true;
 | |
|         return true;
 | |
|     } else if (g_str_equal(name, "RESUME")) {
 | |
|         state->resume_seen = true;
 | |
|         return true;
 | |
|     }
 | |
| 
 | |
|     return false;
 | |
| }
 | |
| 
 | |
| void migrate_qmp_fail(QTestState *who, const char *uri,
 | |
|                       const char *channels, const char *fmt, ...)
 | |
| {
 | |
|     va_list ap;
 | |
|     QDict *args, *err;
 | |
| 
 | |
|     va_start(ap, fmt);
 | |
|     args = qdict_from_vjsonf_nofail(fmt, ap);
 | |
|     va_end(ap);
 | |
| 
 | |
|     g_assert(!qdict_haskey(args, "uri"));
 | |
|     if (uri) {
 | |
|         qdict_put_str(args, "uri", uri);
 | |
|     }
 | |
| 
 | |
|     g_assert(!qdict_haskey(args, "channels"));
 | |
|     if (channels) {
 | |
|         QObject *channels_obj = qobject_from_json(channels, &error_abort);
 | |
|         qdict_put_obj(args, "channels", channels_obj);
 | |
|     }
 | |
| 
 | |
|     err = qtest_qmp_assert_failure_ref(
 | |
|         who, "{ 'execute': 'migrate', 'arguments': %p}", args);
 | |
| 
 | |
|     g_assert(qdict_haskey(err, "desc"));
 | |
| 
 | |
|     qobject_unref(err);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * 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, QTestState *to, const char *uri,
 | |
|                  const char *channels, const char *fmt, ...)
 | |
| {
 | |
|     va_list ap;
 | |
|     QDict *args;
 | |
|     g_autofree char *connect_uri = NULL;
 | |
| 
 | |
|     va_start(ap, fmt);
 | |
|     args = qdict_from_vjsonf_nofail(fmt, ap);
 | |
|     va_end(ap);
 | |
| 
 | |
|     g_assert(!qdict_haskey(args, "uri"));
 | |
|     if (uri) {
 | |
|         qdict_put_str(args, "uri", uri);
 | |
|     } else if (!channels) {
 | |
|         connect_uri = migrate_get_connect_uri(to);
 | |
|         qdict_put_str(args, "uri", connect_uri);
 | |
|     }
 | |
| 
 | |
|     g_assert(!qdict_haskey(args, "channels"));
 | |
|     if (channels) {
 | |
|         QObject *channels_obj = qobject_from_json(channels, &error_abort);
 | |
|         QList *channel_list = qobject_to(QList, channels_obj);
 | |
|         migrate_set_ports(to, channel_list);
 | |
|         qdict_put_obj(args, "channels", channels_obj);
 | |
|     }
 | |
| 
 | |
|     qtest_qmp_assert_success(who,
 | |
|                              "{ 'execute': 'migrate', 'arguments': %p}", args);
 | |
| }
 | |
| 
 | |
| void migrate_set_capability(QTestState *who, const char *capability,
 | |
|                             bool value)
 | |
| {
 | |
|     qtest_qmp_assert_success(who,
 | |
|                              "{ 'execute': 'migrate-set-capabilities',"
 | |
|                              "'arguments': { "
 | |
|                              "'capabilities': [ { "
 | |
|                              "'capability': %s, 'state': %i } ] } }",
 | |
|                              capability, value);
 | |
| }
 | |
| 
 | |
| void migrate_incoming_qmp(QTestState *to, 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);
 | |
| 
 | |
|     /* This function relies on the event to work, make sure it's enabled */
 | |
|     migrate_set_capability(to, "events", true);
 | |
| 
 | |
|     rsp = qtest_qmp(to, "{ 'execute': 'migrate-incoming', 'arguments': %p}",
 | |
|                     args);
 | |
| 
 | |
|     if (!qdict_haskey(rsp, "return")) {
 | |
|         g_autoptr(GString) s = qobject_to_json_pretty(QOBJECT(rsp), true);
 | |
|         g_test_message("%s", s->str);
 | |
|     }
 | |
| 
 | |
|     g_assert(qdict_haskey(rsp, "return"));
 | |
|     qobject_unref(rsp);
 | |
| 
 | |
|     migration_event_wait(to, "setup");
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Note: caller is responsible to free the returned object via
 | |
|  * qobject_unref() after use
 | |
|  */
 | |
| QDict *migrate_query(QTestState *who)
 | |
| {
 | |
|     return qtest_qmp_assert_success_ref(who, "{ 'execute': 'query-migrate' }");
 | |
| }
 | |
| 
 | |
| QDict *migrate_query_not_failed(QTestState *who)
 | |
| {
 | |
|     const char *status;
 | |
|     QDict *rsp = migrate_query(who);
 | |
|     status = qdict_get_str(rsp, "status");
 | |
|     if (g_str_equal(status, "failed")) {
 | |
|         g_printerr("query-migrate shows failed migration: %s\n",
 | |
|                    qdict_get_str(rsp, "error-desc"));
 | |
|     }
 | |
|     g_assert(!g_str_equal(status, "failed"));
 | |
|     return rsp;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * 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)
 | |
| {
 | |
|     g_test_timer_start();
 | |
|     while (!check_migration_status(who, goal, ungoals)) {
 | |
|         usleep(1000);
 | |
| 
 | |
|         g_assert(g_test_timer_elapsed() < MIGRATION_STATUS_WAIT_TIMEOUT);
 | |
|     }
 | |
| }
 | |
| 
 | |
| void wait_for_migration_complete(QTestState *who)
 | |
| {
 | |
|     wait_for_migration_status(who, "completed", NULL);
 | |
| }
 | |
| 
 | |
| void wait_for_migration_fail(QTestState *from, bool allow_active)
 | |
| {
 | |
|     g_test_timer_start();
 | |
|     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);
 | |
| 
 | |
|         g_assert(g_test_timer_elapsed() < MIGRATION_STATUS_WAIT_TIMEOUT);
 | |
|     } while (!failed);
 | |
| 
 | |
|     /* Is the machine currently running? */
 | |
|     rsp_return = qtest_qmp_assert_success_ref(from,
 | |
|                                               "{ 'execute': 'query-status' }");
 | |
|     g_assert(qdict_haskey(rsp_return, "running"));
 | |
|     g_assert(qdict_get_bool(rsp_return, "running"));
 | |
|     qobject_unref(rsp_return);
 | |
| }
 | |
| 
 | |
| char *find_common_machine_version(const char *mtype, const char *var1,
 | |
|                                   const char *var2)
 | |
| {
 | |
|     g_autofree char *type1 = qtest_resolve_machine_alias(var1, mtype);
 | |
|     g_autofree char *type2 = qtest_resolve_machine_alias(var2, mtype);
 | |
| 
 | |
|     g_assert(type1 && type2);
 | |
| 
 | |
|     if (g_str_equal(type1, type2)) {
 | |
|         /* either can be used */
 | |
|         return g_strdup(type1);
 | |
|     }
 | |
| 
 | |
|     if (qtest_has_machine_with_env(var2, type1)) {
 | |
|         return g_strdup(type1);
 | |
|     }
 | |
| 
 | |
|     if (qtest_has_machine_with_env(var1, type2)) {
 | |
|         return g_strdup(type2);
 | |
|     }
 | |
| 
 | |
|     g_test_message("No common machine version for machine type '%s' between "
 | |
|                    "binaries %s and %s", mtype, getenv(var1), getenv(var2));
 | |
|     g_assert_not_reached();
 | |
| }
 | |
| 
 | |
| char *resolve_machine_version(const char *alias, const char *var1,
 | |
|                               const char *var2)
 | |
| {
 | |
|     const char *mname = g_getenv("QTEST_QEMU_MACHINE_TYPE");
 | |
|     g_autofree char *machine_name = NULL;
 | |
| 
 | |
|     if (mname) {
 | |
|         const char *dash = strrchr(mname, '-');
 | |
|         const char *dot = strrchr(mname, '.');
 | |
| 
 | |
|         machine_name = g_strdup(mname);
 | |
| 
 | |
|         if (dash && dot) {
 | |
|             assert(qtest_has_machine(machine_name));
 | |
|             return g_steal_pointer(&machine_name);
 | |
|         }
 | |
|         /* else: probably an alias, let it be resolved below */
 | |
|     } else {
 | |
|         /* use the hardcoded alias */
 | |
|         machine_name = g_strdup(alias);
 | |
|     }
 | |
| 
 | |
|     return find_common_machine_version(machine_name, var1, var2);
 | |
| }
 | |
| 
 | |
| typedef struct {
 | |
|     char *name;
 | |
|     void (*func)(void);
 | |
| } MigrationTest;
 | |
| 
 | |
| static void migration_test_destroy(gpointer data)
 | |
| {
 | |
|     MigrationTest *test = (MigrationTest *)data;
 | |
| 
 | |
|     g_free(test->name);
 | |
|     g_free(test);
 | |
| }
 | |
| 
 | |
| static void migration_test_wrapper(const void *data)
 | |
| {
 | |
|     MigrationTest *test = (MigrationTest *)data;
 | |
| 
 | |
|     g_test_message("Running /%s%s", qtest_get_arch(), test->name);
 | |
|     test->func();
 | |
| }
 | |
| 
 | |
| void migration_test_add(const char *path, void (*fn)(void))
 | |
| {
 | |
|     MigrationTest *test = g_new0(MigrationTest, 1);
 | |
| 
 | |
|     test->func = fn;
 | |
|     test->name = g_strdup(path);
 | |
| 
 | |
|     qtest_add_data_func_full(path, test, migration_test_wrapper,
 | |
|                              migration_test_destroy);
 | |
| }
 | |
| 
 | |
| #ifdef O_DIRECT
 | |
| /*
 | |
|  * Probe for O_DIRECT support on the filesystem. Since this is used
 | |
|  * for tests, be conservative, if anything fails, assume it's
 | |
|  * unsupported.
 | |
|  */
 | |
| bool probe_o_direct_support(const char *tmpfs)
 | |
| {
 | |
|     g_autofree char *filename = g_strdup_printf("%s/probe-o-direct", tmpfs);
 | |
|     int fd, flags = O_CREAT | O_RDWR | O_TRUNC | O_DIRECT;
 | |
|     void *buf;
 | |
|     ssize_t ret, len;
 | |
|     uint64_t offset;
 | |
| 
 | |
|     fd = open(filename, flags, 0660);
 | |
|     if (fd < 0) {
 | |
|         unlink(filename);
 | |
|         return false;
 | |
|     }
 | |
| 
 | |
|     /*
 | |
|      * Using 1MB alignment as conservative choice to satisfy any
 | |
|      * plausible architecture default page size, and/or filesystem
 | |
|      * alignment restrictions.
 | |
|      */
 | |
|     len = 0x100000;
 | |
|     offset = 0x100000;
 | |
| 
 | |
|     buf = qemu_try_memalign(len, len);
 | |
|     g_assert(buf);
 | |
| 
 | |
|     ret = pwrite(fd, buf, len, offset);
 | |
|     unlink(filename);
 | |
|     g_free(buf);
 | |
| 
 | |
|     if (ret < 0) {
 | |
|         return false;
 | |
|     }
 | |
| 
 | |
|     return true;
 | |
| }
 | |
| #endif
 | |
| 
 | |
| /*
 | |
|  * Wait for a "MIGRATION" event.  This is what Libvirt uses to track
 | |
|  * migration status changes.
 | |
|  */
 | |
| void migration_event_wait(QTestState *s, const char *target)
 | |
| {
 | |
|     QDict *response, *data;
 | |
|     const char *status;
 | |
|     bool found;
 | |
| 
 | |
|     do {
 | |
|         response = qtest_qmp_eventwait_ref(s, "MIGRATION");
 | |
|         data = qdict_get_qdict(response, "data");
 | |
|         g_assert(data);
 | |
|         status = qdict_get_str(data, "status");
 | |
|         found = (strcmp(status, target) == 0);
 | |
|         qobject_unref(response);
 | |
|     } while (!found);
 | |
| }
 |