The qmp_migrate_cancel() command is poorly tested and code inspection reveals that there might be concurrency issues with its usage. Add a test that runs a migration and calls qmp_migrate_cancel() at specific moments.
In order to make the test more deterministic, instead of calling qmp_migrate_cancel() at random moments during migration, do it after the migration status change events are seen. The expected result is that qmp_migrate_cancel() on the source ends migration on the source with the "cancelled" state and ends migration on the destination with the "failed" state. Signed-off-by: Fabiano Rosas <faro...@suse.de> --- tests/qtest/migration-test.c | 243 +++++++++++++++++++++++++++++++++++ 1 file changed, 243 insertions(+) diff --git a/tests/qtest/migration-test.c b/tests/qtest/migration-test.c index 74d3000198..48942289bf 100644 --- a/tests/qtest/migration-test.c +++ b/tests/qtest/migration-test.c @@ -23,6 +23,8 @@ #include "qapi/qmp/qlist.h" #include "ppc-util.h" +#include "qapi-types-migration.h" + #include "migration-helpers.h" #include "tests/migration/migration-test.h" #ifdef CONFIG_GNUTLS @@ -3774,6 +3776,234 @@ static bool kvm_dirty_ring_supported(void) #endif } +static void test_cancel_src_after_failed(QTestState *from, QTestState *to, + const char *uri, const char *phase) +{ + /* + * No migrate_incoming_qmp() at the start to force source into + * failed state during migrate_qmp(). + */ + + wait_for_serial("src_serial"); + migrate_ensure_converge(from); + + migrate_qmp(from, to, uri, NULL, "{}"); + + migration_event_wait(from, phase); + migrate_cancel(from); + + /* cancelling will not move the migration out of 'failed' */ + + wait_for_migration_status(from, "failed", + (const char * []) { "completed", NULL }); + + /* + * Not waiting for the destination because it never started + * migration. + */ +} + +static void test_cancel_src_after_cancelled(QTestState *from, QTestState *to, + const char *uri, const char *phase) +{ + migrate_incoming_qmp(to, uri, "{ 'exit-on-error': false }"); + + wait_for_serial("src_serial"); + migrate_ensure_converge(from); + + migrate_qmp(from, to, uri, NULL, "{}"); + + /* To move to cancelled/cancelling */ + migrate_cancel(from); + migration_event_wait(from, phase); + + /* The migrate_cancel under test */ + migrate_cancel(from); + + wait_for_migration_status(from, "cancelled", + (const char * []) { "completed", NULL }); + + wait_for_migration_status(to, "failed", + (const char * []) { "completed", NULL }); +} + +static void test_cancel_src_after_complete(QTestState *from, QTestState *to, + const char *uri, const char *phase) +{ + migrate_incoming_qmp(to, uri, "{ 'exit-on-error': false }"); + + wait_for_serial("src_serial"); + migrate_ensure_converge(from); + + migrate_qmp(from, to, uri, NULL, "{}"); + + migration_event_wait(from, phase); + migrate_cancel(from); + + /* + * qmp_migrate_cancel() exits early if migration is not running + * anymore, the status will not change to cancelled. + */ + wait_for_migration_complete(from); + wait_for_migration_complete(to); +} + +static void test_cancel_src_after_none(QTestState *from, QTestState *to, + const char *uri, const char *phase) +{ + /* + * Test that cancelling without a migration happening does not + * affect subsequent migrations + */ + migrate_cancel(to); + + wait_for_serial("src_serial"); + migrate_cancel(from); + + migrate_incoming_qmp(to, uri, "{ 'exit-on-error': false }"); + + migrate_ensure_converge(from); + migrate_qmp(from, to, uri, NULL, "{}"); + + wait_for_migration_complete(from); + wait_for_migration_complete(to); +} + +static void test_cancel_src_after_postcopy(QTestState *from, QTestState *to, + const char *uri, const char *phase) +{ + bool postcopy_active = g_str_equal(phase, "postcopy-active"); + bool postcopy_paused = g_str_equal(phase, "postcopy-paused"); + bool postcopy_recover = g_str_equal(phase, "postcopy-recover-setup") || + g_str_equal(phase, "postcopy-recover"); + + migrate_set_capability(from, "postcopy-ram", true); + migrate_set_capability(to, "postcopy-ram", true); + + migrate_incoming_qmp(to, uri, "{ 'exit-on-error': false }"); + + wait_for_serial("src_serial"); + migrate_ensure_non_converge(from); + + migrate_qmp(from, to, uri, NULL, "{}"); + + migration_event_wait(from, "active"); + + /* Turn postcopy speed down, 4K/s is slow enough on any machines */ + migrate_set_parameter_int(from, "max-postcopy-bandwidth", 4096); + qtest_qmp_assert_success(from, "{ 'execute': 'migrate-start-postcopy' }"); + + if (!postcopy_active) { + migration_event_wait(from, "postcopy-active"); + migration_event_wait(to, "postcopy-active"); + + wait_for_stop(from, &src_state); + qtest_qmp_eventwait(to, "RESUME"); + + migrate_pause(from); + } + + if (postcopy_recover) { + migration_event_wait(to, "postcopy-paused"); + migration_event_wait(from, "postcopy-paused"); + + migrate_recover(to, uri); + migrate_qmp(from, to, uri, NULL, "{'resume': true}"); + } + + migration_event_wait(from, phase); + migrate_cancel(from); + migration_event_wait(from, "cancelling"); + + wait_for_migration_status(from, "cancelled", + (const char * []) { "completed", NULL }); + + if (postcopy_paused || postcopy_recover) { + /* + * Cancelling on source is not detectable by the destination, + * so it remains paused. + */ + migration_event_wait(to, "postcopy-paused"); + } else { + wait_for_migration_status(to, "failed", + (const char * []) { "completed", NULL }); + } +} + +static void test_cancel_src_pre_switchover(QTestState *from, QTestState *to, + const char *uri, const char *phase) +{ + migrate_set_capability(from, "pause-before-switchover", true); + migrate_set_capability(to, "pause-before-switchover", true); + + migrate_set_parameter_int(from, "multifd-channels", 2); + migrate_set_parameter_int(to, "multifd-channels", 2); + migrate_set_capability(from, "multifd", true); + migrate_set_capability(to, "multifd", true); + + migrate_incoming_qmp(to, uri, "{ 'exit-on-error': false }"); + + wait_for_serial("src_serial"); + migrate_ensure_converge(from); + + migrate_qmp(from, to, uri, NULL, "{}"); + + migration_event_wait(from, phase); + migrate_cancel(from); + migration_event_wait(from, "cancelling"); + + wait_for_migration_status(from, "cancelled", + (const char * []) { "completed", NULL }); + + wait_for_migration_status(to, "failed", + (const char * []) { "completed", NULL }); +} + +static void test_cancel_src_after_status(void *opaque) +{ + const char *test_path = opaque; + g_autofree char *phase = g_path_get_basename(test_path); + g_autofree char *uri = g_strdup_printf("unix:%s/migsocket", tmpfs); + QTestState *from, *to; + MigrateStart args = { + .hide_stderr = true, + }; + + if (test_migrate_start(&from, &to, "defer", &args)) { + return; + } + + if (g_str_has_prefix(phase, "postcopy")) { + if (!ufd_version_check()) { + g_test_skip("No postcopy support. " + "Cannot test migrate_cancel in postcopy states"); + goto out; + } + + test_cancel_src_after_postcopy(from, to, uri, phase); + + } else if (g_str_equal(phase, "cancelling") || + g_str_equal(phase, "cancelled")) { + test_cancel_src_after_cancelled(from, to, uri, phase); + + } else if (g_str_equal(phase, "completed")) { + test_cancel_src_after_complete(from, to, uri, phase); + + } else if (g_str_equal(phase, "failed")) { + test_cancel_src_after_failed(from, to, uri, phase); + + } else if (g_str_equal(phase, "none")) { + test_cancel_src_after_none(from, to, uri, phase); + + } else { + /* any state that comes before pre-switchover */ + test_cancel_src_pre_switchover(from, to, uri, phase); + } + +out: + test_migrate_end(from, to, false); +} + int main(int argc, char **argv) { bool has_kvm, has_tcg; @@ -4034,6 +4264,19 @@ int main(int argc, char **argv) } } + for (int i = MIGRATION_STATUS_NONE; i < MIGRATION_STATUS__MAX; i++) { + switch (i) { + case MIGRATION_STATUS_DEVICE: /* happens too fast */ + case MIGRATION_STATUS_WAIT_UNPLUG: /* no support in tests */ + case MIGRATION_STATUS_COLO: /* no support in tests */ + continue; + default: + migration_test_add_suffix("/migration/cancel/src/after/", + MigrationStatus_str(i), + test_cancel_src_after_status); + } + } + ret = g_test_run(); g_assert_cmpint(ret, ==, 0); -- 2.35.3