From 55fc0c2f68ec81cc51f39964cf6d1bf3f7467a4f Mon Sep 17 00:00:00 2001 From: Fabiano Rosas Date: Mon, 17 Jun 2024 15:57:18 -0300 Subject: tests/qtest/migration: Fix file migration offset check MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When doing file migration, QEMU accepts an offset that should be skipped when writing the migration stream to the file. The purpose of the offset is to allow the management layer to put its own metadata at the start of the file. We have tests for this in migration-test, but only testing that the migration stream starts at the correct offset and not that it actually leaves the data intact. Unsurprisingly, there's been a bug in that area that the tests didn't catch. Fix the tests to write some data to the offset region and check that it's actually there after the migration. While here, switch to using g_get_file_contents() which is more portable than mmap(). Reviewed-by: Peter Xu Reviewed-by: Daniel P. Berrangé Signed-off-by: Fabiano Rosas --- tests/qtest/migration-test.c | 79 +++++++++++++++++++++++++++----------------- 1 file changed, 48 insertions(+), 31 deletions(-) (limited to 'tests/qtest/migration-test.c') diff --git a/tests/qtest/migration-test.c b/tests/qtest/migration-test.c index 0dccb4beff..0a529a527b 100644 --- a/tests/qtest/migration-test.c +++ b/tests/qtest/migration-test.c @@ -68,6 +68,7 @@ static QTestMigrationState dst_state; #define QEMU_VM_FILE_MAGIC 0x5145564d #define FILE_TEST_FILENAME "migfile" #define FILE_TEST_OFFSET 0x1000 +#define FILE_TEST_MARKER 'X' #define QEMU_ENV_SRC "QTEST_QEMU_BINARY_SRC" #define QEMU_ENV_DST "QTEST_QEMU_BINARY_DST" @@ -1693,10 +1694,43 @@ finish: test_migrate_end(from, to, args->result == MIG_TEST_SUCCEED); } +static void file_dirty_offset_region(void) +{ + g_autofree char *path = g_strdup_printf("%s/%s", tmpfs, FILE_TEST_FILENAME); + size_t size = FILE_TEST_OFFSET; + g_autofree char *data = g_new0(char, size); + + memset(data, FILE_TEST_MARKER, size); + g_assert(g_file_set_contents(path, data, size, NULL)); +} + +static void file_check_offset_region(void) +{ + g_autofree char *path = g_strdup_printf("%s/%s", tmpfs, FILE_TEST_FILENAME); + size_t size = FILE_TEST_OFFSET; + g_autofree char *expected = g_new0(char, size); + g_autofree char *actual = NULL; + uint64_t *stream_start; + + /* + * Ensure the skipped offset region's data has not been touched + * and the migration stream starts at the right place. + */ + + memset(expected, FILE_TEST_MARKER, size); + + g_assert(g_file_get_contents(path, &actual, NULL, NULL)); + g_assert(!memcmp(actual, expected, size)); + + stream_start = (uint64_t *)(actual + size); + g_assert_cmpint(cpu_to_be64(*stream_start) >> 32, ==, QEMU_VM_FILE_MAGIC); +} + static void test_file_common(MigrateCommon *args, bool stop_src) { QTestState *from, *to; void *data_hook = NULL; + bool check_offset = false; if (test_migrate_start(&from, &to, args->listen_uri, &args->start)) { return; @@ -1709,6 +1743,16 @@ static void test_file_common(MigrateCommon *args, bool stop_src) */ g_assert_false(args->live); + if (g_strrstr(args->connect_uri, "offset=")) { + check_offset = true; + /* + * This comes before the start_hook because it's equivalent to + * a management application creating the file and writing to + * it so hooks should expect the file to be already present. + */ + file_dirty_offset_region(); + } + if (args->start_hook) { data_hook = args->start_hook(from, to); } @@ -1743,6 +1787,10 @@ static void test_file_common(MigrateCommon *args, bool stop_src) wait_for_serial("dest_serial"); + if (check_offset) { + file_check_offset_region(); + } + finish: if (args->finish_hook) { args->finish_hook(from, to, data_hook); @@ -1942,36 +1990,6 @@ static void test_precopy_file(void) test_file_common(&args, true); } -static void file_offset_finish_hook(QTestState *from, QTestState *to, - void *opaque) -{ -#if defined(__linux__) - g_autofree char *path = g_strdup_printf("%s/%s", tmpfs, FILE_TEST_FILENAME); - size_t size = FILE_TEST_OFFSET + sizeof(QEMU_VM_FILE_MAGIC); - uintptr_t *addr, *p; - int fd; - - fd = open(path, O_RDONLY); - g_assert(fd != -1); - addr = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0); - g_assert(addr != MAP_FAILED); - - /* - * Ensure the skipped offset contains zeros and the migration - * stream starts at the right place. - */ - p = addr; - while (p < addr + FILE_TEST_OFFSET / sizeof(uintptr_t)) { - g_assert(*p == 0); - p++; - } - g_assert_cmpint(cpu_to_be64(*p) >> 32, ==, QEMU_VM_FILE_MAGIC); - - munmap(addr, size); - close(fd); -#endif -} - static void test_precopy_file_offset(void) { g_autofree char *uri = g_strdup_printf("file:%s/%s,offset=%d", tmpfs, @@ -1980,7 +1998,6 @@ static void test_precopy_file_offset(void) MigrateCommon args = { .connect_uri = uri, .listen_uri = "defer", - .finish_hook = file_offset_finish_hook, }; test_file_common(&args, false); -- cgit 1.4.1 From 926554c0bfdfbf7b058ed370c2b484e56b126d34 Mon Sep 17 00:00:00 2001 From: Fabiano Rosas Date: Mon, 17 Jun 2024 15:57:19 -0300 Subject: tests/qtest/migration: Add a precopy file test with fdset Add a test for file migration using fdset. The passing of fds is more complex than using a file path. This is also the scenario where it's most important we ensure that the initial migration stream offset is respected because the fdset interface is the one used by the management layer when providing a non empty migration file. Note that fd passing is not available on Windows, so anything that uses add-fd needs to exclude that platform. Reviewed-by: Peter Xu Signed-off-by: Fabiano Rosas --- tests/qtest/migration-test.c | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) (limited to 'tests/qtest/migration-test.c') diff --git a/tests/qtest/migration-test.c b/tests/qtest/migration-test.c index 0a529a527b..22b07bc0ec 100644 --- a/tests/qtest/migration-test.c +++ b/tests/qtest/migration-test.c @@ -1990,6 +1990,46 @@ static void test_precopy_file(void) test_file_common(&args, true); } +#ifndef _WIN32 +static void fdset_add_fds(QTestState *qts, const char *file, int flags, + int num_fds) +{ + for (int i = 0; i < num_fds; i++) { + int fd; + + fd = open(file, flags, 0660); + assert(fd != -1); + + qtest_qmp_fds_assert_success(qts, &fd, 1, "{'execute': 'add-fd', " + "'arguments': {'fdset-id': 1}}"); + close(fd); + } +} + +static void *file_offset_fdset_start_hook(QTestState *from, QTestState *to) +{ + g_autofree char *file = g_strdup_printf("%s/%s", tmpfs, FILE_TEST_FILENAME); + + fdset_add_fds(from, file, O_WRONLY, 1); + fdset_add_fds(to, file, O_RDONLY, 1); + + return NULL; +} + +static void test_precopy_file_offset_fdset(void) +{ + g_autofree char *uri = g_strdup_printf("file:/dev/fdset/1,offset=%d", + FILE_TEST_OFFSET); + MigrateCommon args = { + .connect_uri = uri, + .listen_uri = "defer", + .start_hook = file_offset_fdset_start_hook, + }; + + test_file_common(&args, false); +} +#endif + static void test_precopy_file_offset(void) { g_autofree char *uri = g_strdup_printf("file:%s/%s,offset=%d", tmpfs, @@ -3527,6 +3567,10 @@ int main(int argc, char **argv) test_precopy_file); migration_test_add("/migration/precopy/file/offset", test_precopy_file_offset); +#ifndef _WIN32 + migration_test_add("/migration/precopy/file/offset/fdset", + test_precopy_file_offset_fdset); +#endif migration_test_add("/migration/precopy/file/offset/bad", test_precopy_file_offset_bad); -- cgit 1.4.1 From 87d67fadb9db5e87072c244df794c0755150fd2a Mon Sep 17 00:00:00 2001 From: Fabiano Rosas Date: Mon, 17 Jun 2024 15:57:22 -0300 Subject: monitor: Stop removing non-duplicated fds monitor_fdsets_cleanup() currently has three responsibilities: 1- Remove the fds that have been marked for removal(->removed=true) by qmp_remove_fd(). This is overly complicated, but ok. 2- Remove any file descriptors that have been passed into QEMU and never duplicated[1,2]. A file descriptor without duplicates indicates that no part of QEMU has made use of it. This is problematic because the current implementation does it only if the guest is not running and the monitor is closed. 3- Remove/free fdsets that have become empty due to the above removals. This is ok. The scenario described in (2) is starting to show some cracks now that we're trying to consume fds from the migration code: - Doing cleanup every time the last monitor connection closes works to reap unused fds, but also has the side effect of forcing the management layer to pass the file descriptors again in case of a disconnect/re-connect, if that happened to be the only monitor connection. Another side effect is that removing an fd with qmp_remove_fd() is effectively delayed until the last monitor connection closes. The usage of mon_refcount is also problematic because it's racy. - Checking runstate_is_running() skips the cleanup unless the VM is running and avoids premature cleanup of the fds, but also has the side effect of blocking the legitimate removal of an fd via qmp_remove_fd() if the VM happens to be in another state. This affects qmp_remove_fd() and qmp_query_fdsets() in particular because requesting a removal at a bad time (guest stopped) might cause an fd to never be removed, or to be removed at a much later point in time, causing the query command to continue showing the supposedly removed fd/fdset. Note that file descriptors that *have* been duplicated are owned by the code that uses them and will be removed after qemu_close() is called. Therefore we've decided that the best course of action to avoid the undesired side-effects is to stop managing non-duplicated file descriptors. 1- efb87c1697 ("monitor: Clean up fd sets on monitor disconnect") 2- ebe52b592d ("monitor: Prevent removing fd from set during init") Reviewed-by: Peter Xu [fix logic mistake: s/fdset_free/fdset_free_if_empty] Signed-off-by: Fabiano Rosas --- monitor/fds.c | 15 ++++---- monitor/hmp.c | 2 -- monitor/monitor-internal.h | 1 - monitor/monitor.c | 1 - monitor/qmp.c | 2 -- tests/qtest/libqtest.c | 15 +++++--- tests/qtest/libqtest.h | 2 ++ tests/qtest/migration-test.c | 82 ++++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 102 insertions(+), 18 deletions(-) (limited to 'tests/qtest/migration-test.c') diff --git a/monitor/fds.c b/monitor/fds.c index bd45a26368..76199d4b3b 100644 --- a/monitor/fds.c +++ b/monitor/fds.c @@ -175,6 +175,11 @@ static void monitor_fdset_free(MonFdset *mon_fdset) static void monitor_fdset_free_if_empty(MonFdset *mon_fdset) { + /* + * Only remove an empty fdset. The fds are owned by the user and + * should have been removed with qmp_remove_fd(). The dup_fds are + * owned by QEMU and should have been removed with qemu_close(). + */ if (QLIST_EMPTY(&mon_fdset->fds) && QLIST_EMPTY(&mon_fdset->dup_fds)) { monitor_fdset_free(mon_fdset); } @@ -194,9 +199,7 @@ static void monitor_fdset_cleanup(MonFdset *mon_fdset) MonFdsetFd *mon_fdset_fd_next; QLIST_FOREACH_SAFE(mon_fdset_fd, &mon_fdset->fds, next, mon_fdset_fd_next) { - if ((mon_fdset_fd->removed || - (QLIST_EMPTY(&mon_fdset->dup_fds) && mon_refcount == 0)) && - runstate_is_running()) { + if (mon_fdset_fd->removed) { monitor_fdset_fd_free(mon_fdset_fd); } } @@ -211,7 +214,7 @@ void monitor_fdsets_cleanup(void) QEMU_LOCK_GUARD(&mon_fdsets_lock); QLIST_FOREACH_SAFE(mon_fdset, &mon_fdsets, next, mon_fdset_next) { - monitor_fdset_cleanup(mon_fdset); + monitor_fdset_free_if_empty(mon_fdset); } } @@ -484,9 +487,7 @@ void monitor_fdset_dup_fd_remove(int dup_fd) if (mon_fdset_fd_dup->fd == dup_fd) { QLIST_REMOVE(mon_fdset_fd_dup, next); g_free(mon_fdset_fd_dup); - if (QLIST_EMPTY(&mon_fdset->dup_fds)) { - monitor_fdset_cleanup(mon_fdset); - } + monitor_fdset_free_if_empty(mon_fdset); return; } } diff --git a/monitor/hmp.c b/monitor/hmp.c index 69c1b7e98a..460e8832f6 100644 --- a/monitor/hmp.c +++ b/monitor/hmp.c @@ -1437,11 +1437,9 @@ static void monitor_event(void *opaque, QEMUChrEvent event) monitor_resume(mon); } qemu_mutex_unlock(&mon->mon_lock); - mon_refcount++; break; case CHR_EVENT_CLOSED: - mon_refcount--; monitor_fdsets_cleanup(); break; diff --git a/monitor/monitor-internal.h b/monitor/monitor-internal.h index 252de85681..cb628f681d 100644 --- a/monitor/monitor-internal.h +++ b/monitor/monitor-internal.h @@ -168,7 +168,6 @@ extern bool qmp_dispatcher_co_shutdown; extern QmpCommandList qmp_commands, qmp_cap_negotiation_commands; extern QemuMutex monitor_lock; extern MonitorList mon_list; -extern int mon_refcount; extern HMPCommand hmp_cmds[]; diff --git a/monitor/monitor.c b/monitor/monitor.c index 01ede1babd..db52a9c7ef 100644 --- a/monitor/monitor.c +++ b/monitor/monitor.c @@ -71,7 +71,6 @@ static GHashTable *monitor_qapi_event_state; static GHashTable *coroutine_mon; /* Maps Coroutine* to Monitor* */ MonitorList mon_list; -int mon_refcount; static bool monitor_destroyed; Monitor *monitor_cur(void) diff --git a/monitor/qmp.c b/monitor/qmp.c index a239945e8d..5e538f34c0 100644 --- a/monitor/qmp.c +++ b/monitor/qmp.c @@ -466,7 +466,6 @@ static void monitor_qmp_event(void *opaque, QEMUChrEvent event) data = qmp_greeting(mon); qmp_send_response(mon, data); qobject_unref(data); - mon_refcount++; break; case CHR_EVENT_CLOSED: /* @@ -479,7 +478,6 @@ static void monitor_qmp_event(void *opaque, QEMUChrEvent event) json_message_parser_destroy(&mon->parser); json_message_parser_init(&mon->parser, handle_qmp_command, mon, NULL); - mon_refcount--; monitor_fdsets_cleanup(); break; case CHR_EVENT_BREAK: diff --git a/tests/qtest/libqtest.c b/tests/qtest/libqtest.c index 18e2f7f282..c7f6897d78 100644 --- a/tests/qtest/libqtest.c +++ b/tests/qtest/libqtest.c @@ -514,11 +514,6 @@ static QTestState *qtest_init_internal(const char *qemu_bin, kill(s->qemu_pid, SIGSTOP); } #endif - - /* ask endianness of the target */ - - s->big_endian = qtest_query_target_endianness(s); - return s; } @@ -527,11 +522,21 @@ QTestState *qtest_init_without_qmp_handshake(const char *extra_args) return qtest_init_internal(qtest_qemu_binary(NULL), extra_args); } +QTestState *qtest_init_with_env_no_handshake(const char *var, + const char *extra_args) +{ + return qtest_init_internal(qtest_qemu_binary(var), extra_args); +} + QTestState *qtest_init_with_env(const char *var, const char *extra_args) { QTestState *s = qtest_init_internal(qtest_qemu_binary(var), extra_args); QDict *greeting; + /* ask endianness of the target */ + + s->big_endian = qtest_query_target_endianness(s); + /* Read the QMP greeting and then do the handshake */ greeting = qtest_qmp_receive(s); qobject_unref(greeting); diff --git a/tests/qtest/libqtest.h b/tests/qtest/libqtest.h index beb96b18eb..c261b7e0b3 100644 --- a/tests/qtest/libqtest.h +++ b/tests/qtest/libqtest.h @@ -68,6 +68,8 @@ QTestState *qtest_init(const char *extra_args); */ QTestState *qtest_init_with_env(const char *var, const char *extra_args); +QTestState *qtest_init_with_env_no_handshake(const char *var, + const char *extra_args); /** * qtest_init_without_qmp_handshake: * @extra_args: other arguments to pass to QEMU. CAUTION: these diff --git a/tests/qtest/migration-test.c b/tests/qtest/migration-test.c index 22b07bc0ec..eb4d5948e0 100644 --- a/tests/qtest/migration-test.c +++ b/tests/qtest/migration-test.c @@ -64,6 +64,7 @@ static QTestMigrationState dst_state; #define DIRTYLIMIT_TOLERANCE_RANGE 25 /* MB/s */ #define ANALYZE_SCRIPT "scripts/analyze-migration.py" +#define VMSTATE_CHECKER_SCRIPT "scripts/vmstate-static-checker.py" #define QEMU_VM_FILE_MAGIC 0x5145564d #define FILE_TEST_FILENAME "migfile" @@ -1589,6 +1590,85 @@ static void test_analyze_script(void) test_migrate_end(from, to, false); cleanup("migfile"); } + +static void test_vmstate_checker_script(void) +{ + g_autofree gchar *cmd_src = NULL; + g_autofree gchar *cmd_dst = NULL; + g_autofree gchar *vmstate_src = NULL; + g_autofree gchar *vmstate_dst = NULL; + const char *machine_alias, *machine_opts = ""; + g_autofree char *machine = NULL; + const char *arch = qtest_get_arch(); + int pid, wstatus; + const char *python = g_getenv("PYTHON"); + + if (!getenv(QEMU_ENV_SRC) && !getenv(QEMU_ENV_DST)) { + g_test_skip("Test needs two different QEMU versions"); + return; + } + + if (!python) { + g_test_skip("PYTHON variable not set"); + return; + } + + if (strcmp(arch, "i386") == 0 || strcmp(arch, "x86_64") == 0) { + if (g_str_equal(arch, "i386")) { + machine_alias = "pc"; + } else { + machine_alias = "q35"; + } + } else if (g_str_equal(arch, "s390x")) { + machine_alias = "s390-ccw-virtio"; + } else if (strcmp(arch, "ppc64") == 0) { + machine_alias = "pseries"; + } else if (strcmp(arch, "aarch64") == 0) { + machine_alias = "virt"; + } else { + g_assert_not_reached(); + } + + if (!qtest_has_machine(machine_alias)) { + g_autofree char *msg = g_strdup_printf("machine %s not supported", machine_alias); + g_test_skip(msg); + return; + } + + machine = resolve_machine_version(machine_alias, QEMU_ENV_SRC, + QEMU_ENV_DST); + + vmstate_src = g_strdup_printf("%s/vmstate-src", tmpfs); + vmstate_dst = g_strdup_printf("%s/vmstate-dst", tmpfs); + + cmd_dst = g_strdup_printf("-machine %s,%s -dump-vmstate %s", + machine, machine_opts, vmstate_dst); + cmd_src = g_strdup_printf("-machine %s,%s -dump-vmstate %s", + machine, machine_opts, vmstate_src); + + qtest_init_with_env_no_handshake(QEMU_ENV_SRC, cmd_src); + qtest_init_with_env_no_handshake(QEMU_ENV_DST, cmd_dst); + + pid = fork(); + if (!pid) { + close(1); + open("/dev/null", O_WRONLY); + execl(python, python, VMSTATE_CHECKER_SCRIPT, + "-s", vmstate_src, + "-d", vmstate_dst, + NULL); + g_assert_not_reached(); + } + + g_assert(waitpid(pid, &wstatus, 0) == pid); + if (!WIFEXITED(wstatus) || WEXITSTATUS(wstatus) != 0) { + g_test_message("Failed to run vmstate-static-checker.py"); + g_test_fail(); + } + + cleanup("vmstate-src"); + cleanup("vmstate-dst"); +} #endif static void test_precopy_common(MigrateCommon *args) @@ -3522,6 +3602,8 @@ int main(int argc, char **argv) migration_test_add("/migration/bad_dest", test_baddest); #ifndef _WIN32 migration_test_add("/migration/analyze-script", test_analyze_script); + migration_test_add("/migration/vmstate-checker-script", + test_vmstate_checker_script); #endif /* -- cgit 1.4.1 From 408d295da82103df833f2f0443442d8aed880cb0 Mon Sep 17 00:00:00 2001 From: Fabiano Rosas Date: Mon, 17 Jun 2024 15:57:28 -0300 Subject: tests/qtest/migration: Add tests for file migration with direct-io The tests are only allowed to run in systems that know about the O_DIRECT flag and in filesystems which support it. Note: this also brings back migrate_set_parameter_bool() which went away when we removed the compression tests. I copied it verbatim. Reviewed-by: Peter Xu Signed-off-by: Fabiano Rosas --- tests/qtest/migration-helpers.c | 44 +++++++++++++++++++++++++++++ tests/qtest/migration-helpers.h | 8 ++++++ tests/qtest/migration-test.c | 62 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 114 insertions(+) (limited to 'tests/qtest/migration-test.c') diff --git a/tests/qtest/migration-helpers.c b/tests/qtest/migration-helpers.c index ce6d6615b5..0ac49ceb54 100644 --- a/tests/qtest/migration-helpers.c +++ b/tests/qtest/migration-helpers.c @@ -18,6 +18,7 @@ #include "qapi/error.h" #include "qapi/qmp/qlist.h" #include "qemu/cutils.h" +#include "qemu/memalign.h" #include "migration-helpers.h" @@ -473,3 +474,46 @@ void migration_test_add(const char *path, void (*fn)(void)) 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 diff --git a/tests/qtest/migration-helpers.h b/tests/qtest/migration-helpers.h index 1339835698..50095fca4a 100644 --- a/tests/qtest/migration-helpers.h +++ b/tests/qtest/migration-helpers.h @@ -54,5 +54,13 @@ char *find_common_machine_version(const char *mtype, const char *var1, const char *var2); char *resolve_machine_version(const char *alias, const char *var1, const char *var2); +#ifdef O_DIRECT +bool probe_o_direct_support(const char *tmpfs); +#else +static inline bool probe_o_direct_support(const char *tmpfs) +{ + return false; +} +#endif void migration_test_add(const char *path, void (*fn)(void)); #endif /* MIGRATION_HELPERS_H */ diff --git a/tests/qtest/migration-test.c b/tests/qtest/migration-test.c index eb4d5948e0..5c41d1b70e 100644 --- a/tests/qtest/migration-test.c +++ b/tests/qtest/migration-test.c @@ -408,6 +408,38 @@ static void migrate_set_parameter_str(QTestState *who, const char *parameter, migrate_check_parameter_str(who, parameter, value); } +static long long migrate_get_parameter_bool(QTestState *who, + const char *parameter) +{ + QDict *rsp; + int result; + + rsp = qtest_qmp_assert_success_ref( + who, "{ 'execute': 'query-migrate-parameters' }"); + result = qdict_get_bool(rsp, parameter); + qobject_unref(rsp); + return !!result; +} + +static void migrate_check_parameter_bool(QTestState *who, const char *parameter, + int value) +{ + int result; + + result = migrate_get_parameter_bool(who, parameter); + g_assert_cmpint(result, ==, value); +} + +static void migrate_set_parameter_bool(QTestState *who, const char *parameter, + int value) +{ + qtest_qmp_assert_success(who, + "{ 'execute': 'migrate-set-parameters'," + "'arguments': { %s: %i } }", + parameter, value); + migrate_check_parameter_bool(who, parameter, value); +} + static void migrate_ensure_non_converge(QTestState *who) { /* Can't converge with 1ms downtime + 3 mbs bandwidth limit */ @@ -2235,6 +2267,33 @@ static void test_multifd_file_mapped_ram(void) test_file_common(&args, true); } +static void *multifd_mapped_ram_dio_start(QTestState *from, QTestState *to) +{ + migrate_multifd_mapped_ram_start(from, to); + + migrate_set_parameter_bool(from, "direct-io", true); + migrate_set_parameter_bool(to, "direct-io", true); + + return NULL; +} + +static void test_multifd_file_mapped_ram_dio(void) +{ + g_autofree char *uri = g_strdup_printf("file:%s/%s", tmpfs, + FILE_TEST_FILENAME); + MigrateCommon args = { + .connect_uri = uri, + .listen_uri = "defer", + .start_hook = multifd_mapped_ram_dio_start, + }; + + if (!probe_o_direct_support(tmpfs)) { + g_test_skip("Filesystem does not support O_DIRECT"); + return; + } + + test_file_common(&args, true); +} static void test_precopy_tcp_plain(void) { @@ -3674,6 +3733,9 @@ int main(int argc, char **argv) migration_test_add("/migration/multifd/file/mapped-ram/live", test_multifd_file_mapped_ram_live); + migration_test_add("/migration/multifd/file/mapped-ram/dio", + test_multifd_file_mapped_ram_dio); + #ifdef CONFIG_GNUTLS migration_test_add("/migration/precopy/unix/tls/psk", test_precopy_unix_tls_psk); -- cgit 1.4.1 From 31a5a3032eb3d62e045e18c80658e5e8f5341cda Mon Sep 17 00:00:00 2001 From: Fabiano Rosas Date: Mon, 17 Jun 2024 15:57:31 -0300 Subject: tests/qtest/migration: Add a test for mapped-ram with passing of fds Add a multifd test for mapped-ram with passing of fds into QEMU. This is how libvirt will consume the feature. There are a couple of details to the fdset mechanism: - multifd needs two distinct file descriptors (not duplicated with dup()) so it can enable O_DIRECT only on the channels that do aligned IO. The dup() system call creates file descriptors that share status flags, of which O_DIRECT is one. - the open() access mode flags used for the fds passed into QEMU need to match the flags QEMU uses to open the file. Currently O_WRONLY for src and O_RDONLY for dst. Note that fdset code goes under _WIN32 because fd passing is not supported on Windows. Reviewed-by: Peter Xu [brought back the qmp_remove_fd() call at the end of the tests] Signed-off-by: Fabiano Rosas --- tests/qtest/migration-test.c | 105 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 102 insertions(+), 3 deletions(-) (limited to 'tests/qtest/migration-test.c') diff --git a/tests/qtest/migration-test.c b/tests/qtest/migration-test.c index 5c41d1b70e..6207305ff8 100644 --- a/tests/qtest/migration-test.c +++ b/tests/qtest/migration-test.c @@ -2104,11 +2104,18 @@ static void test_precopy_file(void) #ifndef _WIN32 static void fdset_add_fds(QTestState *qts, const char *file, int flags, - int num_fds) + int num_fds, bool direct_io) { for (int i = 0; i < num_fds; i++) { int fd; +#ifdef O_DIRECT + /* only secondary channels can use direct-io */ + if (direct_io && i != 0) { + flags |= O_DIRECT; + } +#endif + fd = open(file, flags, 0660); assert(fd != -1); @@ -2122,8 +2129,8 @@ static void *file_offset_fdset_start_hook(QTestState *from, QTestState *to) { g_autofree char *file = g_strdup_printf("%s/%s", tmpfs, FILE_TEST_FILENAME); - fdset_add_fds(from, file, O_WRONLY, 1); - fdset_add_fds(to, file, O_RDONLY, 1); + fdset_add_fds(from, file, O_WRONLY, 1, false); + fdset_add_fds(to, file, O_RDONLY, 1, false); return NULL; } @@ -2295,6 +2302,91 @@ static void test_multifd_file_mapped_ram_dio(void) test_file_common(&args, true); } +#ifndef _WIN32 +static void multifd_mapped_ram_fdset_end(QTestState *from, QTestState *to, + void *opaque) +{ + QDict *resp; + QList *fdsets; + + /* + * Remove the fdsets after migration, otherwise a second migration + * would fail due fdset reuse. + */ + qtest_qmp_assert_success(from, "{'execute': 'remove-fd', " + "'arguments': { 'fdset-id': 1}}"); + + /* + * Make sure no fdsets are left after migration, otherwise a + * second migration would fail due fdset reuse. + */ + resp = qtest_qmp(from, "{'execute': 'query-fdsets', " + "'arguments': {}}"); + g_assert(qdict_haskey(resp, "return")); + fdsets = qdict_get_qlist(resp, "return"); + g_assert(fdsets && qlist_empty(fdsets)); +} + +static void *multifd_mapped_ram_fdset_dio(QTestState *from, QTestState *to) +{ + g_autofree char *file = g_strdup_printf("%s/%s", tmpfs, FILE_TEST_FILENAME); + + fdset_add_fds(from, file, O_WRONLY, 2, true); + fdset_add_fds(to, file, O_RDONLY, 2, true); + + migrate_multifd_mapped_ram_start(from, to); + migrate_set_parameter_bool(from, "direct-io", true); + migrate_set_parameter_bool(to, "direct-io", true); + + return NULL; +} + +static void *multifd_mapped_ram_fdset(QTestState *from, QTestState *to) +{ + g_autofree char *file = g_strdup_printf("%s/%s", tmpfs, FILE_TEST_FILENAME); + + fdset_add_fds(from, file, O_WRONLY, 2, false); + fdset_add_fds(to, file, O_RDONLY, 2, false); + + migrate_multifd_mapped_ram_start(from, to); + + return NULL; +} + +static void test_multifd_file_mapped_ram_fdset(void) +{ + g_autofree char *uri = g_strdup_printf("file:/dev/fdset/1,offset=%d", + FILE_TEST_OFFSET); + MigrateCommon args = { + .connect_uri = uri, + .listen_uri = "defer", + .start_hook = multifd_mapped_ram_fdset, + .finish_hook = multifd_mapped_ram_fdset_end, + }; + + test_file_common(&args, true); +} + +static void test_multifd_file_mapped_ram_fdset_dio(void) +{ + g_autofree char *uri = g_strdup_printf("file:/dev/fdset/1,offset=%d", + FILE_TEST_OFFSET); + MigrateCommon args = { + .connect_uri = uri, + .listen_uri = "defer", + .start_hook = multifd_mapped_ram_fdset_dio, + .finish_hook = multifd_mapped_ram_fdset_end, + }; + + if (!probe_o_direct_support(tmpfs)) { + g_test_skip("Filesystem does not support O_DIRECT"); + return; + } + + test_file_common(&args, true); +} +#endif /* !_WIN32 */ + static void test_precopy_tcp_plain(void) { MigrateCommon args = { @@ -3736,6 +3828,13 @@ int main(int argc, char **argv) migration_test_add("/migration/multifd/file/mapped-ram/dio", test_multifd_file_mapped_ram_dio); +#ifndef _WIN32 + migration_test_add("/migration/multifd/file/mapped-ram/fdset", + test_multifd_file_mapped_ram_fdset); + migration_test_add("/migration/multifd/file/mapped-ram/fdset/dio", + test_multifd_file_mapped_ram_fdset_dio); +#endif + #ifdef CONFIG_GNUTLS migration_test_add("/migration/precopy/unix/tls/psk", test_precopy_unix_tls_psk); -- cgit 1.4.1 From 0fd397359540a6622c5f2164e76fc2cefd811f2a Mon Sep 17 00:00:00 2001 From: Peter Xu Date: Wed, 19 Jun 2024 18:30:42 -0400 Subject: tests/migration-tests: Drop most WIN32 ifdefs for postcopy failure tests Most of them are not needed, we can stick with one ifdef inside postcopy_recover_fail() so as to cover the scm right tricks only. The tests won't run on windows anyway due to has_uffd always false. Reviewed-by: Fabiano Rosas Signed-off-by: Peter Xu Signed-off-by: Fabiano Rosas --- tests/qtest/migration-test.c | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) (limited to 'tests/qtest/migration-test.c') diff --git a/tests/qtest/migration-test.c b/tests/qtest/migration-test.c index 6207305ff8..b7dea1aabb 100644 --- a/tests/qtest/migration-test.c +++ b/tests/qtest/migration-test.c @@ -1364,9 +1364,9 @@ static void wait_for_postcopy_status(QTestState *one, const char *status) "completed", NULL }); } -#ifndef _WIN32 static void postcopy_recover_fail(QTestState *from, QTestState *to) { +#ifndef _WIN32 int ret, pair1[2], pair2[2]; char c; @@ -1428,8 +1428,8 @@ static void postcopy_recover_fail(QTestState *from, QTestState *to) close(pair1[1]); close(pair2[0]); close(pair2[1]); +#endif } -#endif /* _WIN32 */ static void test_postcopy_recovery_common(MigrateCommon *args) { @@ -1469,7 +1469,6 @@ static void test_postcopy_recovery_common(MigrateCommon *args) wait_for_postcopy_status(to, "postcopy-paused"); wait_for_postcopy_status(from, "postcopy-paused"); -#ifndef _WIN32 if (args->postcopy_recovery_test_fail) { /* * Test when a wrong socket specified for recover, and then the @@ -1478,7 +1477,6 @@ static void test_postcopy_recovery_common(MigrateCommon *args) postcopy_recover_fail(from, to); /* continue with a good recovery */ } -#endif /* _WIN32 */ /* * Create a new socket to emulate a new channel that is different @@ -1507,7 +1505,6 @@ static void test_postcopy_recovery(void) test_postcopy_recovery_common(&args); } -#ifndef _WIN32 static void test_postcopy_recovery_double_fail(void) { MigrateCommon args = { @@ -1516,7 +1513,6 @@ static void test_postcopy_recovery_double_fail(void) test_postcopy_recovery_common(&args); } -#endif /* _WIN32 */ #ifdef CONFIG_GNUTLS static void test_postcopy_recovery_tls_psk(void) @@ -3782,10 +3778,8 @@ int main(int argc, char **argv) test_postcopy_preempt); migration_test_add("/migration/postcopy/preempt/recovery/plain", test_postcopy_preempt_recovery); -#ifndef _WIN32 migration_test_add("/migration/postcopy/recovery/double-failures", test_postcopy_recovery_double_fail); -#endif /* _WIN32 */ if (is_x86) { migration_test_add("/migration/postcopy/suspend", test_postcopy_suspend); -- cgit 1.4.1 From cd313b66f203381f2f2f984d5155d7942d26725d Mon Sep 17 00:00:00 2001 From: Peter Xu Date: Wed, 19 Jun 2024 18:30:43 -0400 Subject: tests/migration-tests: Always enable migration events Libvirt should always enable it, so it'll be nice qtest also cover that for all tests on both sides. migrate_incoming_qmp() used to enable it only on dst, now we enable them on both, as we'll start to sanity check events even on the src QEMU. We'll need to leave the one in migrate_incoming_qmp(), because virtio-net-failover test uses that one only, and it relies on the events to work. Signed-off-by: Peter Xu Reviewed-by: Fabiano Rosas Signed-off-by: Fabiano Rosas --- tests/qtest/migration-helpers.c | 1 + tests/qtest/migration-test.c | 7 +++++++ 2 files changed, 8 insertions(+) (limited to 'tests/qtest/migration-test.c') diff --git a/tests/qtest/migration-helpers.c b/tests/qtest/migration-helpers.c index 0ac49ceb54..2ca4425d71 100644 --- a/tests/qtest/migration-helpers.c +++ b/tests/qtest/migration-helpers.c @@ -258,6 +258,7 @@ void migrate_incoming_qmp(QTestState *to, const char *uri, const char *fmt, ...) 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}", diff --git a/tests/qtest/migration-test.c b/tests/qtest/migration-test.c index b7dea1aabb..32e31fff86 100644 --- a/tests/qtest/migration-test.c +++ b/tests/qtest/migration-test.c @@ -852,6 +852,13 @@ static int test_migrate_start(QTestState **from, QTestState **to, unlink(shmem_path); } + /* + * Always enable migration events. Libvirt always uses it, let's try + * to mimic as closer as that. + */ + migrate_set_capability(*from, "events", true); + migrate_set_capability(*to, "events", true); + return 0; } -- cgit 1.4.1 From 8dbd24d3aa6d67b2d3576da016fb631fd1edfc2c Mon Sep 17 00:00:00 2001 From: Peter Xu Date: Wed, 19 Jun 2024 18:30:45 -0400 Subject: tests/migration-tests: Verify postcopy-recover-setup status Making sure the postcopy-recover-setup status is present in the postcopy failure unit test. Note that it only applies to src QEMU not dest. Signed-off-by: Peter Xu Reviewed-by: Fabiano Rosas Signed-off-by: Fabiano Rosas --- tests/qtest/migration-test.c | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'tests/qtest/migration-test.c') diff --git a/tests/qtest/migration-test.c b/tests/qtest/migration-test.c index 32e31fff86..e61096adfe 100644 --- a/tests/qtest/migration-test.c +++ b/tests/qtest/migration-test.c @@ -1413,6 +1413,12 @@ static void postcopy_recover_fail(QTestState *from, QTestState *to) migrate_recover(to, "fd:fd-mig"); migrate_qmp(from, to, "fd:fd-mig", NULL, "{'resume': true}"); + /* + * Source QEMU has an extra RECOVER_SETUP phase, dest doesn't have it. + * Make sure it appears along the way. + */ + migration_event_wait(from, "postcopy-recover-setup"); + /* * Make sure both QEMU instances will go into RECOVER stage, then test * kicking them out using migrate-pause. -- cgit 1.4.1 From 6cf56a87baf8b99c4296a943d220eb8276ca035a Mon Sep 17 00:00:00 2001 From: Peter Xu Date: Wed, 19 Jun 2024 18:30:46 -0400 Subject: tests/migration-tests: Cover postcopy failure on reconnect Make sure there will be an event for postcopy recovery, irrelevant of whether the reconnect will success, or when the failure happens. The added new case is to fail early in postcopy recovery, in which case it didn't even reach RECOVER stage on src (and in real life it'll be the same to dest, but the test case is just slightly more involved due to the dual socketpair setup). To do that, rename the postcopy_recovery_test_fail to reflect either stage to fail, instead of a boolean. Reviewed-by: Fabiano Rosas Signed-off-by: Peter Xu Signed-off-by: Fabiano Rosas --- tests/qtest/migration-test.c | 95 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 77 insertions(+), 18 deletions(-) (limited to 'tests/qtest/migration-test.c') diff --git a/tests/qtest/migration-test.c b/tests/qtest/migration-test.c index e61096adfe..571fc1334c 100644 --- a/tests/qtest/migration-test.c +++ b/tests/qtest/migration-test.c @@ -73,6 +73,17 @@ static QTestMigrationState dst_state; #define QEMU_ENV_SRC "QTEST_QEMU_BINARY_SRC" #define QEMU_ENV_DST "QTEST_QEMU_BINARY_DST" +typedef enum PostcopyRecoveryFailStage { + /* + * "no failure" must be 0 as it's the default. OTOH, real failure + * cases must be >0 to make sure they trigger by a "if" test. + */ + POSTCOPY_FAIL_NONE = 0, + POSTCOPY_FAIL_CHANNEL_ESTABLISH, + POSTCOPY_FAIL_RECOVERY, + POSTCOPY_FAIL_MAX +} PostcopyRecoveryFailStage; + #if defined(__linux__) #include #include @@ -693,7 +704,7 @@ typedef struct { /* Postcopy specific fields */ void *postcopy_data; bool postcopy_preempt; - bool postcopy_recovery_test_fail; + PostcopyRecoveryFailStage postcopy_recovery_fail_stage; } MigrateCommon; static int test_migrate_start(QTestState **from, QTestState **to, @@ -1371,12 +1382,16 @@ static void wait_for_postcopy_status(QTestState *one, const char *status) "completed", NULL }); } -static void postcopy_recover_fail(QTestState *from, QTestState *to) +static void postcopy_recover_fail(QTestState *from, QTestState *to, + PostcopyRecoveryFailStage stage) { #ifndef _WIN32 + bool fail_early = (stage == POSTCOPY_FAIL_CHANNEL_ESTABLISH); int ret, pair1[2], pair2[2]; char c; + g_assert(stage > POSTCOPY_FAIL_NONE && stage < POSTCOPY_FAIL_MAX); + /* Create two unrelated socketpairs */ ret = qemu_socketpair(PF_LOCAL, SOCK_STREAM, 0, pair1); g_assert_cmpint(ret, ==, 0); @@ -1410,6 +1425,14 @@ static void postcopy_recover_fail(QTestState *from, QTestState *to) ret = send(pair2[1], &c, 1, 0); g_assert_cmpint(ret, ==, 1); + if (stage == POSTCOPY_FAIL_CHANNEL_ESTABLISH) { + /* + * This will make src QEMU to fail at an early stage when trying to + * resume later, where it shouldn't reach RECOVER stage at all. + */ + close(pair1[1]); + } + migrate_recover(to, "fd:fd-mig"); migrate_qmp(from, to, "fd:fd-mig", NULL, "{'resume': true}"); @@ -1419,28 +1442,53 @@ static void postcopy_recover_fail(QTestState *from, QTestState *to) */ migration_event_wait(from, "postcopy-recover-setup"); + if (fail_early) { + /* + * When fails at reconnection, src QEMU will automatically goes + * back to PAUSED state. Making sure there is an event in this + * case: Libvirt relies on this to detect early reconnection + * errors. + */ + migration_event_wait(from, "postcopy-paused"); + } else { + /* + * We want to test "fail later" at RECOVER stage here. Make sure + * both QEMU instances will go into RECOVER stage first, then test + * kicking them out using migrate-pause. + * + * Explicitly check the RECOVER event on src, that's what Libvirt + * relies on, rather than polling. + */ + migration_event_wait(from, "postcopy-recover"); + wait_for_postcopy_status(from, "postcopy-recover"); + + /* Need an explicit kick on src QEMU in this case */ + migrate_pause(from); + } + /* - * Make sure both QEMU instances will go into RECOVER stage, then test - * kicking them out using migrate-pause. + * For all failure cases, we'll reach such states on both sides now. + * Check them. */ - wait_for_postcopy_status(from, "postcopy-recover"); + wait_for_postcopy_status(from, "postcopy-paused"); wait_for_postcopy_status(to, "postcopy-recover"); /* - * This would be issued by the admin upon noticing the hang, we should - * make sure we're able to kick this out. + * Kick dest QEMU out too. This is normally not needed in reality + * because when the channel is shutdown it should also happen on src. + * However here we used separate socket pairs so we need to do that + * explicitly. */ - migrate_pause(from); - wait_for_postcopy_status(from, "postcopy-paused"); - - /* Do the same test on dest */ migrate_pause(to); wait_for_postcopy_status(to, "postcopy-paused"); close(pair1[0]); - close(pair1[1]); close(pair2[0]); close(pair2[1]); + + if (stage != POSTCOPY_FAIL_CHANNEL_ESTABLISH) { + close(pair1[1]); + } #endif } @@ -1482,12 +1530,12 @@ static void test_postcopy_recovery_common(MigrateCommon *args) wait_for_postcopy_status(to, "postcopy-paused"); wait_for_postcopy_status(from, "postcopy-paused"); - if (args->postcopy_recovery_test_fail) { + if (args->postcopy_recovery_fail_stage) { /* * Test when a wrong socket specified for recover, and then the * ability to kick it out, and continue with a correct socket. */ - postcopy_recover_fail(from, to); + postcopy_recover_fail(from, to, args->postcopy_recovery_fail_stage); /* continue with a good recovery */ } @@ -1518,10 +1566,19 @@ static void test_postcopy_recovery(void) test_postcopy_recovery_common(&args); } -static void test_postcopy_recovery_double_fail(void) +static void test_postcopy_recovery_fail_handshake(void) +{ + MigrateCommon args = { + .postcopy_recovery_fail_stage = POSTCOPY_FAIL_RECOVERY, + }; + + test_postcopy_recovery_common(&args); +} + +static void test_postcopy_recovery_fail_reconnect(void) { MigrateCommon args = { - .postcopy_recovery_test_fail = true, + .postcopy_recovery_fail_stage = POSTCOPY_FAIL_CHANNEL_ESTABLISH, }; test_postcopy_recovery_common(&args); @@ -3791,8 +3848,10 @@ int main(int argc, char **argv) test_postcopy_preempt); migration_test_add("/migration/postcopy/preempt/recovery/plain", test_postcopy_preempt_recovery); - migration_test_add("/migration/postcopy/recovery/double-failures", - test_postcopy_recovery_double_fail); + migration_test_add("/migration/postcopy/recovery/double-failures/handshake", + test_postcopy_recovery_fail_handshake); + migration_test_add("/migration/postcopy/recovery/double-failures/reconnect", + test_postcopy_recovery_fail_reconnect); if (is_x86) { migration_test_add("/migration/postcopy/suspend", test_postcopy_suspend); -- cgit 1.4.1