Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
Signed-off-by: Dietmar Maurer <dietmar@proxmox.com>
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
-[improve aborting]
+[SR: improve aborting
+ register yank before migration_incoming_state_destroy]
Signed-off-by: Stefan Reiter <s.reiter@proxmox.com>
[FE: further improve aborting
- adapt to removal of QEMUFileOps]
+ adapt to removal of QEMUFileOps
+ improve condition for entering final stage
+ adapt to QAPI and other changes for 8.2]
Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
---
hmp-commands-info.hx | 13 +
- hmp-commands.hx | 33 +++
+ hmp-commands.hx | 17 ++
include/migration/snapshot.h | 2 +
- include/monitor/hmp.h | 5 +
+ include/monitor/hmp.h | 3 +
migration/meson.build | 1 +
- migration/savevm-async.c | 531 +++++++++++++++++++++++++++++++++++
- monitor/hmp-cmds.c | 57 ++++
+ migration/savevm-async.c | 534 +++++++++++++++++++++++++++++++++++
+ monitor/hmp-cmds.c | 38 +++
qapi/migration.json | 34 +++
- qapi/misc.json | 32 +++
+ qapi/misc.json | 16 ++
qemu-options.hx | 12 +
- softmmu/vl.c | 10 +
- 11 files changed, 730 insertions(+)
+ system/vl.c | 10 +
+ 11 files changed, 680 insertions(+)
create mode 100644 migration/savevm-async.c
diff --git a/hmp-commands-info.hx b/hmp-commands-info.hx
-index 754b1e8408..489c524e9e 100644
+index f5b37eb74a..10fdd822e0 100644
--- a/hmp-commands-info.hx
+++ b/hmp-commands-info.hx
-@@ -540,6 +540,19 @@ SRST
+@@ -525,6 +525,19 @@ SRST
Show current migration parameters.
ERST
.name = "balloon",
.args_type = "",
diff --git a/hmp-commands.hx b/hmp-commands.hx
-index 673e39a697..039be0033d 100644
+index 765349ed14..893c3bd240 100644
--- a/hmp-commands.hx
+++ b/hmp-commands.hx
-@@ -1815,3 +1815,36 @@ SRST
- Dump the FDT in dtb format to *filename*.
+@@ -1875,3 +1875,20 @@ SRST
+ List event channels in the guest
ERST
#endif
+
+ },
+
+ {
-+ .name = "snapshot-drive",
-+ .args_type = "device:s,name:s",
-+ .params = "device name",
-+ .help = "Create internal snapshot.",
-+ .cmd = hmp_snapshot_drive,
-+ },
-+
-+ {
-+ .name = "delete-drive-snapshot",
-+ .args_type = "device:s,name:s",
-+ .params = "device name",
-+ .help = "Delete internal snapshot.",
-+ .cmd = hmp_delete_drive_snapshot,
-+ },
-+
-+ {
+ .name = "savevm-end",
+ .args_type = "",
+ .params = "",
+
#endif
diff --git a/include/monitor/hmp.h b/include/monitor/hmp.h
-index dfbc0c9a2f..440f86aba8 100644
+index 13f9a2dedb..7a7def7530 100644
--- a/include/monitor/hmp.h
+++ b/include/monitor/hmp.h
-@@ -27,6 +27,7 @@ void hmp_info_status(Monitor *mon, const QDict *qdict);
+@@ -28,6 +28,7 @@ void hmp_info_status(Monitor *mon, const QDict *qdict);
void hmp_info_uuid(Monitor *mon, const QDict *qdict);
void hmp_info_chardev(Monitor *mon, const QDict *qdict);
void hmp_info_mice(Monitor *mon, const QDict *qdict);
void hmp_info_migrate(Monitor *mon, const QDict *qdict);
void hmp_info_migrate_capabilities(Monitor *mon, const QDict *qdict);
void hmp_info_migrate_parameters(Monitor *mon, const QDict *qdict);
-@@ -81,6 +82,10 @@ void hmp_netdev_add(Monitor *mon, const QDict *qdict);
- void hmp_netdev_del(Monitor *mon, const QDict *qdict);
- void hmp_getfd(Monitor *mon, const QDict *qdict);
- void hmp_closefd(Monitor *mon, const QDict *qdict);
+@@ -94,6 +95,8 @@ void hmp_closefd(Monitor *mon, const QDict *qdict);
+ void hmp_mouse_move(Monitor *mon, const QDict *qdict);
+ void hmp_mouse_button(Monitor *mon, const QDict *qdict);
+ void hmp_mouse_set(Monitor *mon, const QDict *qdict);
+void hmp_savevm_start(Monitor *mon, const QDict *qdict);
-+void hmp_snapshot_drive(Monitor *mon, const QDict *qdict);
-+void hmp_delete_drive_snapshot(Monitor *mon, const QDict *qdict);
+void hmp_savevm_end(Monitor *mon, const QDict *qdict);
void hmp_sendkey(Monitor *mon, const QDict *qdict);
void coroutine_fn hmp_screendump(Monitor *mon, const QDict *qdict);
void hmp_chardev_add(Monitor *mon, const QDict *qdict);
diff --git a/migration/meson.build b/migration/meson.build
-index 8cac83c06c..0842d00cd2 100644
+index 0e689eac09..8f9d122187 100644
--- a/migration/meson.build
+++ b/migration/meson.build
-@@ -24,6 +24,7 @@ softmmu_ss.add(files(
- 'multifd-zlib.c',
+@@ -27,6 +27,7 @@ system_ss.add(files(
+ 'options.c',
'postcopy-ram.c',
'savevm.c',
+ 'savevm-async.c',
'socket.c',
'tls.c',
- ), gnutls)
+ 'threadinfo.c',
diff --git a/migration/savevm-async.c b/migration/savevm-async.c
new file mode 100644
-index 0000000000..05d394c0e2
+index 0000000000..8f63c4c637
--- /dev/null
+++ b/migration/savevm-async.c
-@@ -0,0 +1,531 @@
+@@ -0,0 +1,534 @@
+#include "qemu/osdep.h"
+#include "migration/channel-savevm-async.h"
+#include "migration/migration.h"
++#include "migration/migration-stats.h"
++#include "migration/options.h"
+#include "migration/savevm.h"
+#include "migration/snapshot.h"
+#include "migration/global_state.h"
+#include "qemu/timer.h"
+#include "qemu/main-loop.h"
+#include "qemu/rcu.h"
++#include "qemu/yank.h"
+
+/* #define DEBUG_SAVEVM_STATE */
+
+ info->bytes = s->bs_pos;
+ switch (s->state) {
+ case SAVE_STATE_ERROR:
-+ info->has_status = true;
+ info->status = g_strdup("failed");
+ info->has_total_time = true;
+ info->total_time = s->total_time;
+ if (s->error) {
-+ info->has_error = true;
+ info->error = g_strdup(error_get_pretty(s->error));
+ }
+ break;
+ case SAVE_STATE_ACTIVE:
-+ info->has_status = true;
+ info->status = g_strdup("active");
+ info->has_total_time = true;
+ info->total_time = qemu_clock_get_ms(QEMU_CLOCK_REALTIME)
+ - s->total_time;
+ break;
+ case SAVE_STATE_COMPLETED:
-+ info->has_status = true;
+ info->status = g_strdup("completed");
+ info->has_total_time = true;
+ info->total_time = s->total_time;
+ return ret;
+}
+
-+static void save_snapshot_error(const char *fmt, ...)
++static void G_GNUC_PRINTF(1, 2) save_snapshot_error(const char *fmt, ...)
+{
+ va_list ap;
+ char *msg;
+ (void)qemu_savevm_state_complete_precopy(snap_state.file, false, false);
+ ret = qemu_file_get_error(snap_state.file);
+ if (ret < 0) {
-+ save_snapshot_error("qemu_savevm_state_iterate error %d", ret);
++ save_snapshot_error("qemu_savevm_state_complete_precopy error %d", ret);
+ }
+ }
+
+ }
+
+ while (snap_state.state == SAVE_STATE_ACTIVE) {
-+ uint64_t pending_size, pend_precopy, pend_compatible, pend_postcopy;
++ uint64_t pending_size, pend_precopy, pend_postcopy;
++ uint64_t threshold = 400 * 1000;
+
-+ /* pending is expected to be called without iothread lock */
++ /*
++ * pending_{estimate,exact} are expected to be called without iothread
++ * lock. Similar to what is done in migration.c, call the exact variant
++ * only once pend_precopy in the estimate is below the threshold.
++ */
+ qemu_mutex_unlock_iothread();
-+ qemu_savevm_state_pending(snap_state.file, 0, &pend_precopy, &pend_compatible, &pend_postcopy);
++ qemu_savevm_state_pending_estimate(&pend_precopy, &pend_postcopy);
++ if (pend_precopy <= threshold) {
++ qemu_savevm_state_pending_exact(&pend_precopy, &pend_postcopy);
++ }
+ qemu_mutex_lock_iothread();
++ pending_size = pend_precopy + pend_postcopy;
+
-+ pending_size = pend_precopy + pend_compatible + pend_postcopy;
-+
-+ maxlen = blk_getlength(snap_state.target) - 30*1024*1024;
++ /*
++ * A guest reaching this cutoff is dirtying lots of RAM. It should be
++ * large enough so that the guest can't dirty this much between the
++ * check and the guest actually being stopped, but it should be small
++ * enough to avoid long downtimes for non-hibernation snapshots.
++ */
++ maxlen = blk_getlength(snap_state.target) - 100*1024*1024;
+
-+ if (pending_size > 400000 && snap_state.bs_pos + pending_size < maxlen) {
++ /* Note that there is no progress for pend_postcopy when iterating */
++ if (pend_precopy > threshold && snap_state.bs_pos + pending_size < maxlen) {
+ ret = qemu_savevm_state_iterate(snap_state.file, false);
+ if (ret < 0) {
+ save_snapshot_error("qemu_savevm_state_iterate error %d", ret);
+ DPRINTF("savevm iterate pending size %lu ret %d\n", pending_size, ret);
+ } else {
+ qemu_system_wakeup_request(QEMU_WAKEUP_REASON_OTHER, NULL);
-+ ret = global_state_store();
-+ if (ret) {
-+ save_snapshot_error("global_state_store error %d", ret);
-+ break;
-+ }
++ global_state_store();
+
+ DPRINTF("savevm iterate complete\n");
+ break;
+ * so move there now and after every flush.
+ */
+ aio_co_reschedule_self(qemu_get_aio_context());
-+ for (bs = bdrv_first(&it); bs; bs = bdrv_next(&it)) {
++ bdrv_graph_co_rdlock();
++ bs = bdrv_first(&it);
++ bdrv_graph_co_rdunlock();
++ while (bs) {
+ /* target has BDRV_O_NO_FLUSH, no sense calling bdrv_flush on it */
-+ if (bs == blk_bs(snap_state.target)) {
-+ continue;
-+ }
-+
-+ AioContext *bs_ctx = bdrv_get_aio_context(bs);
-+ if (bs_ctx != qemu_get_aio_context()) {
-+ DPRINTF("savevm: async flushing drive %s\n", bs->filename);
-+ aio_co_reschedule_self(bs_ctx);
-+ bdrv_flush(bs);
-+ aio_co_reschedule_self(qemu_get_aio_context());
++ if (bs != blk_bs(snap_state.target)) {
++ AioContext *bs_ctx = bdrv_get_aio_context(bs);
++ if (bs_ctx != qemu_get_aio_context()) {
++ DPRINTF("savevm: async flushing drive %s\n", bs->filename);
++ aio_co_reschedule_self(bs_ctx);
++ bdrv_graph_co_rdlock();
++ bdrv_flush(bs);
++ bdrv_graph_co_rdunlock();
++ aio_co_reschedule_self(qemu_get_aio_context());
++ }
+ }
++ bdrv_graph_co_rdlock();
++ bs = bdrv_next(&it);
++ bdrv_graph_co_rdunlock();
+ }
+
+ DPRINTF("timing: async flushing took %ld ms\n",
+ qemu_bh_schedule(snap_state.finalize_bh);
+}
+
-+void qmp_savevm_start(bool has_statefile, const char *statefile, Error **errp)
++void qmp_savevm_start(const char *statefile, Error **errp)
+{
+ Error *local_err = NULL;
+ MigrationState *ms = migrate_get_current();
+ return;
+ }
+
-+ if (migrate_use_block()) {
++ if (migrate_block()) {
+ error_set(errp, ERROR_CLASS_GENERIC_ERROR,
+ "Block migration and snapshots are incompatible");
+ return;
+ snap_state.error = NULL;
+ }
+
-+ if (!has_statefile) {
++ if (!statefile) {
+ vm_stop(RUN_STATE_SAVE_VM);
+ snap_state.state = SAVE_STATE_COMPLETED;
+ return;
+ * State is cleared in process_savevm_co, but has to be initialized
+ * here (blocking main thread, from QMP) to avoid race conditions.
+ */
-+ migrate_init(ms);
-+ memset(&ram_counters, 0, sizeof(ram_counters));
++ if (migrate_init(ms, errp)) {
++ return;
++ }
++ memset(&mig_stats, 0, sizeof(mig_stats));
+ ms->to_dst_file = snap_state.file;
+
+ error_setg(&snap_state.blocker, "block device is in use by savevm");
+ snap_state.state = SAVE_STATE_ACTIVE;
+ snap_state.finalize_bh = qemu_bh_new(process_savevm_finalize, &snap_state);
+ snap_state.co = qemu_coroutine_create(&process_savevm_co, NULL);
-+ qemu_mutex_unlock_iothread();
+ qemu_savevm_state_header(snap_state.file);
+ qemu_savevm_state_setup(snap_state.file);
-+ qemu_mutex_lock_iothread();
+
+ /* Async processing from here on out happens in iohandler context, so let
+ * the target bdrv have its home there.
+ DPRINTF("savevm-end: cleanup done\n");
+}
+
-+// FIXME: Deprecated
-+void qmp_snapshot_drive(const char *device, const char *name, Error **errp)
-+{
-+ // Compatibility to older qemu-server.
-+ qmp_blockdev_snapshot_internal_sync(device, name, errp);
-+}
-+
-+// FIXME: Deprecated
-+void qmp_delete_drive_snapshot(const char *device, const char *name,
-+ Error **errp)
-+{
-+ // Compatibility to older qemu-server.
-+ (void)qmp_blockdev_snapshot_delete_internal_sync(device, false, NULL,
-+ true, name, errp);
-+}
-+
+int load_snapshot_from_blockdev(const char *filename, Error **errp)
+{
+ BlockBackend *be;
+ dirty_bitmap_mig_before_vm_start();
+
+ qemu_fclose(f);
++
++ /* state_destroy assumes a real migration which would have added a yank */
++ yank_register_instance(MIGRATION_YANK_INSTANCE, &error_abort);
++
+ migration_incoming_state_destroy();
+ if (ret < 0) {
+ error_setg_errno(errp, -ret, "Error while loading VM state");
+ return ret;
+}
diff --git a/monitor/hmp-cmds.c b/monitor/hmp-cmds.c
-index 480b798963..cfebfd1db5 100644
+index 871898ac46..ef4634e5c1 100644
--- a/monitor/hmp-cmds.c
+++ b/monitor/hmp-cmds.c
-@@ -1906,6 +1906,63 @@ void hmp_info_memory_devices(Monitor *mon, const QDict *qdict)
- hmp_handle_error(mon, err);
- }
+@@ -22,6 +22,7 @@
+ #include "monitor/monitor-internal.h"
+ #include "qapi/error.h"
+ #include "qapi/qapi-commands-control.h"
++#include "qapi/qapi-commands-migration.h"
+ #include "qapi/qapi-commands-misc.h"
+ #include "qapi/qmp/qdict.h"
+ #include "qemu/cutils.h"
+@@ -443,3 +444,40 @@ void hmp_info_mtree(Monitor *mon, const QDict *qdict)
+ mtree_info(flatview, dispatch_tree, owner, disabled);
+ }
++
+void hmp_savevm_start(Monitor *mon, const QDict *qdict)
+{
+ Error *errp = NULL;
+ const char *statefile = qdict_get_try_str(qdict, "statefile");
+
-+ qmp_savevm_start(statefile != NULL, statefile, &errp);
-+ hmp_handle_error(mon, errp);
-+}
-+
-+void hmp_snapshot_drive(Monitor *mon, const QDict *qdict)
-+{
-+ Error *errp = NULL;
-+ const char *name = qdict_get_str(qdict, "name");
-+ const char *device = qdict_get_str(qdict, "device");
-+
-+ qmp_snapshot_drive(device, name, &errp);
-+ hmp_handle_error(mon, errp);
-+}
-+
-+void hmp_delete_drive_snapshot(Monitor *mon, const QDict *qdict)
-+{
-+ Error *errp = NULL;
-+ const char *name = qdict_get_str(qdict, "name");
-+ const char *device = qdict_get_str(qdict, "device");
-+
-+ qmp_delete_drive_snapshot(device, name, &errp);
++ qmp_savevm_start(statefile, &errp);
+ hmp_handle_error(mon, errp);
+}
+
+ SaveVMInfo *info;
+ info = qmp_query_savevm(NULL);
+
-+ if (info->has_status) {
++ if (info->status) {
+ monitor_printf(mon, "savevm status: %s\n", info->status);
+ monitor_printf(mon, "total time: %" PRIu64 " milliseconds\n",
+ info->total_time);
+ if (info->has_bytes) {
+ monitor_printf(mon, "Bytes saved: %"PRIu64"\n", info->bytes);
+ }
-+ if (info->has_error) {
++ if (info->error) {
+ monitor_printf(mon, "Error: %s\n", info->error);
+ }
+}
-+
- void hmp_info_iothreads(Monitor *mon, const QDict *qdict)
- {
- IOThreadInfoList *info_list = qmp_query_iothreads(NULL);
diff --git a/qapi/migration.json b/qapi/migration.json
-index 88ecf86ac8..4435866379 100644
+index 197d3faa43..b41465fbe9 100644
--- a/qapi/migration.json
+++ b/qapi/migration.json
-@@ -261,6 +261,40 @@
- '*compression': 'CompressionStats',
- '*socket-address': ['SocketAddress'] } }
+@@ -298,6 +298,40 @@
+ '*dirty-limit-throttle-time-per-round': 'uint64',
+ '*dirty-limit-ring-full-time': 'uint64'} }
+##
+# @SaveVMInfo:
# @query-migrate:
#
diff --git a/qapi/misc.json b/qapi/misc.json
-index 27ef5a2b20..b3ce75dcae 100644
+index cda2effa81..94a58bb0bf 100644
--- a/qapi/misc.json
+++ b/qapi/misc.json
-@@ -435,6 +435,38 @@
+@@ -456,6 +456,22 @@
##
{ 'command': 'query-fdsets', 'returns': ['FdsetInfo'] }
+{ 'command': 'savevm-start', 'data': { '*statefile': 'str' } }
+
+##
-+# @snapshot-drive:
-+#
-+# Create an internal drive snapshot.
-+#
-+##
-+{ 'command': 'snapshot-drive', 'data': { 'device': 'str', 'name': 'str' } }
-+
-+##
-+# @delete-drive-snapshot:
-+#
-+# Delete a drive snapshot.
-+#
-+##
-+{ 'command': 'delete-drive-snapshot', 'data': { 'device': 'str', 'name': 'str' } }
-+
-+##
+# @savevm-end:
+#
+# Resume VM after a snapshot.
# @CommandLineParameterType:
#
diff --git a/qemu-options.hx b/qemu-options.hx
-index 7f99d15b23..54efb127c4 100644
+index b6b4ad9e67..881b0b3c43 100644
--- a/qemu-options.hx
+++ b/qemu-options.hx
-@@ -4391,6 +4391,18 @@ SRST
+@@ -4590,6 +4590,18 @@ SRST
Start right away with a saved state (``loadvm`` in monitor)
ERST
#ifndef _WIN32
DEF("daemonize", 0, QEMU_OPTION_daemonize, \
"-daemonize daemonize QEMU after initializing\n", QEMU_ARCH_ALL)
-diff --git a/softmmu/vl.c b/softmmu/vl.c
-index 5f7f6ca981..21f067d115 100644
---- a/softmmu/vl.c
-+++ b/softmmu/vl.c
-@@ -164,6 +164,7 @@ static const char *accelerators;
+diff --git a/system/vl.c b/system/vl.c
+index d2a3b3f457..57f7ba0525 100644
+--- a/system/vl.c
++++ b/system/vl.c
+@@ -163,6 +163,7 @@ static const char *accelerators;
static bool have_custom_ram_size;
static const char *ram_memdev_id;
static QDict *machine_opts_dict;
static QTAILQ_HEAD(, ObjectOption) object_opts = QTAILQ_HEAD_INITIALIZER(object_opts);
static QTAILQ_HEAD(, DeviceOption) device_opts = QTAILQ_HEAD_INITIALIZER(device_opts);
static int display_remote;
-@@ -2607,6 +2608,12 @@ void qmp_x_exit_preconfig(Error **errp)
+@@ -2715,6 +2716,12 @@ void qmp_x_exit_preconfig(Error **errp)
if (loadvm) {
load_snapshot(loadvm, NULL, false, NULL, &error_fatal);
}
if (replay_mode != REPLAY_MODE_NONE) {
replay_vmstate_init();
-@@ -3151,6 +3158,9 @@ void qemu_init(int argc, char **argv)
+@@ -3265,6 +3272,9 @@ void qemu_init(int argc, char **argv)
case QEMU_OPTION_loadvm:
loadvm = optarg;
break;