From: phind.uet@gmail.com
To: Peter Xu <peterx@redhat.com>, Fabiano Rosas <farosas@suse.de>,
Laurent Vivier <lvivier@redhat.com>,
Paolo Bonzini <pbonzini@redhat.com>
Cc: Nguyen Dinh Phi <phind.uet@gmail.com>, qemu-devel@nongnu.org
Subject: [PATCH 3/3] tests/migration: Add chain (A to B to C) precopy migration tests
Date: Thu, 9 Jul 2026 21:00:27 +0800 [thread overview]
Message-ID: <20260709130032.58667-4-phind.uet@gmail.com> (raw)
In-Reply-To: <20260709130032.58667-1-phind.uet@gmail.com>
From: Nguyen Dinh Phi <phind.uet@gmail.com>
Extract migrate_precopy_hop() from the SUCCESS path of test_precopy_common()
to wrap the per-hop migration sequence of a succesfull migration.
test_precopy_common() delegates its success path to this helper; failure
paths are handled directly.
Introduce test_precopy_chain(), a helper that drives two consecutive
migration hops on already-running processes: first A→B, then B→C, both
hops are expected to be success.
Add two test cases exercising the new helper:
- /migration/precopy/tcp/plain/chain
- /migration/multifd/tcp/channels/plain/chain
Signed-off-by: Nguyen Dinh Phi <phind.uet@gmail.com>
---
tests/qtest/migration/framework.c | 195 +++++++++++++++++---------
tests/qtest/migration/framework.h | 1 +
tests/qtest/migration/precopy-tests.c | 23 +++
3 files changed, 153 insertions(+), 66 deletions(-)
diff --git a/tests/qtest/migration/framework.c b/tests/qtest/migration/framework.c
index a7dffeb82c..b4218b2f23 100644
--- a/tests/qtest/migration/framework.c
+++ b/tests/qtest/migration/framework.c
@@ -883,28 +883,25 @@ void test_postcopy_recovery_common(MigrateCommon *args,
migrate_postcopy_complete(from, to, hook_data, args);
}
-int test_precopy_common(MigrateCommon *args)
+/*
+ * Perform one precopy migration hop on an already-running (from, to) pair.
+ * Both processes must have migrate_setup_instance() called before this
+ * function.
+ * Always expects the hop to succeed.
+ *
+ */
+static void migrate_precopy_hop(QTestState *from, QTestState *to,
+ const char *listen_uri,
+ const char *connect_uri,
+ MigrateCommon *args)
{
- QTestState *from, *to;
- void *data_hook = NULL;
QObject *channels = NULL;
- const char *listen_uri = args->uri ?: "tcp:127.0.0.1:0";
-
- if (migrate_start(&from, &to, &args->start)) {
- return -1;
- }
-
- if (args->start_hook) {
- data_hook = args->start_hook(from, to);
- }
+ int iters;
migrate_incoming_qmp(to, listen_uri, NULL, "{}");
- /* Wait for the first serial output from the source */
- if (args->result == MIG_TEST_SUCCEED) {
- wait_for_serial(qtest_get_serial_path(from));
- wait_for_suspend(from, &src_state);
- }
+ wait_for_serial(qtest_get_serial_path(from));
+ wait_for_suspend(from, &src_state);
if (args->live) {
migrate_ensure_non_converge(from);
@@ -913,76 +910,104 @@ int test_precopy_common(MigrateCommon *args)
/*
* Testing non-live migration, we allow it to run at
* full speed to ensure short test case duration.
- * For tests expected to fail, we don't need to
- * change anything.
*/
- if (args->result == MIG_TEST_SUCCEED) {
- qtest_qmp_assert_success(from, "{ 'execute' : 'stop'}");
- wait_for_stop(from, &src_state);
- migrate_ensure_converge(from);
- }
+ qtest_qmp_assert_success(from, "{ 'execute' : 'stop'}");
+ wait_for_stop(from, &src_state);
+ migrate_ensure_converge(from);
}
if (args->connect_channels) {
channels = qobject_from_json(args->connect_channels, &error_abort);
}
- if (args->result == MIG_TEST_QMP_ERROR) {
- migrate_qmp_fail(from, args->uri, channels, "{}");
- goto finish;
- }
+ migrate_qmp(from, to, connect_uri, channels, "{}");
- migrate_qmp(from, to, args->uri, channels, "{}");
+ if (args->live) {
+ /*
+ * For initial iteration(s) we must do a full pass,
+ * but for the final iteration, we need only wait
+ * for some dirty mem before switching to converge.
+ */
+ /*
+ * Avoid changing the args->iterations by using a local copy
+ */
+ iters = args->iterations;
+ while (iters > 1) {
+ wait_for_migration_pass(from, &src_state);
+ iters--;
+ }
+ migrate_wait_for_dirty_mem(from, to);
+
+ migrate_ensure_converge(from);
+
+ /*
+ * We do this first, as it has a timeout to stop us
+ * hanging forever if migration didn't converge
+ */
+ wait_for_migration_complete(from);
+
+ wait_for_stop(from, &src_state);
- if (args->result != MIG_TEST_SUCCEED) {
- bool allow_active = args->result == MIG_TEST_FAIL;
- wait_for_migration_fail(from, allow_active);
} else {
- if (args->live) {
- /*
- * For initial iteration(s) we must do a full pass,
- * but for the final iteration, we need only wait
- * for some dirty mem before switching to converge
- */
- while (args->iterations > 1) {
- wait_for_migration_pass(from, &src_state);
- args->iterations--;
- }
- migrate_wait_for_dirty_mem(from, to);
+ wait_for_migration_complete(from);
+ /*
+ * Must wait for dst to finish reading all incoming
+ * data on the socket before issuing 'cont' otherwise
+ * it'll be ignored
+ */
+ wait_for_migration_complete(to);
- migrate_ensure_converge(from);
+ qtest_qmp_assert_success(to, "{ 'execute' : 'cont'}");
+ }
- /*
- * We do this first, as it has a timeout to stop us
- * hanging forever if migration didn't converge
- */
- wait_for_migration_complete(from);
+ wait_for_resume(to, &dst_state);
+
+ if (args->start.suspend_me) {
+ /* wakeup succeeds only if guest is suspended */
+ qtest_qmp_assert_success(to, "{'execute': 'system_wakeup'}");
+ }
- wait_for_stop(from, &src_state);
+ wait_for_serial(qtest_get_serial_path(to));
+}
- } else {
- wait_for_migration_complete(from);
- /*
- * Must wait for dst to finish reading all incoming
- * data on the socket before issuing 'cont' otherwise
- * it'll be ignored
- */
- wait_for_migration_complete(to);
+int test_precopy_common(MigrateCommon *args)
+{
+ QTestState *from, *to;
+ void *data_hook = NULL;
+ const char *listen_uri = args->uri ?: "tcp:127.0.0.1:0";
- qtest_qmp_assert_success(to, "{ 'execute' : 'cont'}");
- }
+ if (migrate_start(&from, &to, &args->start)) {
+ return -1;
+ }
+
+ if (args->start_hook) {
+ data_hook = args->start_hook(from, to);
+ }
- wait_for_resume(to, &dst_state);
+ if (args->result == MIG_TEST_SUCCEED) {
+ migrate_precopy_hop(from, to, listen_uri, args->uri, args);
+ } else {
+ QObject *channels = NULL;
+
+ migrate_incoming_qmp(to, listen_uri, NULL, "{}");
+
+ if (args->live) {
+ migrate_ensure_non_converge(from);
+ migrate_prepare_for_dirty_mem(from);
+ }
- if (args->start.suspend_me) {
- /* wakeup succeeds only if guest is suspended */
- qtest_qmp_assert_success(to, "{'execute': 'system_wakeup'}");
+ if (args->connect_channels) {
+ channels = qobject_from_json(args->connect_channels, &error_abort);
}
- wait_for_serial(qtest_get_serial_path(to));
+ if (args->result == MIG_TEST_QMP_ERROR) {
+ migrate_qmp_fail(from, args->uri, channels, "{}");
+ } else {
+ migrate_qmp(from, to, args->uri, channels, "{}");
+ wait_for_migration_fail(from, args->result == MIG_TEST_FAIL);
+ }
}
-finish:
if (args->end_hook) {
args->end_hook(from, to, data_hook);
}
@@ -1000,6 +1025,44 @@ void test_precopy_unix_common(MigrateCommon *args)
test_precopy_common(args);
}
+int test_precopy_chain(MigrateCommon *args)
+{
+ QTestState *from, *to, *to2;
+ const char *listen_uri = args->uri ?: "tcp:127.0.0.1:0";
+
+ /*
+ * Hook callbacks operate on a fixed (from, to) pair; they do not
+ * compose across multiple migration legs.
+ */
+ g_assert(!args->start_hook && !args->end_hook);
+
+ if (migrate_start(&from, &to, &args->start)) {
+ return -1;
+ }
+
+ /* First leg: A -> B */
+ migrate_precopy_hop(from, to, listen_uri, args->uri, args);
+
+ /*
+ * Second leg: B -> C.
+ */
+ qtest_quit(from);
+ migrate_setup_instance(to, &src_state, &args->start);
+
+ to2 = migrate_launch_dest("dest_serial2", &args->start);
+ if (!to2) {
+ qtest_quit(to);
+ return -1;
+ }
+ migrate_setup_instance(to2, &dst_state, &args->start);
+
+ migrate_precopy_hop(to, to2, listen_uri, NULL, args);
+
+ migrate_end(to, to2, true);
+
+ return 0;
+}
+
static void file_dirty_offset_region(void)
{
g_autofree char *path = g_strdup_printf("%s/%s", tmpfs, FILE_TEST_FILENAME);
diff --git a/tests/qtest/migration/framework.h b/tests/qtest/migration/framework.h
index f6936eb8c6..ca5289329a 100644
--- a/tests/qtest/migration/framework.h
+++ b/tests/qtest/migration/framework.h
@@ -254,6 +254,7 @@ void test_postcopy_common(MigrateCommon *args);
void test_postcopy_recovery_common(MigrateCommon *args,
PostcopyRecoveryFailStage fail_stage);
int test_precopy_common(MigrateCommon *args);
+int test_precopy_chain(MigrateCommon *args);
void test_precopy_unix_common(MigrateCommon *args);
void test_file_common(MigrateCommon *args, bool stop_src);
diff --git a/tests/qtest/migration/precopy-tests.c b/tests/qtest/migration/precopy-tests.c
index 194462c841..fccff76ef6 100644
--- a/tests/qtest/migration/precopy-tests.c
+++ b/tests/qtest/migration/precopy-tests.c
@@ -182,6 +182,11 @@ static void test_precopy_tcp_plain(char *name, MigrateCommon *args)
test_precopy_common(args);
}
+static void test_precopy_tcp_plain_chain(char *name, MigrateCommon *args)
+{
+ test_precopy_chain(args);
+}
+
static void test_precopy_tcp_switchover_ack(char *name, MigrateCommon *args)
{
/*
@@ -398,6 +403,20 @@ static void test_multifd_tcp_channels_none(char *name, MigrateCommon *args)
test_precopy_common(args);
}
+static void test_multifd_tcp_channels_chain(char *name, MigrateCommon *args)
+{
+ args->live = true;
+ args->connect_channels = ("[ { 'channel-type': 'main',"
+ " 'addr': { 'transport': 'socket',"
+ " 'type': 'inet',"
+ " 'host': '127.0.0.1',"
+ " 'port': '0' } } ]");
+
+ args->start.caps[MIGRATION_CAPABILITY_MULTIFD] = true;
+
+ test_precopy_chain(args);
+}
+
/*
* This test does:
* source target
@@ -1112,6 +1131,8 @@ void migration_test_add_precopy(MigrationTestEnv *env)
return;
}
+ migration_test_add("/migration/precopy/tcp/plain/chain",
+ test_precopy_tcp_plain_chain);
migration_test_add("/migration/precopy/tcp/plain/switchover-ack",
test_precopy_tcp_switchover_ack);
@@ -1133,6 +1154,8 @@ void migration_test_add_precopy(MigrationTestEnv *env)
}
migration_test_add("/migration/multifd/tcp/channels/plain/none",
test_multifd_tcp_channels_none);
+ migration_test_add("/migration/multifd/tcp/channels/plain/chain",
+ test_multifd_tcp_channels_chain);
migration_test_add("/migration/multifd/tcp/plain/zero-page/legacy",
test_multifd_tcp_zero_page_legacy);
migration_test_add("/migration/multifd/tcp/plain/zero-page/none",
--
2.50.1 (Apple Git-155)
prev parent reply other threads:[~2026-07-09 13:01 UTC|newest]
Thread overview: 4+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-09 13:00 [PATCH 0/3] Support chain migration in test phind.uet
2026-07-09 13:00 ` [PATCH 1/3] tests/migration: Factor out per-process launch and setup helpers phind.uet
2026-07-09 13:00 ` [PATCH 2/3] tests/migration: Retrieve serial path from QTestState phind.uet
2026-07-09 13:00 ` phind.uet [this message]
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=20260709130032.58667-4-phind.uet@gmail.com \
--to=phind.uet@gmail.com \
--cc=farosas@suse.de \
--cc=lvivier@redhat.com \
--cc=pbonzini@redhat.com \
--cc=peterx@redhat.com \
--cc=qemu-devel@nongnu.org \
/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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox