QEMU-Devel Archive on lore.kernel.org
 help / color / mirror / Atom feed
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)



      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