1 | CI: https://gitlab.com/peterx/qemu/-/pipelines/1577280033 | ||
---|---|---|---|
2 | (note: it's a pipeline of two patchsets, to save CI credits and time) | ||
3 | |||
4 | v1: https://lore.kernel.org/r/20241204005138.702289-1-peterx@redhat.com | ||
5 | |||
6 | This is v2 of the series, removing RFC tag, because my goal is to have them | ||
7 | (or some newer version) merged. | ||
8 | |||
9 | The major change is I merged last three patches, and did quite some changes | ||
10 | here and there, to make sure the global disk activation status is always | ||
11 | consistent. The whole idea is still the same. I say changelog won't help. | ||
12 | |||
13 | I also temporarily dropped Fabiano's ping-pong test cases to avoid | ||
14 | different versions floating on the list (as I know a new version is coming | ||
15 | at some point. Fabiano: you're taking over the 10.0 pulls, so I assume | ||
16 | you're aware so there's no concern on order of merges). I'll review the | ||
17 | test cases separately when they're ready, but this series is still tested | ||
18 | with that pingpong test and it keeps working. | ||
19 | |||
1 | I started looking at this problem as a whole when reviewing Fabiano's | 20 | I started looking at this problem as a whole when reviewing Fabiano's |
2 | series, especially the patch (for a QEMU crash [1]): | 21 | series, especially the patch (for a QEMU crash [1]): |
3 | 22 | ||
4 | https://lore.kernel.org/r/20241125144612.16194-5-farosas@suse.de | 23 | https://lore.kernel.org/r/20241125144612.16194-5-farosas@suse.de |
5 | 24 | ||
6 | The proposed patch could work, but it's unwanted to add such side effect to | 25 | The proposed patch could work, but it's unwanted to add such side effect to |
7 | migration. So I start to think about whether we can provide a cleaner | 26 | migration. So I start to think about whether we can provide a cleaner |
8 | approach, at least remove that "we must active the disk for migration" | 27 | approach, because migration doesn't need the disks to be active to work at |
9 | dependency, because migration really don't need the disks to be active.. | 28 | all. Hence we should try to avoid adding a migration ABI (which doesn't |
29 | matter now, but may matter some day) into prepare phase on disk activation | ||
30 | status. Migration should happen with disks inactivated. | ||
10 | 31 | ||
11 | It's also a pure wish that, if bdrv_inactivate_all() could be benign to be | 32 | It's also a pure wish that, if bdrv_inactivate_all() could be benign to be |
12 | called even if all disks are already inactive. Then problem also gone. | 33 | called even if all disks are already inactive. Then the bug is also gone. |
13 | After all, similar call on bdrv_activate_all() upon all-active disks is all | 34 | After all, similar call on bdrv_activate_all() upon all-active disks is all |
14 | fine. I hope that wish could still be fair. | 35 | fine. I hope that wish could still be fair. But I don't know well on |
36 | block layer to say anything meaningful. | ||
15 | 37 | ||
16 | And when I was looking at that, I found more things spread all over the | 38 | And when I was looking at that, I found more things spread all over the |
17 | place on disk activation. I decided to clean all of them up, while | 39 | place on disk activation. I decided to clean all of them up, while |
18 | hopefully fixing the QEMU crash [1] too. | 40 | hopefully fixing the QEMU crash [1] too. |
19 | 41 | ||
20 | So this is what I came up with as of today. Marking RFC as of now, just to | 42 | For this v2, I did some more tests, I want to make sure all the past paths |
21 | collect some feedbacks first. At least I'd like to go with one more patch | 43 | keep working at least on failure or cancel races, also in postcopy failure |
22 | to deprecate late-block-active - not deprecating its function, but make it | 44 | cases. So I did below and they all run pass (when I said "emulated" below, |
23 | always happen (which is the default as of now for Libvirt), which should | 45 | I meant I hacked something to trigger those race / rare failures, because |
24 | hopefully be migration-ABI-safe. | 46 | they aren't easy to trigger with vanilla binary): |
25 | 47 | ||
26 | With the help of Fabiano's test cases, I at least am sure this series works | 48 | - Tested generic migrate_cancel during precopy, disk activation won't be |
27 | for the ping pong migrations, and all existing qtests. | 49 | affected. Disk status reports correct values in tracepoints. |
28 | 50 | ||
29 | Let me know, thanks. | 51 | - Test Fabiano's ping-pong migration tests on PAUSED state VM. |
52 | |||
53 | - Emulated precopy failure before sending non-iterable, disk inactivation | ||
54 | won't happen, and also activation won't trigger after migration cleanups | ||
55 | (even if activation on top of activate disk is benign, I checked traces | ||
56 | to make sure it'll provide consistent disk status, skipping activation). | ||
57 | |||
58 | - Emulated precopy failure right after sending non-iterable. Disks will be | ||
59 | inactivated, but then can be reactivated properly before VM starts. | ||
60 | |||
61 | - Emulated postcopy failure when sending the packed data (which is after | ||
62 | disk invalidated), and making sure src VM will get back to live properly, | ||
63 | re-activate the disks before starting. | ||
64 | |||
65 | - Emulated concurrent migrate_cancel at the end of migration_completion() | ||
66 | of precopy, after disk inactivated. Disks can be reactivated properly. | ||
67 | |||
68 | NOTE: here if dest QEMU didn't quit before migrate_cancel, | ||
69 | bdrv_activate_all() can crash src QEMU. This behavior should be the same | ||
70 | before/after this patch. | ||
71 | |||
72 | Comments welcomed, thanks. | ||
30 | 73 | ||
31 | [1] https://gitlab.com/qemu-project/qemu/-/issues/2395 | 74 | [1] https://gitlab.com/qemu-project/qemu/-/issues/2395 |
32 | 75 | ||
33 | Fabiano Rosas (4): | 76 | Peter Xu (6): |
34 | tests/qtest/migration: Move more code under only_target | ||
35 | tests/qtest/migration: Don't use hardcoded strings for -serial | ||
36 | tests/qtest/migration: Support cleaning up only one side of migration | ||
37 | tests/qtest/migration: Test successive migrations | ||
38 | |||
39 | Peter Xu (7): | ||
40 | migration: Add helper to get target runstate | 77 | migration: Add helper to get target runstate |
78 | qmp/cont: Only activate disks if migration completed | ||
41 | migration/block: Make late-block-active the default | 79 | migration/block: Make late-block-active the default |
42 | migration/block: Apply late-block-active behavior to postcopy | 80 | migration/block: Apply late-block-active behavior to postcopy |
43 | migration/block: Fix possible race with block_inactive | 81 | migration/block: Fix possible race with block_inactive |
44 | migration/block: Merge block reactivations for fail/cancel | 82 | migration/block: Rewrite disk activation |
45 | migration/block: Extend the migration_block_* API to dest side | ||
46 | migration/block: Apply the migration_block_* API to postcopy | ||
47 | 83 | ||
48 | migration/migration.h | 33 ++++- | 84 | include/migration/misc.h | 4 ++ |
49 | tests/qtest/migration-helpers.h | 2 + | 85 | migration/migration.h | 6 +- |
50 | migration/migration.c | 177 +++++++++++----------- | 86 | migration/block-active.c | 94 +++++++++++++++++++++++++++ |
51 | migration/savevm.c | 32 ++-- | 87 | migration/colo.c | 2 +- |
52 | tests/qtest/migration-helpers.c | 8 + | 88 | migration/migration.c | 136 +++++++++++++++------------------------ |
53 | tests/qtest/migration-test.c | 252 +++++++++++++++++++++++++------- | 89 | migration/savevm.c | 46 ++++++------- |
54 | 6 files changed, 344 insertions(+), 160 deletions(-) | 90 | monitor/qmp-cmds.c | 22 +++---- |
91 | migration/meson.build | 1 + | ||
92 | migration/trace-events | 3 + | ||
93 | 9 files changed, 188 insertions(+), 126 deletions(-) | ||
94 | create mode 100644 migration/block-active.c | ||
55 | 95 | ||
56 | -- | 96 | -- |
57 | 2.47.0 | 97 | 2.47.0 | diff view generated by jsdifflib |
1 | In 99% cases, after QEMU migrates to dest host, it tries to detect the | 1 | In 99% cases, after QEMU migrates to dest host, it tries to detect the |
---|---|---|---|
2 | target VM runstate using global_state_get_runstate(). | 2 | target VM runstate using global_state_get_runstate(). |
3 | 3 | ||
4 | There's one outlier so far which is Xen that won't send global state. | 4 | There's one outlier so far which is Xen that won't send global state. |
5 | That's the major reason why global_state_received() check was always there | 5 | That's the major reason why global_state_received() check was always there |
6 | together with global_state_get_runstate(). | 6 | together with global_state_get_runstate(). |
7 | 7 | ||
8 | However it's utterly confusing why global_state_received() has anything to | 8 | However it's utterly confusing why global_state_received() has anything to |
9 | do with "let's start VM or not". | 9 | do with "let's start VM or not". |
10 | 10 | ||
11 | Provide a helper to explain it, then we have an unified entry for getting | 11 | Provide a helper to explain it, then we have an unified entry for getting |
12 | the target dest QEMU runstate after migration. | 12 | the target dest QEMU runstate after migration. |
13 | 13 | ||
14 | Suggested-by: Fabiano Rosas <farosas@suse.de> | 14 | Suggested-by: Fabiano Rosas <farosas@suse.de> |
15 | Signed-off-by: Peter Xu <peterx@redhat.com> | 15 | Signed-off-by: Peter Xu <peterx@redhat.com> |
16 | --- | 16 | --- |
17 | migration/migration.c | 21 +++++++++++++++++---- | 17 | migration/migration.c | 21 +++++++++++++++++---- |
18 | 1 file changed, 17 insertions(+), 4 deletions(-) | 18 | 1 file changed, 17 insertions(+), 4 deletions(-) |
19 | 19 | ||
20 | diff --git a/migration/migration.c b/migration/migration.c | 20 | diff --git a/migration/migration.c b/migration/migration.c |
21 | index XXXXXXX..XXXXXXX 100644 | 21 | index XXXXXXX..XXXXXXX 100644 |
22 | --- a/migration/migration.c | 22 | --- a/migration/migration.c |
23 | +++ b/migration/migration.c | 23 | +++ b/migration/migration.c |
24 | @@ -XXX,XX +XXX,XX @@ static bool migration_needs_multiple_sockets(void) | 24 | @@ -XXX,XX +XXX,XX @@ static bool migration_needs_multiple_sockets(void) |
25 | return migrate_multifd() || migrate_postcopy_preempt(); | 25 | return migrate_multifd() || migrate_postcopy_preempt(); |
26 | } | 26 | } |
27 | 27 | ||
28 | +static RunState migration_get_target_runstate(void) | 28 | +static RunState migration_get_target_runstate(void) |
29 | +{ | 29 | +{ |
30 | + /* | 30 | + /* |
31 | + * When the global state is not migrated, it means we don't know the | 31 | + * When the global state is not migrated, it means we don't know the |
32 | + * runstate of the src QEMU. We don't have much choice but assuming | 32 | + * runstate of the src QEMU. We don't have much choice but assuming |
33 | + * the VM is running. NOTE: this is pretty rare case, so far only Xen | 33 | + * the VM is running. NOTE: this is pretty rare case, so far only Xen |
34 | + * uses it. | 34 | + * uses it. |
35 | + */ | 35 | + */ |
36 | + if (!global_state_received()) { | 36 | + if (!global_state_received()) { |
37 | + return RUN_STATE_RUNNING; | 37 | + return RUN_STATE_RUNNING; |
38 | + } | 38 | + } |
39 | + | 39 | + |
40 | + return global_state_get_runstate(); | 40 | + return global_state_get_runstate(); |
41 | +} | 41 | +} |
42 | + | 42 | + |
43 | static bool transport_supports_multi_channels(MigrationAddress *addr) | 43 | static bool transport_supports_multi_channels(MigrationAddress *addr) |
44 | { | 44 | { |
45 | if (addr->transport == MIGRATION_ADDRESS_TYPE_SOCKET) { | 45 | if (addr->transport == MIGRATION_ADDRESS_TYPE_SOCKET) { |
46 | @@ -XXX,XX +XXX,XX @@ static void process_incoming_migration_bh(void *opaque) | 46 | @@ -XXX,XX +XXX,XX @@ static void process_incoming_migration_bh(void *opaque) |
47 | * unless we really are starting the VM. | 47 | * unless we really are starting the VM. |
48 | */ | 48 | */ |
49 | if (!migrate_late_block_activate() || | 49 | if (!migrate_late_block_activate() || |
50 | - (autostart && (!global_state_received() || | 50 | - (autostart && (!global_state_received() || |
51 | - runstate_is_live(global_state_get_runstate())))) { | 51 | - runstate_is_live(global_state_get_runstate())))) { |
52 | + (autostart && runstate_is_live(migration_get_target_runstate()))) { | 52 | + (autostart && runstate_is_live(migration_get_target_runstate()))) { |
53 | /* Make sure all file formats throw away their mutable metadata. | 53 | /* Make sure all file formats throw away their mutable metadata. |
54 | * If we get an error here, just don't restart the VM yet. */ | 54 | * If we get an error here, just don't restart the VM yet. */ |
55 | bdrv_activate_all(&local_err); | 55 | bdrv_activate_all(&local_err); |
56 | @@ -XXX,XX +XXX,XX @@ static void process_incoming_migration_bh(void *opaque) | 56 | @@ -XXX,XX +XXX,XX @@ static void process_incoming_migration_bh(void *opaque) |
57 | 57 | ||
58 | dirty_bitmap_mig_before_vm_start(); | 58 | dirty_bitmap_mig_before_vm_start(); |
59 | 59 | ||
60 | - if (!global_state_received() || | 60 | - if (!global_state_received() || |
61 | - runstate_is_live(global_state_get_runstate())) { | 61 | - runstate_is_live(global_state_get_runstate())) { |
62 | + if (runstate_is_live(migration_get_target_runstate())) { | 62 | + if (runstate_is_live(migration_get_target_runstate())) { |
63 | if (autostart) { | 63 | if (autostart) { |
64 | vm_start(); | 64 | vm_start(); |
65 | } else { | 65 | } else { |
66 | -- | 66 | -- |
67 | 2.47.0 | 67 | 2.47.0 | diff view generated by jsdifflib |
1 | From: Fabiano Rosas <farosas@suse.de> | 1 | As the comment says, the activation of disks is for the case where |
---|---|---|---|
2 | migration has completed, rather than when QEMU is still during | ||
3 | migration (RUN_STATE_INMIGRATE). | ||
2 | 4 | ||
3 | The only_target option's purpose is to make sure only the destination | 5 | Move the code over to reflect what the comment is describing. |
4 | QTestState machine is initialized. This allows the test code to retain | ||
5 | an already initialized source machine (e.g. for doing ping pong | ||
6 | migration). | ||
7 | 6 | ||
8 | We have drifted from that a bit when adding new code, so move some | 7 | Cc: Kevin Wolf <kwolf@redhat.com> |
9 | lines under only_target to restore the functionality. | 8 | Cc: Markus Armbruster <armbru@redhat.com> |
10 | |||
11 | Signed-off-by: Fabiano Rosas <farosas@suse.de> | ||
12 | Link: https://lore.kernel.org/r/20241125144612.16194-2-farosas@suse.de | ||
13 | Signed-off-by: Peter Xu <peterx@redhat.com> | 9 | Signed-off-by: Peter Xu <peterx@redhat.com> |
14 | --- | 10 | --- |
15 | tests/qtest/migration-test.c | 44 ++++++++++++++++++++---------------- | 11 | monitor/qmp-cmds.c | 26 ++++++++++++++------------ |
16 | 1 file changed, 25 insertions(+), 19 deletions(-) | 12 | 1 file changed, 14 insertions(+), 12 deletions(-) |
17 | 13 | ||
18 | diff --git a/tests/qtest/migration-test.c b/tests/qtest/migration-test.c | 14 | diff --git a/monitor/qmp-cmds.c b/monitor/qmp-cmds.c |
19 | index XXXXXXX..XXXXXXX 100644 | 15 | index XXXXXXX..XXXXXXX 100644 |
20 | --- a/tests/qtest/migration-test.c | 16 | --- a/monitor/qmp-cmds.c |
21 | +++ b/tests/qtest/migration-test.c | 17 | +++ b/monitor/qmp-cmds.c |
22 | @@ -XXX,XX +XXX,XX @@ static int test_migrate_start(QTestState **from, QTestState **to, | 18 | @@ -XXX,XX +XXX,XX @@ void qmp_cont(Error **errp) |
23 | g_autofree gchar *arch_target = NULL; | ||
24 | /* options for source and target */ | ||
25 | g_autofree gchar *arch_opts = NULL; | ||
26 | - g_autofree gchar *cmd_source = NULL; | ||
27 | g_autofree gchar *cmd_target = NULL; | ||
28 | const gchar *ignore_stderr; | ||
29 | g_autofree char *shmem_opts = NULL; | ||
30 | @@ -XXX,XX +XXX,XX @@ static int test_migrate_start(QTestState **from, QTestState **to, | ||
31 | } | 19 | } |
32 | } | 20 | } |
33 | 21 | ||
34 | - dst_state = (QTestMigrationState) { }; | 22 | - /* Continuing after completed migration. Images have been inactivated to |
35 | - src_state = (QTestMigrationState) { }; | 23 | - * allow the destination to take control. Need to get control back now. |
36 | bootfile_create(tmpfs, args->suspend_me); | 24 | - * |
37 | - src_state.suspend_me = args->suspend_me; | 25 | - * If there are no inactive block nodes (e.g. because the VM was just |
38 | 26 | - * paused rather than completing a migration), bdrv_inactivate_all() simply | |
39 | if (strcmp(arch, "i386") == 0 || strcmp(arch, "x86_64") == 0) { | 27 | - * doesn't do anything. */ |
40 | memory_size = "150M"; | 28 | - bdrv_activate_all(&local_err); |
41 | @@ -XXX,XX +XXX,XX @@ static int test_migrate_start(QTestState **from, QTestState **to, | 29 | - if (local_err) { |
42 | 30 | - error_propagate(errp, local_err); | |
43 | g_test_message("Using machine type: %s", machine); | 31 | - return; |
44 | 32 | - } | |
45 | - cmd_source = g_strdup_printf("-accel kvm%s -accel tcg " | 33 | - |
46 | - "-machine %s,%s " | 34 | if (runstate_check(RUN_STATE_INMIGRATE)) { |
47 | - "-name source,debug-threads=on " | 35 | autostart = 1; |
48 | - "-m %s " | 36 | } else { |
49 | - "-serial file:%s/src_serial " | 37 | + /* |
50 | - "%s %s %s %s %s", | 38 | + * Continuing after completed migration. Images have been |
51 | - kvm_opts ? kvm_opts : "", | 39 | + * inactivated to allow the destination to take control. Need to |
52 | - machine, machine_opts, | 40 | + * get control back now. |
53 | - memory_size, tmpfs, | 41 | + * |
54 | - arch_opts ? arch_opts : "", | 42 | + * If there are no inactive block nodes (e.g. because the VM was |
55 | - arch_source ? arch_source : "", | 43 | + * just paused rather than completing a migration), |
56 | - shmem_opts ? shmem_opts : "", | 44 | + * bdrv_inactivate_all() simply doesn't do anything. |
57 | - args->opts_source ? args->opts_source : "", | 45 | + */ |
58 | - ignore_stderr); | 46 | + bdrv_activate_all(&local_err); |
59 | if (!args->only_target) { | 47 | + if (local_err) { |
60 | + g_autofree gchar *cmd_source = NULL; | 48 | + error_propagate(errp, local_err); |
61 | + | 49 | + return; |
62 | + src_state = (QTestMigrationState) { }; | 50 | + } |
63 | + src_state.suspend_me = args->suspend_me; | 51 | vm_start(); |
64 | + | ||
65 | + cmd_source = g_strdup_printf("-accel kvm%s -accel tcg " | ||
66 | + "-machine %s,%s " | ||
67 | + "-name source,debug-threads=on " | ||
68 | + "-m %s " | ||
69 | + "-serial file:%s/src_serial " | ||
70 | + "%s %s %s %s %s", | ||
71 | + kvm_opts ? kvm_opts : "", | ||
72 | + machine, machine_opts, | ||
73 | + memory_size, tmpfs, | ||
74 | + arch_opts ? arch_opts : "", | ||
75 | + arch_source ? arch_source : "", | ||
76 | + shmem_opts ? shmem_opts : "", | ||
77 | + args->opts_source ? args->opts_source : "", | ||
78 | + ignore_stderr); | ||
79 | + | ||
80 | *from = qtest_init_with_env(QEMU_ENV_SRC, cmd_source); | ||
81 | qtest_qmp_set_event_callback(*from, | ||
82 | migrate_watch_for_events, | ||
83 | &src_state); | ||
84 | } | 52 | } |
85 | 53 | } | |
86 | + dst_state = (QTestMigrationState) { }; | ||
87 | + | ||
88 | cmd_target = g_strdup_printf("-accel kvm%s -accel tcg " | ||
89 | "-machine %s,%s " | ||
90 | "-name target,debug-threads=on " | ||
91 | @@ -XXX,XX +XXX,XX @@ static int test_migrate_start(QTestState **from, QTestState **to, | ||
92 | * Always enable migration events. Libvirt always uses it, let's try | ||
93 | * to mimic as closer as that. | ||
94 | */ | ||
95 | - migrate_set_capability(*from, "events", true); | ||
96 | + if (!args->only_target) { | ||
97 | + migrate_set_capability(*from, "events", true); | ||
98 | + } | ||
99 | migrate_set_capability(*to, "events", true); | ||
100 | |||
101 | return 0; | ||
102 | -- | 54 | -- |
103 | 2.47.0 | 55 | 2.47.0 | diff view generated by jsdifflib |
1 | Migration capability 'late-block-active' controls when the block drives | 1 | Migration capability 'late-block-active' controls when the block drives |
---|---|---|---|
2 | will be activated. If enabled, block drives will only be activated until | 2 | will be activated. If enabled, block drives will only be activated until |
3 | VM starts, either src runstate was "live" (RUNNING, or SUSPENDED), or it'll | 3 | VM starts, either src runstate was "live" (RUNNING, or SUSPENDED), or it'll |
4 | be postponed until qmp_cont(). | 4 | be postponed until qmp_cont(). |
5 | 5 | ||
6 | Let's do this unconditionally. There's no harm to delay activation of | 6 | Let's do this unconditionally. There's no harm to delay activation of |
7 | block drives. Meanwhile there's no ABI breakage if dest does it, because | 7 | block drives. Meanwhile there's no ABI breakage if dest does it, because |
8 | src QEMU has nothing to do with it, so it's no concern on ABI breakage. | 8 | src QEMU has nothing to do with it, so it's no concern on ABI breakage. |
9 | 9 | ||
10 | IIUC we could avoid introducing this cap when introducing it before, but | 10 | IIUC we could avoid introducing this cap when introducing it before, but |
11 | now it's still not too late to just always do it. Cap now prone to | 11 | now it's still not too late to just always do it. Cap now prone to |
12 | removal, but it'll be for later patches. | 12 | removal, but it'll be for later patches. |
13 | 13 | ||
14 | Signed-off-by: Peter Xu <peterx@redhat.com> | 14 | Signed-off-by: Peter Xu <peterx@redhat.com> |
15 | --- | 15 | --- |
16 | migration/migration.c | 38 +++++++++++++++++++------------------- | 16 | migration/migration.c | 38 +++++++++++++++++++------------------- |
17 | 1 file changed, 19 insertions(+), 19 deletions(-) | 17 | 1 file changed, 19 insertions(+), 19 deletions(-) |
18 | 18 | ||
19 | diff --git a/migration/migration.c b/migration/migration.c | 19 | diff --git a/migration/migration.c b/migration/migration.c |
20 | index XXXXXXX..XXXXXXX 100644 | 20 | index XXXXXXX..XXXXXXX 100644 |
21 | --- a/migration/migration.c | 21 | --- a/migration/migration.c |
22 | +++ b/migration/migration.c | 22 | +++ b/migration/migration.c |
23 | @@ -XXX,XX +XXX,XX @@ static void process_incoming_migration_bh(void *opaque) | 23 | @@ -XXX,XX +XXX,XX @@ static void process_incoming_migration_bh(void *opaque) |
24 | 24 | ||
25 | trace_vmstate_downtime_checkpoint("dst-precopy-bh-enter"); | 25 | trace_vmstate_downtime_checkpoint("dst-precopy-bh-enter"); |
26 | 26 | ||
27 | - /* If capability late_block_activate is set: | 27 | - /* If capability late_block_activate is set: |
28 | - * Only fire up the block code now if we're going to restart the | 28 | - * Only fire up the block code now if we're going to restart the |
29 | - * VM, else 'cont' will do it. | 29 | - * VM, else 'cont' will do it. |
30 | - * This causes file locking to happen; so we don't want it to happen | 30 | - * This causes file locking to happen; so we don't want it to happen |
31 | - * unless we really are starting the VM. | 31 | - * unless we really are starting the VM. |
32 | - */ | 32 | - */ |
33 | - if (!migrate_late_block_activate() || | 33 | - if (!migrate_late_block_activate() || |
34 | - (autostart && runstate_is_live(migration_get_target_runstate()))) { | 34 | - (autostart && runstate_is_live(migration_get_target_runstate()))) { |
35 | - /* Make sure all file formats throw away their mutable metadata. | 35 | - /* Make sure all file formats throw away their mutable metadata. |
36 | - * If we get an error here, just don't restart the VM yet. */ | 36 | - * If we get an error here, just don't restart the VM yet. */ |
37 | - bdrv_activate_all(&local_err); | 37 | - bdrv_activate_all(&local_err); |
38 | - if (local_err) { | 38 | - if (local_err) { |
39 | - error_report_err(local_err); | 39 | - error_report_err(local_err); |
40 | - local_err = NULL; | 40 | - local_err = NULL; |
41 | - autostart = false; | 41 | - autostart = false; |
42 | - } | 42 | - } |
43 | - } | 43 | - } |
44 | - | 44 | - |
45 | /* | 45 | /* |
46 | * This must happen after all error conditions are dealt with and | 46 | * This must happen after all error conditions are dealt with and |
47 | * we're sure the VM is going to be running on this host. | 47 | * we're sure the VM is going to be running on this host. |
48 | @@ -XXX,XX +XXX,XX @@ static void process_incoming_migration_bh(void *opaque) | 48 | @@ -XXX,XX +XXX,XX @@ static void process_incoming_migration_bh(void *opaque) |
49 | 49 | ||
50 | if (runstate_is_live(migration_get_target_runstate())) { | 50 | if (runstate_is_live(migration_get_target_runstate())) { |
51 | if (autostart) { | 51 | if (autostart) { |
52 | - vm_start(); | 52 | - vm_start(); |
53 | + /* | 53 | + /* |
54 | + * Block activation is always delayed until VM starts, either | 54 | + * Block activation is always delayed until VM starts, either |
55 | + * here (which means we need to start the dest VM right now..), | 55 | + * here (which means we need to start the dest VM right now..), |
56 | + * or until qmp_cont() later. | 56 | + * or until qmp_cont() later. |
57 | + * | 57 | + * |
58 | + * We used to have cap 'late-block-activate' but now we do this | 58 | + * We used to have cap 'late-block-activate' but now we do this |
59 | + * unconditionally, as it has no harm but only benefit. E.g., | 59 | + * unconditionally, as it has no harm but only benefit. E.g., |
60 | + * it's not part of migration ABI on the time of disk activation. | 60 | + * it's not part of migration ABI on the time of disk activation. |
61 | + * | 61 | + * |
62 | + * Make sure all file formats throw away their mutable | 62 | + * Make sure all file formats throw away their mutable |
63 | + * metadata. If error, don't restart the VM yet. | 63 | + * metadata. If error, don't restart the VM yet. |
64 | + */ | 64 | + */ |
65 | + bdrv_activate_all(&local_err); | 65 | + bdrv_activate_all(&local_err); |
66 | + if (local_err) { | 66 | + if (local_err) { |
67 | + error_report_err(local_err); | 67 | + error_report_err(local_err); |
68 | + local_err = NULL; | 68 | + local_err = NULL; |
69 | + } else { | 69 | + } else { |
70 | + vm_start(); | 70 | + vm_start(); |
71 | + } | 71 | + } |
72 | } else { | 72 | } else { |
73 | runstate_set(RUN_STATE_PAUSED); | 73 | runstate_set(RUN_STATE_PAUSED); |
74 | } | 74 | } |
75 | -- | 75 | -- |
76 | 2.47.0 | 76 | 2.47.0 | diff view generated by jsdifflib |
... | ... | ||
---|---|---|---|
5 | for postcopy unconditionally, just like precopy. After this patch, we | 5 | for postcopy unconditionally, just like precopy. After this patch, we |
6 | should have unified the behavior across all. | 6 | should have unified the behavior across all. |
7 | 7 | ||
8 | Signed-off-by: Peter Xu <peterx@redhat.com> | 8 | Signed-off-by: Peter Xu <peterx@redhat.com> |
9 | --- | 9 | --- |
10 | migration/savevm.c | 23 ++++++++++------------- | 10 | migration/savevm.c | 25 ++++++++++++------------- |
11 | 1 file changed, 10 insertions(+), 13 deletions(-) | 11 | 1 file changed, 12 insertions(+), 13 deletions(-) |
12 | 12 | ||
13 | diff --git a/migration/savevm.c b/migration/savevm.c | 13 | diff --git a/migration/savevm.c b/migration/savevm.c |
14 | index XXXXXXX..XXXXXXX 100644 | 14 | index XXXXXXX..XXXXXXX 100644 |
15 | --- a/migration/savevm.c | 15 | --- a/migration/savevm.c |
16 | +++ b/migration/savevm.c | 16 | +++ b/migration/savevm.c |
... | ... | ||
32 | dirty_bitmap_mig_before_vm_start(); | 32 | dirty_bitmap_mig_before_vm_start(); |
33 | 33 | ||
34 | if (autostart) { | 34 | if (autostart) { |
35 | - /* Hold onto your hats, starting the CPU */ | 35 | - /* Hold onto your hats, starting the CPU */ |
36 | - vm_start(); | 36 | - vm_start(); |
37 | + /* Make sure all file formats throw away their mutable metadata. | 37 | + /* |
38 | + * If we get an error here, just don't restart the VM yet. */ | 38 | + * Make sure all file formats throw away their mutable metadata. |
39 | + * If we get an error here, just don't restart the VM yet. | ||
40 | + */ | ||
39 | + bdrv_activate_all(&local_err); | 41 | + bdrv_activate_all(&local_err); |
40 | + trace_vmstate_downtime_checkpoint("dst-postcopy-bh-cache-invalidated"); | 42 | + trace_vmstate_downtime_checkpoint("dst-postcopy-bh-cache-invalidated"); |
41 | + if (local_err) { | 43 | + if (local_err) { |
42 | + error_report_err(local_err); | 44 | + error_report_err(local_err); |
43 | + local_err = NULL; | 45 | + local_err = NULL; |
... | ... | diff view generated by jsdifflib |
1 | Src QEMU sets block_inactive=true very early before the invalidation takes | 1 | Src QEMU sets block_inactive=true very early before the invalidation takes |
---|---|---|---|
2 | place. It means if something wrong happened during setting the flag but | 2 | place. It means if something wrong happened during setting the flag but |
3 | before reaching qemu_savevm_state_complete_precopy_non_iterable() where it | 3 | before reaching qemu_savevm_state_complete_precopy_non_iterable() where it |
4 | did the invalidation work, it'll make block_inactive flag inconsistent. | 4 | did the invalidation work, it'll make block_inactive flag inconsistent. |
5 | 5 | ||
6 | For example, think about when qemu_savevm_state_complete_precopy_iterable() | 6 | For example, think about when qemu_savevm_state_complete_precopy_iterable() |
7 | can fail: it will have block_inactive set to true even if all block drives | 7 | can fail: it will have block_inactive set to true even if all block drives |
8 | are active. | 8 | are active. |
9 | 9 | ||
10 | Fix that by only update the flag after the invalidation is done. | 10 | Fix that by only update the flag after the invalidation is done. |
11 | 11 | ||
12 | No Fixes for any commit, because it's not an issue if bdrv_activate_all() | 12 | No Fixes for any commit, because it's not an issue if bdrv_activate_all() |
13 | is re-entrant upon all-active disks - false positive block_inactive can | 13 | is re-entrant upon all-active disks - false positive block_inactive can |
14 | bring nothing more than "trying to active the blocks but they're already | 14 | bring nothing more than "trying to active the blocks but they're already |
15 | active". However let's still do it right to avoid the inconsistent flag | 15 | active". However let's still do it right to avoid the inconsistent flag |
16 | v.s. reality. | 16 | v.s. reality. |
17 | 17 | ||
18 | Signed-off-by: Peter Xu <peterx@redhat.com> | 18 | Signed-off-by: Peter Xu <peterx@redhat.com> |
19 | --- | 19 | --- |
20 | migration/migration.c | 9 +++------ | 20 | migration/migration.c | 9 +++------ |
21 | migration/savevm.c | 2 ++ | 21 | migration/savevm.c | 2 ++ |
22 | 2 files changed, 5 insertions(+), 6 deletions(-) | 22 | 2 files changed, 5 insertions(+), 6 deletions(-) |
23 | 23 | ||
24 | diff --git a/migration/migration.c b/migration/migration.c | 24 | diff --git a/migration/migration.c b/migration/migration.c |
25 | index XXXXXXX..XXXXXXX 100644 | 25 | index XXXXXXX..XXXXXXX 100644 |
26 | --- a/migration/migration.c | 26 | --- a/migration/migration.c |
27 | +++ b/migration/migration.c | 27 | +++ b/migration/migration.c |
28 | @@ -XXX,XX +XXX,XX @@ static int migration_completion_precopy(MigrationState *s, | 28 | @@ -XXX,XX +XXX,XX @@ static int migration_completion_precopy(MigrationState *s, |
29 | goto out_unlock; | 29 | goto out_unlock; |
30 | } | 30 | } |
31 | 31 | ||
32 | - /* | 32 | - /* |
33 | - * Inactivate disks except in COLO, and track that we have done so in order | 33 | - * Inactivate disks except in COLO, and track that we have done so in order |
34 | - * to remember to reactivate them if migration fails or is cancelled. | 34 | - * to remember to reactivate them if migration fails or is cancelled. |
35 | - */ | 35 | - */ |
36 | - s->block_inactive = !migrate_colo(); | 36 | - s->block_inactive = !migrate_colo(); |
37 | migration_rate_set(RATE_LIMIT_DISABLED); | 37 | migration_rate_set(RATE_LIMIT_DISABLED); |
38 | + | 38 | + |
39 | + /* Inactivate disks except in COLO */ | 39 | + /* Inactivate disks except in COLO */ |
40 | ret = qemu_savevm_state_complete_precopy(s->to_dst_file, false, | 40 | ret = qemu_savevm_state_complete_precopy(s->to_dst_file, false, |
41 | - s->block_inactive); | 41 | - s->block_inactive); |
42 | + !migrate_colo()); | 42 | + !migrate_colo()); |
43 | out_unlock: | 43 | out_unlock: |
44 | bql_unlock(); | 44 | bql_unlock(); |
45 | return ret; | 45 | return ret; |
46 | diff --git a/migration/savevm.c b/migration/savevm.c | 46 | diff --git a/migration/savevm.c b/migration/savevm.c |
47 | index XXXXXXX..XXXXXXX 100644 | 47 | index XXXXXXX..XXXXXXX 100644 |
48 | --- a/migration/savevm.c | 48 | --- a/migration/savevm.c |
49 | +++ b/migration/savevm.c | 49 | +++ b/migration/savevm.c |
50 | @@ -XXX,XX +XXX,XX @@ int qemu_savevm_state_complete_precopy_non_iterable(QEMUFile *f, | 50 | @@ -XXX,XX +XXX,XX @@ int qemu_savevm_state_complete_precopy_non_iterable(QEMUFile *f, |
51 | qemu_file_set_error(f, ret); | 51 | qemu_file_set_error(f, ret); |
52 | return ret; | 52 | return ret; |
53 | } | 53 | } |
54 | + /* Remember that we did this */ | 54 | + /* Remember that we did this */ |
55 | + s->block_inactive = true; | 55 | + s->block_inactive = true; |
56 | } | 56 | } |
57 | if (!in_postcopy) { | 57 | if (!in_postcopy) { |
58 | /* Postcopy stream will still be going */ | 58 | /* Postcopy stream will still be going */ |
59 | -- | 59 | -- |
60 | 2.47.0 | 60 | 2.47.0 | diff view generated by jsdifflib |
1 | This patch proposes a flag to maintain disk activation status globally. It | ||
---|---|---|---|
2 | mostly rewrites disk activation mgmt for QEMU, including COLO and QMP | ||
3 | command xen_save_devices_state. | ||
4 | |||
5 | Backgrounds | ||
6 | =========== | ||
7 | |||
8 | We have two problems on disk activations, one resolved, one not. | ||
9 | |||
10 | Problem 1: disk activation recover (for switchover interruptions) | ||
11 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
12 | |||
1 | When migration is either cancelled or failed during switchover, especially | 13 | When migration is either cancelled or failed during switchover, especially |
2 | when after the disks are inactivated, QEMU needs to remember re-activate | 14 | when after the disks are inactivated, QEMU needs to remember re-activate |
3 | the disks again before vm starts. | 15 | the disks again before vm starts. |
4 | 16 | ||
5 | It used to be done separately in two paths: one in qmp_migrate_cancel(), | 17 | It used to be done separately in two paths: one in qmp_migrate_cancel(), |
6 | the other one in the failure path of migration_completion(). | 18 | the other one in the failure path of migration_completion(). |
7 | 19 | ||
8 | It used to be fixed in different commits, all over the places in QEMU. So | 20 | It used to be fixed in different commits, all over the places in QEMU. So |
9 | these are the ones I see at least: | 21 | these are the relevant changes I saw, I'm not sure if it's complete list: |
10 | 22 | ||
11 | - In 2016, commit fe904ea824 ("migration: regain control of images when | 23 | - In 2016, commit fe904ea824 ("migration: regain control of images when |
12 | migration fails to complete") | 24 | migration fails to complete") |
13 | 25 | ||
14 | - In 2017, commit 1d2acc3162 ("migration: re-active images while migration | 26 | - In 2017, commit 1d2acc3162 ("migration: re-active images while migration |
15 | been canceled after inactive them") | 27 | been canceled after inactive them") |
16 | 28 | ||
17 | - In 2023, commit 6dab4c93ec ("migration: Attempt disk reactivation in | 29 | - In 2023, commit 6dab4c93ec ("migration: Attempt disk reactivation in |
18 | more failure scenarios") | 30 | more failure scenarios") |
19 | 31 | ||
20 | We could have done better on trying to solve this once and for all. Now it | 32 | Now since we have a slightly better picture maybe we can unify the |
21 | might be a good chance because we have a better whole picture now. | 33 | reactivation in a single path. |
22 | 34 | ||
23 | Put both of the error cases together now into migration_iteration_finish(), | 35 | One side benefit of doing so is, we can move the disk operation outside QMP |
24 | which will be invoked for either of the scenario. | 36 | command "migrate_cancel". It's possible that in the future we may want to |
25 | 37 | make "migrate_cancel" be OOB-compatible, while that requires the command | |
26 | At the meantime, cleanup block_inactive in a few ways: | 38 | doesn't need BQL in the first place. This will already do that and make |
27 | 39 | migrate_cancel command lightweight. | |
28 | - Rename it to block_active, which is easier to follow.. | 40 | |
29 | 41 | Problem 2: disk invalidation on top of invalidated disks | |
30 | - Maintain the flag for the whole lifecycle of QEMU. Otherwise it's not | 42 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
31 | clear on when the flag is valid or not. | 43 | |
32 | 44 | This is an unresolved bug for current QEMU. Link in "Resolves:" at the | |
33 | - Put it together with the operations, rather than updating the flag | 45 | end. It turns out besides the src switchover phase (problem 1 above), QEMU |
34 | separately. | 46 | also needs to remember block activation on destination. |
35 | 47 | ||
36 | Now we set the flag to TRUE from the start, showing block ownership of a | 48 | Consider two continuous migration in a row, where the VM was always paused. |
37 | fresh started QEMU (but so far only used in migration module). Then it can | 49 | In that scenario, the disks are not activated even until migration |
38 | be modified by migration switchover code if invalidation happened. With | 50 | completed in the 1st round. When the 2nd round starts, if QEMU doesn't |
39 | that, the flag always reflects the correct block ownership. | 51 | know the status of the disks, it needs to try inactivate the disk again. |
40 | 52 | ||
41 | NOTE: it can always be racy if there's concurrent operation to change the | 53 | Here the issue is the block layer API bdrv_inactivate_all() will crash a |
42 | block activation status (e.g., qmp_cont() right after migration failure but | 54 | QEMU if invoked on already inactive disks for the 2nd migration. For |
43 | right before block re-activate happens), but that's not avoidable even | 55 | detail, see the bug link at the end. |
44 | before this patch, so it's not part of the goal that this patch would like | 56 | |
45 | to achieve, so put aside as of now. | 57 | Implementation |
46 | 58 | ============== | |
59 | |||
60 | This patch proposes to maintain disk activation with a global flag, so we | ||
61 | know: | ||
62 | |||
63 | - If we used to inactivate disks for migration, but migration got | ||
64 | cancelled, or failed, QEMU will know it should reactivate the disks. | ||
65 | |||
66 | - On incoming side, if the disks are never activated but then another | ||
67 | migration is triggered, QEMU should be able to tell that inactivate is | ||
68 | not needed for the 2nd migration. | ||
69 | |||
70 | We used to have disk_inactive, but it only solves the 1st issue, not the | ||
71 | 2nd. Also, it's done in completely separate paths so it's extremely hard | ||
72 | to follow either how the flag changes, or the duration that the flag is | ||
73 | valid, and when we will reactivate the disks. | ||
74 | |||
75 | Convert the existing disk_inactive flag into that global flag (also invert | ||
76 | its naming), and maintain the disk activation status for the whole | ||
77 | lifecycle of qemu. That includes the incoming QEMU. | ||
78 | |||
79 | Put both of the error cases of source migration (failure, cancelled) | ||
80 | together into migration_iteration_finish(), which will be invoked for | ||
81 | either of the scenario. So from that part QEMU should behave the same as | ||
82 | before. However with such global maintenance on disk activation status, we | ||
83 | not only cleanup quite a few temporary paths that we try to maintain the | ||
84 | disk activation status (e.g. in postcopy code), meanwhile it fixes the | ||
85 | crash for problem 2 in one shot. | ||
86 | |||
87 | For freshly started QEMU, the flag is initialized to TRUE showing that the | ||
88 | QEMU owns the disks by default. | ||
89 | |||
90 | For incoming migrated QEMU, the flag will be initialized to FALSE once and | ||
91 | for all showing that the dest QEMU doesn't own the disks until switchover. | ||
92 | That is guaranteed by the "once" variable. | ||
93 | |||
94 | Resolves: https://gitlab.com/qemu-project/qemu/-/issues/2395 | ||
47 | Signed-off-by: Peter Xu <peterx@redhat.com> | 95 | Signed-off-by: Peter Xu <peterx@redhat.com> |
48 | --- | 96 | --- |
49 | migration/migration.h | 20 +++++++++- | 97 | include/migration/misc.h | 4 ++ |
50 | migration/migration.c | 86 +++++++++++++++++++++++++------------------ | 98 | migration/migration.h | 6 +-- |
51 | migration/savevm.c | 11 ++---- | 99 | migration/block-active.c | 94 ++++++++++++++++++++++++++++++++++++++++ |
52 | 3 files changed, 72 insertions(+), 45 deletions(-) | 100 | migration/colo.c | 2 +- |
53 | 101 | migration/migration.c | 80 ++++++++-------------------------- | |
102 | migration/savevm.c | 33 ++++++-------- | ||
103 | monitor/qmp-cmds.c | 8 +--- | ||
104 | migration/meson.build | 1 + | ||
105 | migration/trace-events | 3 ++ | ||
106 | 9 files changed, 140 insertions(+), 91 deletions(-) | ||
107 | create mode 100644 migration/block-active.c | ||
108 | |||
109 | diff --git a/include/migration/misc.h b/include/migration/misc.h | ||
110 | index XXXXXXX..XXXXXXX 100644 | ||
111 | --- a/include/migration/misc.h | ||
112 | +++ b/include/migration/misc.h | ||
113 | @@ -XXX,XX +XXX,XX @@ bool migration_incoming_postcopy_advised(void); | ||
114 | /* True if background snapshot is active */ | ||
115 | bool migration_in_bg_snapshot(void); | ||
116 | |||
117 | +/* Wrapper for block active/inactive operations */ | ||
118 | +bool migration_block_activate(Error **errp); | ||
119 | +bool migration_block_inactivate(void); | ||
120 | + | ||
121 | #endif | ||
54 | diff --git a/migration/migration.h b/migration/migration.h | 122 | diff --git a/migration/migration.h b/migration/migration.h |
55 | index XXXXXXX..XXXXXXX 100644 | 123 | index XXXXXXX..XXXXXXX 100644 |
56 | --- a/migration/migration.h | 124 | --- a/migration/migration.h |
57 | +++ b/migration/migration.h | 125 | +++ b/migration/migration.h |
58 | @@ -XXX,XX +XXX,XX @@ struct MigrationState { | 126 | @@ -XXX,XX +XXX,XX @@ struct MigrationState { |
59 | /* Flag set once the migration thread is running (and needs joining) */ | 127 | /* Flag set once the migration thread is running (and needs joining) */ |
60 | bool migration_thread_running; | 128 | bool migration_thread_running; |
61 | 129 | ||
62 | - /* Flag set once the migration thread called bdrv_inactivate_all */ | 130 | - /* Flag set once the migration thread called bdrv_inactivate_all */ |
63 | - bool block_inactive; | 131 | - bool block_inactive; |
64 | + /* | 132 | - |
65 | + * Migration-only cache to remember the block layer activation status. | ||
66 | + * Protected by BQL. | ||
67 | + * | ||
68 | + * We need this because.. | ||
69 | + * | ||
70 | + * - Migration can fail after block devices are invalidated (during | ||
71 | + * switchover phase). When that happens, we need to be able to | ||
72 | + * recover the block drive status by re-activating them. | ||
73 | + * | ||
74 | + * For freshly started QEMU, block_active is initialized to TRUE | ||
75 | + * reflecting the scenario where QEMU owns block device ownerships. | ||
76 | + */ | ||
77 | + bool block_active; | ||
78 | |||
79 | /* Migration is waiting for guest to unplug device */ | 133 | /* Migration is waiting for guest to unplug device */ |
80 | QemuSemaphore wait_unplug_sem; | 134 | QemuSemaphore wait_unplug_sem; |
135 | |||
81 | @@ -XXX,XX +XXX,XX @@ void migration_bitmap_sync_precopy(bool last_stage); | 136 | @@ -XXX,XX +XXX,XX @@ void migration_bitmap_sync_precopy(bool last_stage); |
82 | /* migration/block-dirty-bitmap.c */ | 137 | /* migration/block-dirty-bitmap.c */ |
83 | void dirty_bitmap_mig_init(void); | 138 | void dirty_bitmap_mig_init(void); |
84 | 139 | ||
85 | +/* Wrapper for block active/inactive operations */ | 140 | +/* migration/block-active.c */ |
86 | +bool migration_block_activate(MigrationState *s); | 141 | +void migration_block_active_setup(bool active); |
87 | +bool migration_block_inactivate(MigrationState *s); | ||
88 | + | 142 | + |
89 | #endif | 143 | #endif |
90 | diff --git a/migration/migration.c b/migration/migration.c | 144 | diff --git a/migration/block-active.c b/migration/block-active.c |
91 | index XXXXXXX..XXXXXXX 100644 | 145 | new file mode 100644 |
92 | --- a/migration/migration.c | 146 | index XXXXXXX..XXXXXXX |
93 | +++ b/migration/migration.c | 147 | --- /dev/null |
94 | @@ -XXX,XX +XXX,XX @@ static void migration_downtime_end(MigrationState *s) | 148 | +++ b/migration/block-active.c |
95 | trace_vmstate_downtime_checkpoint("src-downtime-end"); | 149 | @@ -XXX,XX +XXX,XX @@ |
96 | } | 150 | +/* |
97 | 151 | + * Block activation tracking for migration purpose | |
98 | +bool migration_block_activate(MigrationState *s) | 152 | + * |
153 | + * SPDX-License-Identifier: GPL-2.0-or-later | ||
154 | + * | ||
155 | + * Copyright (C) 2024 Red Hat, Inc. | ||
156 | + */ | ||
157 | +#include "qemu/osdep.h" | ||
158 | +#include "block/block.h" | ||
159 | +#include "qapi/error.h" | ||
160 | +#include "migration/migration.h" | ||
161 | +#include "qemu/error-report.h" | ||
162 | +#include "trace.h" | ||
163 | + | ||
164 | +/* | ||
165 | + * Migration-only cache to remember the block layer activation status. | ||
166 | + * Protected by BQL. | ||
167 | + * | ||
168 | + * We need this because.. | ||
169 | + * | ||
170 | + * - Migration can fail after block devices are invalidated (during | ||
171 | + * switchover phase). When that happens, we need to be able to recover | ||
172 | + * the block drive status by re-activating them. | ||
173 | + * | ||
174 | + * - Currently bdrv_inactivate_all() is not safe to be invoked on top of | ||
175 | + * invalidated drives (even if bdrv_activate_all() is actually safe to be | ||
176 | + * called any time!). It means remembering this could help migration to | ||
177 | + * make sure it won't invalidate twice in a row, crashing QEMU. It can | ||
178 | + * happen when we migrate a PAUSED VM from host1 to host2, then migrate | ||
179 | + * again to host3 without starting it. TODO: a cleaner solution is to | ||
180 | + * allow safe invoke of bdrv_inactivate_all() at anytime, like | ||
181 | + * bdrv_activate_all(). | ||
182 | + * | ||
183 | + * For freshly started QEMU, the flag is initialized to TRUE reflecting the | ||
184 | + * scenario where QEMU owns block device ownerships. | ||
185 | + * | ||
186 | + * For incoming QEMU taking a migration stream, the flag is initialized to | ||
187 | + * FALSE reflecting that the incoming side doesn't own the block devices, | ||
188 | + * not until switchover happens. | ||
189 | + */ | ||
190 | +static bool migration_block_active; | ||
191 | + | ||
192 | +/* Setup the disk activation status */ | ||
193 | +void migration_block_active_setup(bool active) | ||
99 | +{ | 194 | +{ |
100 | + Error *local_err = NULL; | 195 | + migration_block_active = active; |
196 | +} | ||
197 | + | ||
198 | +bool migration_block_activate(Error **errp) | ||
199 | +{ | ||
200 | + ERRP_GUARD(); | ||
101 | + | 201 | + |
102 | + assert(bql_locked()); | 202 | + assert(bql_locked()); |
103 | + | 203 | + |
104 | + if (s->block_active) { | 204 | + if (migration_block_active) { |
205 | + trace_migration_block_activation("active-skipped"); | ||
105 | + return true; | 206 | + return true; |
106 | + } | 207 | + } |
107 | + | 208 | + |
108 | + bdrv_activate_all(&local_err); | 209 | + trace_migration_block_activation("active"); |
109 | + if (local_err) { | 210 | + |
110 | + error_report_err(local_err); | 211 | + bdrv_activate_all(errp); |
212 | + if (*errp) { | ||
213 | + error_report_err(error_copy(*errp)); | ||
111 | + return false; | 214 | + return false; |
112 | + } | 215 | + } |
113 | + | 216 | + |
114 | + s->block_active = true; | 217 | + migration_block_active = true; |
115 | + return true; | 218 | + return true; |
116 | +} | 219 | +} |
117 | + | 220 | + |
118 | +bool migration_block_inactivate(MigrationState *s) | 221 | +bool migration_block_inactivate(void) |
119 | +{ | 222 | +{ |
120 | + int ret; | 223 | + int ret; |
121 | + | 224 | + |
122 | + assert(bql_locked()); | 225 | + assert(bql_locked()); |
123 | + | 226 | + |
124 | + if (!s->block_active) { | 227 | + if (!migration_block_active) { |
228 | + trace_migration_block_activation("inactive-skipped"); | ||
125 | + return true; | 229 | + return true; |
126 | + } | 230 | + } |
127 | + | 231 | + |
232 | + trace_migration_block_activation("inactive"); | ||
233 | + | ||
128 | + ret = bdrv_inactivate_all(); | 234 | + ret = bdrv_inactivate_all(); |
129 | + if (ret) { | 235 | + if (ret) { |
130 | + error_report("%s: bdrv_inactivate_all() failed: %d\n", | 236 | + error_report("%s: bdrv_inactivate_all() failed: %d", |
131 | + __func__, ret); | 237 | + __func__, ret); |
132 | + return false; | 238 | + return false; |
133 | + } | 239 | + } |
134 | + | 240 | + |
135 | + s->block_active = false; | 241 | + migration_block_active = false; |
136 | + return true; | 242 | + return true; |
137 | +} | 243 | +} |
138 | + | 244 | diff --git a/migration/colo.c b/migration/colo.c |
139 | static bool migration_needs_multiple_sockets(void) | 245 | index XXXXXXX..XXXXXXX 100644 |
246 | --- a/migration/colo.c | ||
247 | +++ b/migration/colo.c | ||
248 | @@ -XXX,XX +XXX,XX @@ static void *colo_process_incoming_thread(void *opaque) | ||
249 | |||
250 | /* Make sure all file formats throw away their mutable metadata */ | ||
251 | bql_lock(); | ||
252 | - bdrv_activate_all(&local_err); | ||
253 | + migration_block_activate(&local_err); | ||
254 | bql_unlock(); | ||
255 | if (local_err) { | ||
256 | error_report_err(local_err); | ||
257 | diff --git a/migration/migration.c b/migration/migration.c | ||
258 | index XXXXXXX..XXXXXXX 100644 | ||
259 | --- a/migration/migration.c | ||
260 | +++ b/migration/migration.c | ||
261 | @@ -XXX,XX +XXX,XX @@ static void qemu_start_incoming_migration(const char *uri, bool has_channels, | ||
262 | |||
263 | static void process_incoming_migration_bh(void *opaque) | ||
140 | { | 264 | { |
141 | return migrate_multifd() || migrate_postcopy_preempt(); | 265 | - Error *local_err = NULL; |
266 | MigrationIncomingState *mis = opaque; | ||
267 | |||
268 | trace_vmstate_downtime_checkpoint("dst-precopy-bh-enter"); | ||
269 | @@ -XXX,XX +XXX,XX @@ static void process_incoming_migration_bh(void *opaque) | ||
270 | * Make sure all file formats throw away their mutable | ||
271 | * metadata. If error, don't restart the VM yet. | ||
272 | */ | ||
273 | - bdrv_activate_all(&local_err); | ||
274 | - if (local_err) { | ||
275 | - error_report_err(local_err); | ||
276 | - local_err = NULL; | ||
277 | - } else { | ||
278 | + if (migration_block_activate(NULL)) { | ||
279 | vm_start(); | ||
280 | } | ||
281 | } else { | ||
142 | @@ -XXX,XX +XXX,XX @@ static void migrate_fd_cancel(MigrationState *s) | 282 | @@ -XXX,XX +XXX,XX @@ static void migrate_fd_cancel(MigrationState *s) |
143 | } | 283 | } |
144 | } | 284 | } |
145 | } | 285 | } |
146 | - if (s->state == MIGRATION_STATUS_CANCELLING && s->block_inactive) { | 286 | - if (s->state == MIGRATION_STATUS_CANCELLING && s->block_inactive) { |
... | ... | ||
154 | - } | 294 | - } |
155 | - } | 295 | - } |
156 | } | 296 | } |
157 | 297 | ||
158 | void migration_add_notifier_mode(NotifierWithReturn *notify, | 298 | void migration_add_notifier_mode(NotifierWithReturn *notify, |
299 | @@ -XXX,XX +XXX,XX @@ void qmp_migrate_incoming(const char *uri, bool has_channels, | ||
300 | return; | ||
301 | } | ||
302 | |||
303 | + /* | ||
304 | + * Newly setup incoming QEMU. Mark the block active state to reflect | ||
305 | + * that the src currently owns the disks. | ||
306 | + */ | ||
307 | + migration_block_active_setup(false); | ||
308 | + | ||
309 | once = false; | ||
310 | } | ||
311 | |||
312 | @@ -XXX,XX +XXX,XX @@ static int postcopy_start(MigrationState *ms, Error **errp) | ||
313 | QIOChannelBuffer *bioc; | ||
314 | QEMUFile *fb; | ||
315 | uint64_t bandwidth = migrate_max_postcopy_bandwidth(); | ||
316 | - bool restart_block = false; | ||
317 | int cur_state = MIGRATION_STATUS_ACTIVE; | ||
318 | |||
319 | if (migrate_postcopy_preempt()) { | ||
320 | @@ -XXX,XX +XXX,XX @@ static int postcopy_start(MigrationState *ms, Error **errp) | ||
321 | goto fail; | ||
322 | } | ||
323 | |||
324 | - ret = bdrv_inactivate_all(); | ||
325 | - if (ret < 0) { | ||
326 | - error_setg_errno(errp, -ret, "%s: Failed in bdrv_inactivate_all()", | ||
327 | - __func__); | ||
328 | + if (!migration_block_inactivate()) { | ||
329 | + error_setg(errp, "%s: Failed in bdrv_inactivate_all()", __func__); | ||
330 | goto fail; | ||
331 | } | ||
332 | - restart_block = true; | ||
333 | |||
334 | /* | ||
335 | * Cause any non-postcopiable, but iterative devices to | ||
336 | @@ -XXX,XX +XXX,XX @@ static int postcopy_start(MigrationState *ms, Error **errp) | ||
337 | goto fail_closefb; | ||
338 | } | ||
339 | |||
340 | - restart_block = false; | ||
341 | - | ||
342 | /* Now send that blob */ | ||
343 | if (qemu_savevm_send_packaged(ms->to_dst_file, bioc->data, bioc->usage)) { | ||
344 | error_setg(errp, "%s: Failed to send packaged data", __func__); | ||
345 | @@ -XXX,XX +XXX,XX @@ fail_closefb: | ||
346 | fail: | ||
347 | migrate_set_state(&ms->state, MIGRATION_STATUS_POSTCOPY_ACTIVE, | ||
348 | MIGRATION_STATUS_FAILED); | ||
349 | - if (restart_block) { | ||
350 | - /* A failure happened early enough that we know the destination hasn't | ||
351 | - * accessed block devices, so we're safe to recover. | ||
352 | - */ | ||
353 | - Error *local_err = NULL; | ||
354 | - | ||
355 | - bdrv_activate_all(&local_err); | ||
356 | - if (local_err) { | ||
357 | - error_report_err(local_err); | ||
358 | - } | ||
359 | - } | ||
360 | + migration_block_activate(NULL); | ||
361 | migration_call_notifiers(ms, MIG_EVENT_PRECOPY_FAILED, NULL); | ||
362 | bql_unlock(); | ||
363 | return -1; | ||
159 | @@ -XXX,XX +XXX,XX @@ static void migration_completion_postcopy(MigrationState *s) | 364 | @@ -XXX,XX +XXX,XX @@ static void migration_completion_postcopy(MigrationState *s) |
160 | trace_migration_completion_postcopy_end_after_complete(); | 365 | trace_migration_completion_postcopy_end_after_complete(); |
161 | } | 366 | } |
162 | 367 | ||
163 | -static void migration_completion_failed(MigrationState *s, | 368 | -static void migration_completion_failed(MigrationState *s, |
... | ... | ||
204 | case MIGRATION_STATUS_CANCELLING: | 409 | case MIGRATION_STATUS_CANCELLING: |
205 | + /* | 410 | + /* |
206 | + * Re-activate the block drives if they're inactivated. Note, COLO | 411 | + * Re-activate the block drives if they're inactivated. Note, COLO |
207 | + * shouldn't use block_active at all, so it should be no-op there. | 412 | + * shouldn't use block_active at all, so it should be no-op there. |
208 | + */ | 413 | + */ |
209 | + migration_block_activate(s); | 414 | + migration_block_activate(NULL); |
210 | if (runstate_is_live(s->vm_old_state)) { | 415 | if (runstate_is_live(s->vm_old_state)) { |
211 | if (!runstate_check(RUN_STATE_SHUTDOWN)) { | 416 | if (!runstate_check(RUN_STATE_SHUTDOWN)) { |
212 | vm_start(); | 417 | vm_start(); |
213 | @@ -XXX,XX +XXX,XX @@ static void migration_instance_init(Object *obj) | 418 | @@ -XXX,XX +XXX,XX @@ static void migration_instance_init(Object *obj) |
214 | ms->state = MIGRATION_STATUS_NONE; | 419 | ms->state = MIGRATION_STATUS_NONE; |
215 | ms->mbps = -1; | 420 | ms->mbps = -1; |
216 | ms->pages_per_second = -1; | 421 | ms->pages_per_second = -1; |
217 | + /* Freshly started QEMU owns all the block devices */ | 422 | + /* Freshly started QEMU owns all the block devices */ |
218 | + ms->block_active = true; | 423 | + migration_block_active_setup(true); |
219 | qemu_sem_init(&ms->pause_sem, 0); | 424 | qemu_sem_init(&ms->pause_sem, 0); |
220 | qemu_mutex_init(&ms->error_mutex); | 425 | qemu_mutex_init(&ms->error_mutex); |
221 | 426 | ||
222 | diff --git a/migration/savevm.c b/migration/savevm.c | 427 | diff --git a/migration/savevm.c b/migration/savevm.c |
223 | index XXXXXXX..XXXXXXX 100644 | 428 | index XXXXXXX..XXXXXXX 100644 |
224 | --- a/migration/savevm.c | 429 | --- a/migration/savevm.c |
225 | +++ b/migration/savevm.c | 430 | +++ b/migration/savevm.c |
226 | @@ -XXX,XX +XXX,XX @@ int qemu_savevm_state_complete_precopy_non_iterable(QEMUFile *f, | 431 | @@ -XXX,XX +XXX,XX @@ int qemu_savevm_state_complete_precopy_non_iterable(QEMUFile *f, |
432 | } | ||
433 | |||
227 | if (inactivate_disks) { | 434 | if (inactivate_disks) { |
228 | /* Inactivate before sending QEMU_VM_EOF so that the | 435 | - /* Inactivate before sending QEMU_VM_EOF so that the |
229 | * bdrv_activate_all() on the other end won't fail. */ | 436 | - * bdrv_activate_all() on the other end won't fail. */ |
230 | - ret = bdrv_inactivate_all(); | 437 | - ret = bdrv_inactivate_all(); |
231 | - if (ret) { | 438 | - if (ret) { |
232 | - error_setg(&local_err, "%s: bdrv_inactivate_all() failed (%d)", | 439 | - error_setg(&local_err, "%s: bdrv_inactivate_all() failed (%d)", |
233 | - __func__, ret); | 440 | - __func__, ret); |
234 | + if (!migration_block_inactivate(ms)) { | 441 | + /* |
442 | + * Inactivate before sending QEMU_VM_EOF so that the | ||
443 | + * bdrv_activate_all() on the other end won't fail. | ||
444 | + */ | ||
445 | + if (!migration_block_inactivate()) { | ||
235 | + error_setg(&local_err, "%s: bdrv_inactivate_all() failed", | 446 | + error_setg(&local_err, "%s: bdrv_inactivate_all() failed", |
236 | + __func__); | 447 | + __func__); |
237 | migrate_set_error(ms, local_err); | 448 | migrate_set_error(ms, local_err); |
238 | error_report_err(local_err); | 449 | error_report_err(local_err); |
239 | - qemu_file_set_error(f, ret); | 450 | - qemu_file_set_error(f, ret); |
... | ... | ||
243 | - /* Remember that we did this */ | 454 | - /* Remember that we did this */ |
244 | - s->block_inactive = true; | 455 | - s->block_inactive = true; |
245 | } | 456 | } |
246 | if (!in_postcopy) { | 457 | if (!in_postcopy) { |
247 | /* Postcopy stream will still be going */ | 458 | /* Postcopy stream will still be going */ |
459 | @@ -XXX,XX +XXX,XX @@ static int loadvm_postcopy_handle_listen(MigrationIncomingState *mis) | ||
460 | |||
461 | static void loadvm_postcopy_handle_run_bh(void *opaque) | ||
462 | { | ||
463 | - Error *local_err = NULL; | ||
464 | MigrationIncomingState *mis = opaque; | ||
465 | |||
466 | trace_vmstate_downtime_checkpoint("dst-postcopy-bh-enter"); | ||
467 | @@ -XXX,XX +XXX,XX @@ static void loadvm_postcopy_handle_run_bh(void *opaque) | ||
468 | * Make sure all file formats throw away their mutable metadata. | ||
469 | * If we get an error here, just don't restart the VM yet. | ||
470 | */ | ||
471 | - bdrv_activate_all(&local_err); | ||
472 | + bool success = migration_block_activate(NULL); | ||
473 | + | ||
474 | trace_vmstate_downtime_checkpoint("dst-postcopy-bh-cache-invalidated"); | ||
475 | - if (local_err) { | ||
476 | - error_report_err(local_err); | ||
477 | - local_err = NULL; | ||
478 | - } else { | ||
479 | + | ||
480 | + if (success) { | ||
481 | vm_start(); | ||
482 | } | ||
483 | } else { | ||
484 | @@ -XXX,XX +XXX,XX @@ void qmp_xen_save_devices_state(const char *filename, bool has_live, bool live, | ||
485 | * side of the migration take control of the images. | ||
486 | */ | ||
487 | if (live && !saved_vm_running) { | ||
488 | - ret = bdrv_inactivate_all(); | ||
489 | - if (ret) { | ||
490 | - error_setg(errp, "%s: bdrv_inactivate_all() failed (%d)", | ||
491 | - __func__, ret); | ||
492 | - } | ||
493 | + migration_block_inactivate(); | ||
494 | } | ||
495 | } | ||
496 | |||
497 | diff --git a/monitor/qmp-cmds.c b/monitor/qmp-cmds.c | ||
498 | index XXXXXXX..XXXXXXX 100644 | ||
499 | --- a/monitor/qmp-cmds.c | ||
500 | +++ b/monitor/qmp-cmds.c | ||
501 | @@ -XXX,XX +XXX,XX @@ | ||
502 | #include "qapi/type-helpers.h" | ||
503 | #include "hw/mem/memory-device.h" | ||
504 | #include "hw/intc/intc.h" | ||
505 | +#include "migration/misc.h" | ||
506 | |||
507 | NameInfo *qmp_query_name(Error **errp) | ||
508 | { | ||
509 | @@ -XXX,XX +XXX,XX @@ void qmp_cont(Error **errp) | ||
510 | * Continuing after completed migration. Images have been | ||
511 | * inactivated to allow the destination to take control. Need to | ||
512 | * get control back now. | ||
513 | - * | ||
514 | - * If there are no inactive block nodes (e.g. because the VM was | ||
515 | - * just paused rather than completing a migration), | ||
516 | - * bdrv_inactivate_all() simply doesn't do anything. | ||
517 | */ | ||
518 | - bdrv_activate_all(&local_err); | ||
519 | - if (local_err) { | ||
520 | + if (!migration_block_activate(&local_err)) { | ||
521 | error_propagate(errp, local_err); | ||
522 | return; | ||
523 | } | ||
524 | diff --git a/migration/meson.build b/migration/meson.build | ||
525 | index XXXXXXX..XXXXXXX 100644 | ||
526 | --- a/migration/meson.build | ||
527 | +++ b/migration/meson.build | ||
528 | @@ -XXX,XX +XXX,XX @@ migration_files = files( | ||
529 | |||
530 | system_ss.add(files( | ||
531 | 'block-dirty-bitmap.c', | ||
532 | + 'block-active.c', | ||
533 | 'channel.c', | ||
534 | 'channel-block.c', | ||
535 | 'cpu-throttle.c', | ||
536 | diff --git a/migration/trace-events b/migration/trace-events | ||
537 | index XXXXXXX..XXXXXXX 100644 | ||
538 | --- a/migration/trace-events | ||
539 | +++ b/migration/trace-events | ||
540 | @@ -XXX,XX +XXX,XX @@ migration_pagecache_insert(void) "Error allocating page" | ||
541 | # cpu-throttle.c | ||
542 | cpu_throttle_set(int new_throttle_pct) "set guest CPU throttled by %d%%" | ||
543 | cpu_throttle_dirty_sync(void) "" | ||
544 | + | ||
545 | +# block-active.c | ||
546 | +migration_block_activation(const char *name) "%s" | ||
248 | -- | 547 | -- |
249 | 2.47.0 | 548 | 2.47.0 | diff view generated by jsdifflib |
Deleted patch | |||
---|---|---|---|
1 | Extend the usage of such API to the receiver side, because such status | ||
2 | maintenance is also necessary there. | ||
3 | 1 | ||
4 | It's needed to avoid bdrv_inactivate_all() not being invoked when the | ||
5 | drives are already inactive. When invoked, it can crash QEMU. See the | ||
6 | issue link for more information. | ||
7 | |||
8 | So it means we need to start remember the block activation status not only | ||
9 | on src, but also on dest. | ||
10 | |||
11 | One thing tricky here is, when we need to consider incoming QEMU, we need | ||
12 | to also consider its initial status as incoming side. Due to the fact that | ||
13 | before switchover the incoming QEMU doesn't own the disks, we need to set | ||
14 | initial state to FALSE for a incoming QEMU. In this case, postcopy recover | ||
15 | doesn't count. | ||
16 | |||
17 | Resolves: https://gitlab.com/qemu-project/qemu/-/issues/2395 | ||
18 | Signed-off-by: Peter Xu <peterx@redhat.com> | ||
19 | --- | ||
20 | migration/migration.h | 13 +++++++++++++ | ||
21 | migration/migration.c | 13 +++++++------ | ||
22 | 2 files changed, 20 insertions(+), 6 deletions(-) | ||
23 | |||
24 | diff --git a/migration/migration.h b/migration/migration.h | ||
25 | index XXXXXXX..XXXXXXX 100644 | ||
26 | --- a/migration/migration.h | ||
27 | +++ b/migration/migration.h | ||
28 | @@ -XXX,XX +XXX,XX @@ struct MigrationState { | ||
29 | * switchover phase). When that happens, we need to be able to | ||
30 | * recover the block drive status by re-activating them. | ||
31 | * | ||
32 | + * - Currently bdrv_inactivate_all() is not safe to be invoked on top | ||
33 | + * of invalidated drives (even if bdrv_activate_all() is actually | ||
34 | + * safe to be called any time!). It means remembering this could | ||
35 | + * help migration to make sure it won't invalidate twice in a row, | ||
36 | + * crashing QEMU. It can happen when we migrate a PAUSED VM from | ||
37 | + * host1 to host2, then migrate again to host3 without starting it. | ||
38 | + * TODO: a cleaner solution is to allow safe invoke of | ||
39 | + * bdrv_inactivate_all() at anytime, like bdrv_activate_all(). | ||
40 | + * | ||
41 | * For freshly started QEMU, block_active is initialized to TRUE | ||
42 | * reflecting the scenario where QEMU owns block device ownerships. | ||
43 | + * | ||
44 | + * For incoming QEMU taking a migration stream, block_active is set to | ||
45 | + * FALSE reflecting that the incoming side doesn't own the block | ||
46 | + * devices, not until switchover happens. | ||
47 | */ | ||
48 | bool block_active; | ||
49 | |||
50 | diff --git a/migration/migration.c b/migration/migration.c | ||
51 | index XXXXXXX..XXXXXXX 100644 | ||
52 | --- a/migration/migration.c | ||
53 | +++ b/migration/migration.c | ||
54 | @@ -XXX,XX +XXX,XX @@ migration_incoming_state_setup(MigrationIncomingState *mis, Error **errp) | ||
55 | return false; | ||
56 | } | ||
57 | |||
58 | + /* | ||
59 | + * Newly setup QEMU, prepared for incoming migration. Mark the block | ||
60 | + * active state to reflect that the src currently owns the disks. | ||
61 | + */ | ||
62 | + migrate_get_current()->block_active = false; | ||
63 | + | ||
64 | migrate_set_state(&mis->state, current, MIGRATION_STATUS_SETUP); | ||
65 | return true; | ||
66 | } | ||
67 | @@ -XXX,XX +XXX,XX @@ static void qemu_start_incoming_migration(const char *uri, bool has_channels, | ||
68 | |||
69 | static void process_incoming_migration_bh(void *opaque) | ||
70 | { | ||
71 | - Error *local_err = NULL; | ||
72 | MigrationIncomingState *mis = opaque; | ||
73 | |||
74 | trace_vmstate_downtime_checkpoint("dst-precopy-bh-enter"); | ||
75 | @@ -XXX,XX +XXX,XX @@ static void process_incoming_migration_bh(void *opaque) | ||
76 | * Make sure all file formats throw away their mutable | ||
77 | * metadata. If error, don't restart the VM yet. | ||
78 | */ | ||
79 | - bdrv_activate_all(&local_err); | ||
80 | - if (local_err) { | ||
81 | - error_report_err(local_err); | ||
82 | - local_err = NULL; | ||
83 | - } else { | ||
84 | + if (migration_block_activate(migrate_get_current())) { | ||
85 | vm_start(); | ||
86 | } | ||
87 | } else { | ||
88 | -- | ||
89 | 2.47.0 | diff view generated by jsdifflib |
Deleted patch | |||
---|---|---|---|
1 | Postcopy also has similar error handling for re-activation of block | ||
2 | devices. Use the same API as precopy to do the re-activation, for both src | ||
3 | & dst sides. | ||
4 | 1 | ||
5 | Signed-off-by: Peter Xu <peterx@redhat.com> | ||
6 | --- | ||
7 | migration/migration.c | 22 +++------------------- | ||
8 | migration/savevm.c | 10 ++++------ | ||
9 | 2 files changed, 7 insertions(+), 25 deletions(-) | ||
10 | |||
11 | diff --git a/migration/migration.c b/migration/migration.c | ||
12 | index XXXXXXX..XXXXXXX 100644 | ||
13 | --- a/migration/migration.c | ||
14 | +++ b/migration/migration.c | ||
15 | @@ -XXX,XX +XXX,XX @@ static int postcopy_start(MigrationState *ms, Error **errp) | ||
16 | QIOChannelBuffer *bioc; | ||
17 | QEMUFile *fb; | ||
18 | uint64_t bandwidth = migrate_max_postcopy_bandwidth(); | ||
19 | - bool restart_block = false; | ||
20 | int cur_state = MIGRATION_STATUS_ACTIVE; | ||
21 | |||
22 | if (migrate_postcopy_preempt()) { | ||
23 | @@ -XXX,XX +XXX,XX @@ static int postcopy_start(MigrationState *ms, Error **errp) | ||
24 | goto fail; | ||
25 | } | ||
26 | |||
27 | - ret = bdrv_inactivate_all(); | ||
28 | - if (ret < 0) { | ||
29 | - error_setg_errno(errp, -ret, "%s: Failed in bdrv_inactivate_all()", | ||
30 | - __func__); | ||
31 | + if (!migration_block_inactivate(ms)) { | ||
32 | + error_setg(errp, "%s: Failed in bdrv_inactivate_all()", __func__); | ||
33 | goto fail; | ||
34 | } | ||
35 | - restart_block = true; | ||
36 | |||
37 | /* | ||
38 | * Cause any non-postcopiable, but iterative devices to | ||
39 | @@ -XXX,XX +XXX,XX @@ static int postcopy_start(MigrationState *ms, Error **errp) | ||
40 | goto fail_closefb; | ||
41 | } | ||
42 | |||
43 | - restart_block = false; | ||
44 | - | ||
45 | /* Now send that blob */ | ||
46 | if (qemu_savevm_send_packaged(ms->to_dst_file, bioc->data, bioc->usage)) { | ||
47 | error_setg(errp, "%s: Failed to send packaged data", __func__); | ||
48 | @@ -XXX,XX +XXX,XX @@ fail_closefb: | ||
49 | fail: | ||
50 | migrate_set_state(&ms->state, MIGRATION_STATUS_POSTCOPY_ACTIVE, | ||
51 | MIGRATION_STATUS_FAILED); | ||
52 | - if (restart_block) { | ||
53 | - /* A failure happened early enough that we know the destination hasn't | ||
54 | - * accessed block devices, so we're safe to recover. | ||
55 | - */ | ||
56 | - Error *local_err = NULL; | ||
57 | - | ||
58 | - bdrv_activate_all(&local_err); | ||
59 | - if (local_err) { | ||
60 | - error_report_err(local_err); | ||
61 | - } | ||
62 | - } | ||
63 | + migration_block_activate(ms); | ||
64 | migration_call_notifiers(ms, MIG_EVENT_PRECOPY_FAILED, NULL); | ||
65 | bql_unlock(); | ||
66 | return -1; | ||
67 | diff --git a/migration/savevm.c b/migration/savevm.c | ||
68 | index XXXXXXX..XXXXXXX 100644 | ||
69 | --- a/migration/savevm.c | ||
70 | +++ b/migration/savevm.c | ||
71 | @@ -XXX,XX +XXX,XX @@ static int loadvm_postcopy_handle_listen(MigrationIncomingState *mis) | ||
72 | |||
73 | static void loadvm_postcopy_handle_run_bh(void *opaque) | ||
74 | { | ||
75 | - Error *local_err = NULL; | ||
76 | MigrationIncomingState *mis = opaque; | ||
77 | |||
78 | trace_vmstate_downtime_checkpoint("dst-postcopy-bh-enter"); | ||
79 | @@ -XXX,XX +XXX,XX @@ static void loadvm_postcopy_handle_run_bh(void *opaque) | ||
80 | if (autostart) { | ||
81 | /* Make sure all file formats throw away their mutable metadata. | ||
82 | * If we get an error here, just don't restart the VM yet. */ | ||
83 | - bdrv_activate_all(&local_err); | ||
84 | + bool success = migration_block_activate(migrate_get_current()); | ||
85 | + | ||
86 | trace_vmstate_downtime_checkpoint("dst-postcopy-bh-cache-invalidated"); | ||
87 | - if (local_err) { | ||
88 | - error_report_err(local_err); | ||
89 | - local_err = NULL; | ||
90 | - } else { | ||
91 | + | ||
92 | + if (success) { | ||
93 | vm_start(); | ||
94 | } | ||
95 | } else { | ||
96 | -- | ||
97 | 2.47.0 | diff view generated by jsdifflib |
Deleted patch | |||
---|---|---|---|
1 | From: Fabiano Rosas <farosas@suse.de> | ||
2 | 1 | ||
3 | Stop using hardcoded strings for -serial so we can in the next patches | ||
4 | perform more than one migration in a row. Having the serial path | ||
5 | hardcoded means we cannot reuse the code when dst becomes the new src. | ||
6 | |||
7 | Signed-off-by: Fabiano Rosas <farosas@suse.de> | ||
8 | Link: https://lore.kernel.org/r/20241125144612.16194-3-farosas@suse.de | ||
9 | Signed-off-by: Peter Xu <peterx@redhat.com> | ||
10 | --- | ||
11 | tests/qtest/migration-helpers.h | 2 + | ||
12 | tests/qtest/migration-helpers.c | 8 ++++ | ||
13 | tests/qtest/migration-test.c | 68 ++++++++++++++++++--------------- | ||
14 | 3 files changed, 48 insertions(+), 30 deletions(-) | ||
15 | |||
16 | diff --git a/tests/qtest/migration-helpers.h b/tests/qtest/migration-helpers.h | ||
17 | index XXXXXXX..XXXXXXX 100644 | ||
18 | --- a/tests/qtest/migration-helpers.h | ||
19 | +++ b/tests/qtest/migration-helpers.h | ||
20 | @@ -XXX,XX +XXX,XX @@ typedef struct QTestMigrationState { | ||
21 | bool resume_seen; | ||
22 | bool suspend_seen; | ||
23 | bool suspend_me; | ||
24 | + char *serial; | ||
25 | } QTestMigrationState; | ||
26 | |||
27 | bool migrate_watch_for_events(QTestState *who, const char *name, | ||
28 | @@ -XXX,XX +XXX,XX @@ static inline bool probe_o_direct_support(const char *tmpfs) | ||
29 | #endif | ||
30 | void migration_test_add(const char *path, void (*fn)(void)); | ||
31 | void migration_event_wait(QTestState *s, const char *target); | ||
32 | +char *migrate_get_unique_serial(const char *tmpfs); | ||
33 | |||
34 | #endif /* MIGRATION_HELPERS_H */ | ||
35 | diff --git a/tests/qtest/migration-helpers.c b/tests/qtest/migration-helpers.c | ||
36 | index XXXXXXX..XXXXXXX 100644 | ||
37 | --- a/tests/qtest/migration-helpers.c | ||
38 | +++ b/tests/qtest/migration-helpers.c | ||
39 | @@ -XXX,XX +XXX,XX @@ void migration_event_wait(QTestState *s, const char *target) | ||
40 | qobject_unref(response); | ||
41 | } while (!found); | ||
42 | } | ||
43 | + | ||
44 | +char *migrate_get_unique_serial(const char *tmpfs) | ||
45 | +{ | ||
46 | + static int i; | ||
47 | + | ||
48 | + assert(i < INT_MAX); | ||
49 | + return g_strdup_printf("%s/serial_%d", tmpfs, i++); | ||
50 | +} | ||
51 | diff --git a/tests/qtest/migration-test.c b/tests/qtest/migration-test.c | ||
52 | index XXXXXXX..XXXXXXX 100644 | ||
53 | --- a/tests/qtest/migration-test.c | ||
54 | +++ b/tests/qtest/migration-test.c | ||
55 | @@ -XXX,XX +XXX,XX @@ static void bootfile_create(char *dir, bool suspend_me) | ||
56 | * we get an 'A' followed by an endless string of 'B's | ||
57 | * but on the destination we won't have the A (unless we enabled suspend/resume) | ||
58 | */ | ||
59 | -static void wait_for_serial(const char *side) | ||
60 | +static void wait_for_serial(const char *serialpath) | ||
61 | { | ||
62 | - g_autofree char *serialpath = g_strdup_printf("%s/%s", tmpfs, side); | ||
63 | FILE *serialfile = fopen(serialpath, "r"); | ||
64 | |||
65 | do { | ||
66 | @@ -XXX,XX +XXX,XX @@ static void wait_for_serial(const char *side) | ||
67 | break; | ||
68 | |||
69 | default: | ||
70 | - fprintf(stderr, "Unexpected %d on %s serial\n", readvalue, side); | ||
71 | + fprintf(stderr, "Unexpected %d on %s\n", readvalue, serialpath); | ||
72 | g_assert_not_reached(); | ||
73 | } | ||
74 | } while (true); | ||
75 | @@ -XXX,XX +XXX,XX @@ static int test_migrate_start(QTestState **from, QTestState **to, | ||
76 | |||
77 | src_state = (QTestMigrationState) { }; | ||
78 | src_state.suspend_me = args->suspend_me; | ||
79 | + src_state.serial = migrate_get_unique_serial(tmpfs); | ||
80 | |||
81 | cmd_source = g_strdup_printf("-accel kvm%s -accel tcg " | ||
82 | "-machine %s,%s " | ||
83 | "-name source,debug-threads=on " | ||
84 | "-m %s " | ||
85 | - "-serial file:%s/src_serial " | ||
86 | + "-serial file:%s " | ||
87 | "%s %s %s %s %s", | ||
88 | kvm_opts ? kvm_opts : "", | ||
89 | machine, machine_opts, | ||
90 | - memory_size, tmpfs, | ||
91 | + memory_size, src_state.serial, | ||
92 | arch_opts ? arch_opts : "", | ||
93 | arch_source ? arch_source : "", | ||
94 | shmem_opts ? shmem_opts : "", | ||
95 | @@ -XXX,XX +XXX,XX @@ static int test_migrate_start(QTestState **from, QTestState **to, | ||
96 | } | ||
97 | |||
98 | dst_state = (QTestMigrationState) { }; | ||
99 | + dst_state.serial = migrate_get_unique_serial(tmpfs); | ||
100 | |||
101 | cmd_target = g_strdup_printf("-accel kvm%s -accel tcg " | ||
102 | "-machine %s,%s " | ||
103 | "-name target,debug-threads=on " | ||
104 | "-m %s " | ||
105 | - "-serial file:%s/dest_serial " | ||
106 | + "-serial file:%s " | ||
107 | "-incoming %s " | ||
108 | "%s %s %s %s %s", | ||
109 | kvm_opts ? kvm_opts : "", | ||
110 | machine, machine_opts, | ||
111 | - memory_size, tmpfs, uri, | ||
112 | + memory_size, dst_state.serial, uri, | ||
113 | arch_opts ? arch_opts : "", | ||
114 | arch_target ? arch_target : "", | ||
115 | shmem_opts ? shmem_opts : "", | ||
116 | @@ -XXX,XX +XXX,XX @@ static void test_migrate_end(QTestState *from, QTestState *to, bool test_dest) | ||
117 | qtest_quit(to); | ||
118 | |||
119 | cleanup("migsocket"); | ||
120 | - cleanup("src_serial"); | ||
121 | - cleanup("dest_serial"); | ||
122 | + unlink(src_state.serial); | ||
123 | + g_free(src_state.serial); | ||
124 | + unlink(dst_state.serial); | ||
125 | + g_free(dst_state.serial); | ||
126 | cleanup(FILE_TEST_FILENAME); | ||
127 | } | ||
128 | |||
129 | @@ -XXX,XX +XXX,XX @@ static int migrate_postcopy_prepare(QTestState **from_ptr, | ||
130 | " 'port': '0' } } ] } }"); | ||
131 | |||
132 | /* Wait for the first serial output from the source */ | ||
133 | - wait_for_serial("src_serial"); | ||
134 | + wait_for_serial(src_state.serial); | ||
135 | wait_for_suspend(from, &src_state); | ||
136 | |||
137 | migrate_qmp(from, to, NULL, NULL, "{}"); | ||
138 | @@ -XXX,XX +XXX,XX @@ static void migrate_postcopy_complete(QTestState *from, QTestState *to, | ||
139 | } | ||
140 | |||
141 | /* Make sure we get at least one "B" on destination */ | ||
142 | - wait_for_serial("dest_serial"); | ||
143 | + wait_for_serial(dst_state.serial); | ||
144 | |||
145 | if (uffd_feature_thread_id) { | ||
146 | read_blocktime(to); | ||
147 | @@ -XXX,XX +XXX,XX @@ static void test_precopy_common(MigrateCommon *args) | ||
148 | |||
149 | /* Wait for the first serial output from the source */ | ||
150 | if (args->result == MIG_TEST_SUCCEED) { | ||
151 | - wait_for_serial("src_serial"); | ||
152 | + wait_for_serial(src_state.serial); | ||
153 | wait_for_suspend(from, &src_state); | ||
154 | } | ||
155 | |||
156 | @@ -XXX,XX +XXX,XX @@ static void test_precopy_common(MigrateCommon *args) | ||
157 | qtest_qmp_assert_success(to, "{'execute': 'system_wakeup'}"); | ||
158 | } | ||
159 | |||
160 | - wait_for_serial("dest_serial"); | ||
161 | + wait_for_serial(dst_state.serial); | ||
162 | } | ||
163 | |||
164 | finish: | ||
165 | @@ -XXX,XX +XXX,XX @@ static void test_file_common(MigrateCommon *args, bool stop_src) | ||
166 | } | ||
167 | |||
168 | migrate_ensure_converge(from); | ||
169 | - wait_for_serial("src_serial"); | ||
170 | + wait_for_serial(src_state.serial); | ||
171 | |||
172 | if (stop_src) { | ||
173 | qtest_qmp_assert_success(from, "{ 'execute' : 'stop'}"); | ||
174 | @@ -XXX,XX +XXX,XX @@ static void test_file_common(MigrateCommon *args, bool stop_src) | ||
175 | } | ||
176 | wait_for_resume(to, &dst_state); | ||
177 | |||
178 | - wait_for_serial("dest_serial"); | ||
179 | + wait_for_serial(dst_state.serial); | ||
180 | |||
181 | if (check_offset) { | ||
182 | file_check_offset_region(); | ||
183 | @@ -XXX,XX +XXX,XX @@ static void test_ignore_shared(void) | ||
184 | migrate_set_capability(to, "x-ignore-shared", true); | ||
185 | |||
186 | /* Wait for the first serial output from the source */ | ||
187 | - wait_for_serial("src_serial"); | ||
188 | + wait_for_serial(src_state.serial); | ||
189 | |||
190 | migrate_qmp(from, to, uri, NULL, "{}"); | ||
191 | |||
192 | @@ -XXX,XX +XXX,XX @@ static void test_ignore_shared(void) | ||
193 | |||
194 | qtest_qmp_eventwait(to, "RESUME"); | ||
195 | |||
196 | - wait_for_serial("dest_serial"); | ||
197 | + wait_for_serial(dst_state.serial); | ||
198 | wait_for_migration_complete(from); | ||
199 | |||
200 | /* Check whether shared RAM has been really skipped */ | ||
201 | @@ -XXX,XX +XXX,XX @@ static void do_test_validate_uuid(MigrateStart *args, bool should_fail) | ||
202 | migrate_set_capability(from, "validate-uuid", true); | ||
203 | |||
204 | /* Wait for the first serial output from the source */ | ||
205 | - wait_for_serial("src_serial"); | ||
206 | + wait_for_serial(src_state.serial); | ||
207 | |||
208 | migrate_qmp(from, to, uri, NULL, "{}"); | ||
209 | |||
210 | @@ -XXX,XX +XXX,XX @@ static void do_test_validate_uri_channel(MigrateCommon *args) | ||
211 | } | ||
212 | |||
213 | /* Wait for the first serial output from the source */ | ||
214 | - wait_for_serial("src_serial"); | ||
215 | + wait_for_serial(src_state.serial); | ||
216 | |||
217 | /* | ||
218 | * 'uri' and 'channels' validation is checked even before the migration | ||
219 | @@ -XXX,XX +XXX,XX @@ static void test_migrate_auto_converge(void) | ||
220 | migrate_set_capability(from, "pause-before-switchover", true); | ||
221 | |||
222 | /* Wait for the first serial output from the source */ | ||
223 | - wait_for_serial("src_serial"); | ||
224 | + wait_for_serial(src_state.serial); | ||
225 | |||
226 | migrate_qmp(from, to, uri, NULL, "{}"); | ||
227 | |||
228 | @@ -XXX,XX +XXX,XX @@ static void test_migrate_auto_converge(void) | ||
229 | |||
230 | qtest_qmp_eventwait(to, "RESUME"); | ||
231 | |||
232 | - wait_for_serial("dest_serial"); | ||
233 | + wait_for_serial(dst_state.serial); | ||
234 | wait_for_migration_complete(from); | ||
235 | |||
236 | test_migrate_end(from, to, true); | ||
237 | @@ -XXX,XX +XXX,XX @@ static void test_multifd_tcp_cancel(void) | ||
238 | migrate_incoming_qmp(to, "tcp:127.0.0.1:0", "{}"); | ||
239 | |||
240 | /* Wait for the first serial output from the source */ | ||
241 | - wait_for_serial("src_serial"); | ||
242 | + wait_for_serial(src_state.serial); | ||
243 | |||
244 | migrate_qmp(from, to, NULL, NULL, "{}"); | ||
245 | |||
246 | @@ -XXX,XX +XXX,XX @@ static void test_multifd_tcp_cancel(void) | ||
247 | /* Make sure QEMU process "to" exited */ | ||
248 | qtest_set_expected_status(to, EXIT_FAILURE); | ||
249 | qtest_wait_qemu(to); | ||
250 | - qtest_quit(to); | ||
251 | + unlink(dst_state.serial); | ||
252 | + g_free(dst_state.serial); | ||
253 | |||
254 | /* | ||
255 | * Ensure the source QEMU finishes its cancellation process before we | ||
256 | @@ -XXX,XX +XXX,XX @@ static void test_multifd_tcp_cancel(void) | ||
257 | wait_for_stop(from, &src_state); | ||
258 | qtest_qmp_eventwait(to2, "RESUME"); | ||
259 | |||
260 | - wait_for_serial("dest_serial"); | ||
261 | + wait_for_serial(dst_state.serial); | ||
262 | wait_for_migration_complete(from); | ||
263 | test_migrate_end(from, to2, true); | ||
264 | } | ||
265 | @@ -XXX,XX +XXX,XX @@ static QTestState *dirtylimit_start_vm(void) | ||
266 | QTestState *vm = NULL; | ||
267 | g_autofree gchar *cmd = NULL; | ||
268 | |||
269 | + src_state = (QTestMigrationState) { }; | ||
270 | + src_state.serial = migrate_get_unique_serial(tmpfs); | ||
271 | + | ||
272 | bootfile_create(tmpfs, false); | ||
273 | cmd = g_strdup_printf("-accel kvm,dirty-ring-size=4096 " | ||
274 | "-name dirtylimit-test,debug-threads=on " | ||
275 | "-m 150M -smp 1 " | ||
276 | - "-serial file:%s/vm_serial " | ||
277 | + "-serial file:%s " | ||
278 | "-drive file=%s,format=raw ", | ||
279 | - tmpfs, bootpath); | ||
280 | + src_state.serial, bootpath); | ||
281 | |||
282 | vm = qtest_init(cmd); | ||
283 | return vm; | ||
284 | @@ -XXX,XX +XXX,XX @@ static QTestState *dirtylimit_start_vm(void) | ||
285 | static void dirtylimit_stop_vm(QTestState *vm) | ||
286 | { | ||
287 | qtest_quit(vm); | ||
288 | - cleanup("vm_serial"); | ||
289 | + unlink(src_state.serial); | ||
290 | + g_free(src_state.serial); | ||
291 | } | ||
292 | |||
293 | static void test_vcpu_dirty_limit(void) | ||
294 | @@ -XXX,XX +XXX,XX @@ static void test_vcpu_dirty_limit(void) | ||
295 | vm = dirtylimit_start_vm(); | ||
296 | |||
297 | /* Wait for the first serial output from the vm*/ | ||
298 | - wait_for_serial("vm_serial"); | ||
299 | + wait_for_serial(src_state.serial); | ||
300 | |||
301 | /* Do dirtyrate measurement with calc time equals 1s */ | ||
302 | calc_dirty_rate(vm, 1); | ||
303 | @@ -XXX,XX +XXX,XX @@ static void migrate_dirty_limit_wait_showup(QTestState *from, | ||
304 | migrate_set_capability(from, "pause-before-switchover", true); | ||
305 | |||
306 | /* Wait for the serial output from the source */ | ||
307 | - wait_for_serial("src_serial"); | ||
308 | + wait_for_serial(src_state.serial); | ||
309 | } | ||
310 | |||
311 | /* | ||
312 | @@ -XXX,XX +XXX,XX @@ static void test_migrate_dirty_limit(void) | ||
313 | |||
314 | qtest_qmp_eventwait(to, "RESUME"); | ||
315 | |||
316 | - wait_for_serial("dest_serial"); | ||
317 | + wait_for_serial(dst_state.serial); | ||
318 | wait_for_migration_complete(from); | ||
319 | |||
320 | test_migrate_end(from, to, true); | ||
321 | -- | ||
322 | 2.47.0 | diff view generated by jsdifflib |
Deleted patch | |||
---|---|---|---|
1 | From: Fabiano Rosas <farosas@suse.de> | ||
2 | 1 | ||
3 | We don't always want to cleanup both VMs at the same time. One example | ||
4 | is the multifd cancel test, where there's a second migration reusing | ||
5 | the source VM. The next patches will add another instance, keeping the | ||
6 | destination VM instead. | ||
7 | |||
8 | Extract the cleanup routine from test_migrate_end() into another | ||
9 | function so it can be reused. | ||
10 | |||
11 | Signed-off-by: Fabiano Rosas <farosas@suse.de> | ||
12 | Link: https://lore.kernel.org/r/20241125144612.16194-4-farosas@suse.de | ||
13 | Signed-off-by: Peter Xu <peterx@redhat.com> | ||
14 | --- | ||
15 | tests/qtest/migration-test.c | 35 +++++++++++++++++++++-------------- | ||
16 | 1 file changed, 21 insertions(+), 14 deletions(-) | ||
17 | |||
18 | diff --git a/tests/qtest/migration-test.c b/tests/qtest/migration-test.c | ||
19 | index XXXXXXX..XXXXXXX 100644 | ||
20 | --- a/tests/qtest/migration-test.c | ||
21 | +++ b/tests/qtest/migration-test.c | ||
22 | @@ -XXX,XX +XXX,XX @@ static int test_migrate_start(QTestState **from, QTestState **to, | ||
23 | return 0; | ||
24 | } | ||
25 | |||
26 | +static void migrate_cleanup(QTestState *from, QTestState *to) | ||
27 | +{ | ||
28 | + if (from) { | ||
29 | + qtest_quit(from); | ||
30 | + unlink(src_state.serial); | ||
31 | + g_free(src_state.serial); | ||
32 | + } | ||
33 | + | ||
34 | + if (to) { | ||
35 | + qtest_quit(to); | ||
36 | + unlink(dst_state.serial); | ||
37 | + g_free(dst_state.serial); | ||
38 | + } | ||
39 | + | ||
40 | + cleanup("migsocket"); | ||
41 | + cleanup(FILE_TEST_FILENAME); | ||
42 | +} | ||
43 | + | ||
44 | static void test_migrate_end(QTestState *from, QTestState *to, bool test_dest) | ||
45 | { | ||
46 | unsigned char dest_byte_a, dest_byte_b, dest_byte_c, dest_byte_d; | ||
47 | |||
48 | - qtest_quit(from); | ||
49 | - | ||
50 | - if (test_dest) { | ||
51 | + if (to && test_dest) { | ||
52 | qtest_memread(to, start_address, &dest_byte_a, 1); | ||
53 | |||
54 | /* Destination still running, wait for a byte to change */ | ||
55 | @@ -XXX,XX +XXX,XX @@ static void test_migrate_end(QTestState *from, QTestState *to, bool test_dest) | ||
56 | check_guests_ram(to); | ||
57 | } | ||
58 | |||
59 | - qtest_quit(to); | ||
60 | - | ||
61 | - cleanup("migsocket"); | ||
62 | - unlink(src_state.serial); | ||
63 | - g_free(src_state.serial); | ||
64 | - unlink(dst_state.serial); | ||
65 | - g_free(dst_state.serial); | ||
66 | - cleanup(FILE_TEST_FILENAME); | ||
67 | + migrate_cleanup(from, to); | ||
68 | } | ||
69 | |||
70 | #ifdef CONFIG_GNUTLS | ||
71 | @@ -XXX,XX +XXX,XX @@ static void test_multifd_tcp_cancel(void) | ||
72 | |||
73 | /* Make sure QEMU process "to" exited */ | ||
74 | qtest_set_expected_status(to, EXIT_FAILURE); | ||
75 | - qtest_wait_qemu(to); | ||
76 | - unlink(dst_state.serial); | ||
77 | - g_free(dst_state.serial); | ||
78 | + migrate_cleanup(NULL, to); | ||
79 | |||
80 | /* | ||
81 | * Ensure the source QEMU finishes its cancellation process before we | ||
82 | -- | ||
83 | 2.47.0 | diff view generated by jsdifflib |
Deleted patch | |||
---|---|---|---|
1 | From: Fabiano Rosas <farosas@suse.de> | ||
2 | 1 | ||
3 | Add a framework for running migrations back and forth between two | ||
4 | guests (aka ping-pong migration). We have seen a couple of bugs that | ||
5 | only reproduce when a guest is migrated and the destination machine is | ||
6 | used as the source of a new migration. | ||
7 | |||
8 | Add a simple test that does 2 migrations and another test that uses | ||
9 | the late-block-activate capability, which is one area where a bug has | ||
10 | been found. | ||
11 | |||
12 | Note that this does not reuse any of the existing tests | ||
13 | (e.g. test_precopy_common), because there is too much ambiguity | ||
14 | regarding how to handle the hooks, args->result, stop/continue, etc. | ||
15 | |||
16 | Signed-off-by: Fabiano Rosas <farosas@suse.de> | ||
17 | Link: https://lore.kernel.org/r/20241125144612.16194-6-farosas@suse.de | ||
18 | Signed-off-by: Peter Xu <peterx@redhat.com> | ||
19 | --- | ||
20 | tests/qtest/migration-test.c | 121 +++++++++++++++++++++++++++++++++++ | ||
21 | 1 file changed, 121 insertions(+) | ||
22 | |||
23 | diff --git a/tests/qtest/migration-test.c b/tests/qtest/migration-test.c | ||
24 | index XXXXXXX..XXXXXXX 100644 | ||
25 | --- a/tests/qtest/migration-test.c | ||
26 | +++ b/tests/qtest/migration-test.c | ||
27 | @@ -XXX,XX +XXX,XX @@ finish: | ||
28 | test_migrate_end(from, to, args->result == MIG_TEST_SUCCEED); | ||
29 | } | ||
30 | |||
31 | +static void migrate_dst_becomes_src(QTestState **from, QTestState **to, | ||
32 | + QTestMigrationState *src_state, | ||
33 | + QTestMigrationState *dst_state) | ||
34 | +{ | ||
35 | + *from = *to; | ||
36 | + | ||
37 | + *src_state = (QTestMigrationState) { }; | ||
38 | + src_state->serial = dst_state->serial; | ||
39 | + qtest_qmp_set_event_callback(*from, migrate_watch_for_events, src_state); | ||
40 | + | ||
41 | + src_state->stop_seen = dst_state->stop_seen; | ||
42 | +} | ||
43 | + | ||
44 | +static void test_precopy_ping_pong_common(MigrateCommon *args, int n, | ||
45 | + bool late_block_activate) | ||
46 | +{ | ||
47 | + QTestState *from, *to; | ||
48 | + bool keep_paused = false; | ||
49 | + | ||
50 | + g_assert(!args->live); | ||
51 | + | ||
52 | + g_test_message("Migrating back and forth %d times", n); | ||
53 | + | ||
54 | + for (int i = 0; i < n; i++) { | ||
55 | + g_test_message("%s (%d/%d)", i % 2 ? "pong" : "ping", i + 1, n); | ||
56 | + | ||
57 | + if (test_migrate_start(&from, &to, args->listen_uri, &args->start)) { | ||
58 | + return; | ||
59 | + } | ||
60 | + | ||
61 | + if (late_block_activate) { | ||
62 | + migrate_set_capability(from, "late-block-activate", true); | ||
63 | + migrate_set_capability(to, "late-block-activate", true); | ||
64 | + | ||
65 | + /* | ||
66 | + * The late-block-activate capability expects that the | ||
67 | + * management layer will issue a qmp_continue command on | ||
68 | + * the destination once the migration finishes. In order | ||
69 | + * to test the capability properly, make sure the test is | ||
70 | + * not issuing spurious continue commands. | ||
71 | + */ | ||
72 | + keep_paused = true; | ||
73 | + } | ||
74 | + | ||
75 | + if (!src_state.stop_seen) { | ||
76 | + wait_for_serial(src_state.serial); | ||
77 | + } | ||
78 | + | ||
79 | + /* because of !args->live */ | ||
80 | + qtest_qmp_assert_success(from, "{ 'execute' : 'stop'}"); | ||
81 | + wait_for_stop(from, &src_state); | ||
82 | + | ||
83 | + migrate_ensure_converge(from); | ||
84 | + migrate_qmp(from, to, args->connect_uri, args->connect_channels, "{}"); | ||
85 | + | ||
86 | + wait_for_migration_complete(from); | ||
87 | + wait_for_migration_complete(to); | ||
88 | + | ||
89 | + /* note check_guests_ram() requires a stopped guest */ | ||
90 | + check_guests_ram(to); | ||
91 | + | ||
92 | + if (keep_paused) { | ||
93 | + /* | ||
94 | + * Nothing issued continue on the destination, reflect | ||
95 | + * that the guest is paused in the dst_state. | ||
96 | + */ | ||
97 | + dst_state.stop_seen = true; | ||
98 | + } else { | ||
99 | + qtest_qmp_assert_success(to, "{ 'execute' : 'cont'}"); | ||
100 | + wait_for_serial(dst_state.serial); | ||
101 | + dst_state.stop_seen = false; | ||
102 | + } | ||
103 | + | ||
104 | + /* | ||
105 | + * Last iteration, let the guest run to make sure there's no | ||
106 | + * memory corruption. The test_migrate_end() below will check | ||
107 | + * the memory one last time. | ||
108 | + */ | ||
109 | + if (i == n - 1) { | ||
110 | + qtest_qmp_assert_success(to, "{ 'execute' : 'cont'}"); | ||
111 | + wait_for_serial(dst_state.serial); | ||
112 | + dst_state.stop_seen = false; | ||
113 | + break; | ||
114 | + } | ||
115 | + | ||
116 | + /* | ||
117 | + * Prepare for migrating back: clear the original source and | ||
118 | + * switch source & destination. | ||
119 | + */ | ||
120 | + migrate_cleanup(from, NULL); | ||
121 | + migrate_dst_becomes_src(&from, &to, &src_state, &dst_state); | ||
122 | + | ||
123 | + args->start.only_target = true; | ||
124 | + } | ||
125 | + | ||
126 | + test_migrate_end(from, to, true); | ||
127 | +} | ||
128 | + | ||
129 | static void file_dirty_offset_region(void) | ||
130 | { | ||
131 | g_autofree char *path = g_strdup_printf("%s/%s", tmpfs, FILE_TEST_FILENAME); | ||
132 | @@ -XXX,XX +XXX,XX @@ static void test_migrate_dirty_limit(void) | ||
133 | test_migrate_end(from, to, true); | ||
134 | } | ||
135 | |||
136 | +static void test_precopy_ping_pong(void) | ||
137 | +{ | ||
138 | + MigrateCommon args = { | ||
139 | + .listen_uri = "tcp:127.0.0.1:0", | ||
140 | + }; | ||
141 | + | ||
142 | + test_precopy_ping_pong_common(&args, 2, false); | ||
143 | +} | ||
144 | + | ||
145 | +static void test_precopy_ping_pong_late_block(void) | ||
146 | +{ | ||
147 | + MigrateCommon args = { | ||
148 | + .listen_uri = "tcp:127.0.0.1:0", | ||
149 | + }; | ||
150 | + | ||
151 | + test_precopy_ping_pong_common(&args, 2, true); | ||
152 | +} | ||
153 | + | ||
154 | static bool kvm_dirty_ring_supported(void) | ||
155 | { | ||
156 | #if defined(__linux__) && defined(HOST_X86_64) | ||
157 | @@ -XXX,XX +XXX,XX @@ int main(int argc, char **argv) | ||
158 | } | ||
159 | } | ||
160 | |||
161 | + migration_test_add("/migration/precopy/ping-pong/plain", | ||
162 | + test_precopy_ping_pong); | ||
163 | + migration_test_add("/migration/precopy/ping-pong/late-block-activate", | ||
164 | + test_precopy_ping_pong_late_block); | ||
165 | + | ||
166 | ret = g_test_run(); | ||
167 | |||
168 | g_assert_cmpint(ret, ==, 0); | ||
169 | -- | ||
170 | 2.47.0 | diff view generated by jsdifflib |