diff mbox series

[4/4] tests/qtest/migration: Add savevm tests

Message ID 20250327143934.7935-5-farosas@suse.de (mailing list archive)
State New
Headers show
Series migration: savevm testing | expand

Commit Message

Fabiano Rosas March 27, 2025, 2:39 p.m. UTC
Add a test file for savevm tests so the snapshot functionality can be
better tested in the context of migration. There's currently issues
with migration capabilities causing crashes in QEMU when running
savevm.

Start with a couple of tests, one that simply saves and loads a
snapshot and another to check that migration capabilities don't cause
disruption of savevm.

The simple savevm/loadvm test will be redundant with some block layer
tests that are already present. The objective here is more to have an
infrastructure that can save and load snapshots from different QEMU
versions, which is convenient for tracking compatibility bugs.

I chose to not add a guest workload for this because we could in the
future run the test for all architectures without having to write
guest code (but also because the QEMU cmdline construction is way more
complex).

Both tests only run during the full set of tests.

Signed-off-by: Fabiano Rosas <farosas@suse.de>
---
 tests/qtest/meson.build              |   1 +
 tests/qtest/migration-test.c         |   1 +
 tests/qtest/migration/framework.c    |   4 +-
 tests/qtest/migration/framework.h    |   5 +
 tests/qtest/migration/savevm-tests.c | 144 +++++++++++++++++++++++++++
 5 files changed, 152 insertions(+), 3 deletions(-)
 create mode 100644 tests/qtest/migration/savevm-tests.c
diff mbox series

Patch

diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index 3136d15e0f..305b662620 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -347,6 +347,7 @@  migration_files = [files(
   'migration/misc-tests.c',
   'migration/precopy-tests.c',
   'migration/postcopy-tests.c',
+  'migration/savevm-tests.c',
 )]
 
 migration_tls_files = []
diff --git a/tests/qtest/migration-test.c b/tests/qtest/migration-test.c
index 0893687174..b15f6d091b 100644
--- a/tests/qtest/migration-test.c
+++ b/tests/qtest/migration-test.c
@@ -55,6 +55,7 @@  int main(int argc, char **argv)
     migration_test_add_precopy(env);
     migration_test_add_cpr(env);
     migration_test_add_misc(env);
+    migration_test_add_savevm(env);
 
     ret = g_test_run();
 
diff --git a/tests/qtest/migration/framework.c b/tests/qtest/migration/framework.c
index 2311100dd6..42bda03693 100644
--- a/tests/qtest/migration/framework.c
+++ b/tests/qtest/migration/framework.c
@@ -28,8 +28,6 @@ 
 
 
 #define QEMU_VM_FILE_MAGIC 0x5145564d
-#define QEMU_ENV_SRC "QTEST_QEMU_BINARY_SRC"
-#define QEMU_ENV_DST "QTEST_QEMU_BINARY_DST"
 
 unsigned start_address;
 unsigned end_address;
@@ -207,7 +205,7 @@  static QList *migrate_start_get_qmp_capabilities(const MigrateStart *args)
     return capabilities;
 }
 
-static char *migrate_resolve_alias(const char *arch)
+char *migrate_resolve_alias(const char *arch)
 {
     const char *machine_alias;
 
diff --git a/tests/qtest/migration/framework.h b/tests/qtest/migration/framework.h
index e4a11870f6..66823267af 100644
--- a/tests/qtest/migration/framework.h
+++ b/tests/qtest/migration/framework.h
@@ -17,6 +17,9 @@ 
 #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"
+
 typedef struct MigrationTestEnv {
     bool has_kvm;
     bool has_tcg;
@@ -225,6 +228,7 @@  void test_file_common(MigrateCommon *args, bool stop_src);
 void *migrate_hook_start_precopy_tcp_multifd_common(QTestState *from,
                                                     QTestState *to,
                                                     const char *method);
+char *migrate_resolve_alias(const char *arch);
 
 typedef struct QTestMigrationState QTestMigrationState;
 QTestMigrationState *get_src(void);
@@ -240,5 +244,6 @@  void migration_test_add_file(MigrationTestEnv *env);
 void migration_test_add_precopy(MigrationTestEnv *env);
 void migration_test_add_cpr(MigrationTestEnv *env);
 void migration_test_add_misc(MigrationTestEnv *env);
+void migration_test_add_savevm(MigrationTestEnv *env);
 
 #endif /* TEST_FRAMEWORK_H */
diff --git a/tests/qtest/migration/savevm-tests.c b/tests/qtest/migration/savevm-tests.c
new file mode 100644
index 0000000000..5904c4b07e
--- /dev/null
+++ b/tests/qtest/migration/savevm-tests.c
@@ -0,0 +1,144 @@ 
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include "qemu/osdep.h"
+#include "libqtest.h"
+#include "migration/framework.h"
+#include "migration/migration-qmp.h"
+#include "migration/migration-util.h"
+#include "qapi/qapi-types-migration.h"
+
+char *disk_path;
+
+static char *savevm_make_cmdline(void)
+{
+    MigrationTestEnv *env = migration_get_env();
+    g_autofree char *drive_opts = NULL;
+    g_autofree char *arch_opts = NULL;
+    g_autofree char *machine_opts = NULL;
+    g_autofree char *machine = NULL;
+
+    disk_path = g_strdup_printf("%s/qtest-savevm-%d.qcow2", g_get_tmp_dir(),
+                                getpid());
+    drive_opts = g_strdup_printf("-drive if=none,file=%s,format=qcow2,node-name=disk0 ",
+                                disk_path);
+
+    g_assert(mkimg(disk_path, "qcow2", 100));
+
+    machine = migrate_resolve_alias(env->arch);
+
+    if (machine) {
+        machine_opts = g_strdup_printf("-machine %s", machine);
+    }
+
+    return g_strdup_printf("%s %s %s",
+                           drive_opts,
+                           arch_opts ?: "",
+                           machine_opts ?: "");
+}
+
+static void teardown_savevm_test(void)
+{
+    unlink(disk_path);
+    g_free(disk_path);
+}
+
+/*
+ * Enabling capabilities before savevm/loadvm should either apply the
+ * appropriate feature or reject the command. Crashing or ignoring the
+ * capability is not acceptable. Most (all?) migration capabilities
+ * are incompatible with snapshots, but they've historically not been
+ * rejected. Since there are compatibility concerns with simply
+ * rejecting all caps, for now this test only validates that nothing
+ * crashes.
+ */
+static void test_savevm_caps(void)
+{
+    MigrationTestEnv *env = migration_get_env();
+    g_autofree char *cmdline = savevm_make_cmdline();
+    QTestState *vm;
+
+    /*
+     * Only one VM to avoid having to shutdown the machine several
+     * times to release the disks lock.
+     */
+    if (env->qemu_src || env->qemu_dst) {
+        g_test_skip("Only one QEMU binary is supported");
+        return;
+    }
+
+    vm = qtest_init(cmdline);
+
+    for (int i = 0; i < MIGRATION_CAPABILITY__MAX; i++) {
+        const char *cap = MigrationCapability_str(i);
+        g_autofree char *error_str = NULL;
+        bool ret;
+
+        switch (i) {
+        case MIGRATION_CAPABILITY_ZERO_BLOCKS:          /* deprecated */
+        case MIGRATION_CAPABILITY_ZERO_COPY_SEND:       /* requires multifd */
+        case MIGRATION_CAPABILITY_POSTCOPY_PREEMPT:     /* requires postcopy-ram */
+        case MIGRATION_CAPABILITY_SWITCHOVER_ACK:       /* requires return-path */
+        case MIGRATION_CAPABILITY_DIRTY_LIMIT:          /* requires dirty ring setup */
+        case MIGRATION_CAPABILITY_BACKGROUND_SNAPSHOT:  /* requires uffd setup */
+            continue;
+        default:
+            break;
+        }
+
+        if (getenv("QTEST_LOG")) {
+            g_test_message("%s", MigrationCapability_str(i));
+        }
+        migrate_set_capability(vm, MigrationCapability_str(i), true);
+
+        ret = snapshot_save_qmp_sync(vm, &error_str);
+
+        if (ret) {
+            g_assert(snapshot_load_qmp_sync(vm, NULL));
+            g_assert(snapshot_delete_qmp_sync(vm, NULL));
+        } else {
+            g_autofree char *msg = g_strdup_printf(
+                "Snapshots are not compatible with %s", cap);
+
+            g_assert(error_str);
+            g_assert(g_str_equal(msg, error_str));
+        }
+
+        migrate_set_capability(vm, MigrationCapability_str(i), false);
+    }
+
+    qtest_quit(vm);
+    teardown_savevm_test();
+}
+
+static void test_savevm_loadvm(void)
+{
+    g_autofree char *cmdline = savevm_make_cmdline();
+    QTestState *src, *dst;
+    bool ret;
+
+    src = qtest_init_with_env(QEMU_ENV_SRC, cmdline, true);
+
+    ret = snapshot_save_qmp_sync(src, NULL);
+    qtest_quit(src);
+
+    if (ret) {
+        dst = qtest_init_with_env(QEMU_ENV_DST, cmdline, true);
+
+        g_assert(snapshot_load_qmp_sync(dst, NULL));
+        g_assert(snapshot_delete_qmp_sync(dst, NULL));
+        qtest_quit(dst);
+    }
+
+    teardown_savevm_test();
+}
+
+void migration_test_add_savevm(MigrationTestEnv *env)
+{
+    if (!getenv("QTEST_QEMU_IMG")) {
+        g_test_message("savevm tests require QTEST_QEMU_IMG");
+        return;
+    }
+
+    migration_test_add("/migration/savevm/save-load", test_savevm_loadvm);
+    migration_test_add("/migration/savevm/capabilities", test_savevm_caps);
+}